diff --git a/.clang_complete b/.clang_complete index d7cda0ff..e88dbc7b 100644 --- a/.clang_complete +++ b/.clang_complete @@ -1,5 +1,5 @@ -xc++ --std=c++14 +-std=c++17 -iquote . -iquote tdtl/ -iquote tl/ diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..78c6ddee --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 2 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..dbcae6ab --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/.github/script/amd64-20.04.Dockerfile b/.github/script/amd64-20.04.Dockerfile new file mode 100644 index 00000000..40d980e5 --- /dev/null +++ b/.github/script/amd64-20.04.Dockerfile @@ -0,0 +1,20 @@ +FROM ubuntu:20.04 + +RUN apt update +RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata +RUN apt install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git curl libreadline-dev ccache libmicrohttpd-dev ninja-build libsecp256k1-dev libsodium-dev pkg-config + +WORKDIR / + +ARG BRANCH +ARG REPO +RUN git clone --recurse-submodules https://github.com/$REPO ton && cd ton && git checkout $BRANCH && git submodule update + +WORKDIR /ton +RUN mkdir /ton/build +WORKDIR /ton/build +ENV CC clang +ENV CXX clang++ +ENV CCACHE_DISABLE 1 +RUN cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DPORTABLE=1 -DTON_ARCH= -DCMAKE_CXX_FLAGS="-mavx2" .. +RUN ninja storage-daemon storage-daemon-cli tonlibjson blockchain-explorer fift func validator-engine validator-engine-console create-state generate-random-id create-hardfork dht-server lite-client \ No newline at end of file diff --git a/.github/script/amd64-22.04.Dockerfile b/.github/script/amd64-22.04.Dockerfile new file mode 100644 index 00000000..44c9c40b --- /dev/null +++ b/.github/script/amd64-22.04.Dockerfile @@ -0,0 +1,20 @@ +FROM ubuntu:22.04 + +RUN apt update +RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata +RUN apt install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git curl libreadline-dev ccache libmicrohttpd-dev ninja-build libsecp256k1-dev libsodium-dev pkg-config + +WORKDIR / + +ARG BRANCH +ARG REPO +RUN git clone --recurse-submodules https://github.com/$REPO ton && cd ton && git checkout $BRANCH && git submodule update + +WORKDIR /ton +RUN mkdir /ton/build +WORKDIR /ton/build +ENV CC clang +ENV CXX clang++ +ENV CCACHE_DISABLE 1 +RUN cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DPORTABLE=1 -DTON_ARCH= -DCMAKE_CXX_FLAGS="-mavx2" .. +RUN ninja storage-daemon storage-daemon-cli tonlibjson blockchain-explorer fift func validator-engine validator-engine-console create-state generate-random-id create-hardfork dht-server lite-client \ No newline at end of file diff --git a/.github/script/arm64-20.04.Dockerfile b/.github/script/arm64-20.04.Dockerfile new file mode 100644 index 00000000..1f57dc40 --- /dev/null +++ b/.github/script/arm64-20.04.Dockerfile @@ -0,0 +1,20 @@ +FROM ubuntu:20.04 + +RUN apt update +RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata +RUN apt install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git curl libreadline-dev ccache libmicrohttpd-dev ninja-build libsecp256k1-dev libsodium-dev pkg-config + +WORKDIR / + +ARG BRANCH +ARG REPO +RUN git clone --recurse-submodules https://github.com/$REPO ton && cd ton && git checkout $BRANCH && git submodule update + +WORKDIR /ton +RUN mkdir /ton/build +WORKDIR /ton/build +ENV CC clang +ENV CXX clang++ +ENV CCACHE_DISABLE 1 +RUN cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DPORTABLE=1 -DTON_ARCH= .. +RUN ninja storage-daemon storage-daemon-cli tonlibjson blockchain-explorer fift func validator-engine validator-engine-console create-state generate-random-id dht-server lite-client \ No newline at end of file diff --git a/.github/script/arm64-22.04.Dockerfile b/.github/script/arm64-22.04.Dockerfile new file mode 100644 index 00000000..2b595839 --- /dev/null +++ b/.github/script/arm64-22.04.Dockerfile @@ -0,0 +1,20 @@ +FROM ubuntu:22.04 + +RUN apt update +RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata +RUN apt install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git curl libreadline-dev ccache libmicrohttpd-dev ninja-build libsecp256k1-dev libsodium-dev pkg-config + +WORKDIR / + +ARG BRANCH +ARG REPO +RUN git clone --recurse-submodules https://github.com/$REPO ton && cd ton && git checkout $BRANCH && git submodule update + +WORKDIR /ton +RUN mkdir /ton/build +WORKDIR /ton/build +ENV CC clang +ENV CXX clang++ +ENV CCACHE_DISABLE 1 +RUN cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DPORTABLE=1 -DTON_ARCH= .. +RUN ninja storage-daemon storage-daemon-cli tonlibjson blockchain-explorer fift func validator-engine validator-engine-console create-state generate-random-id dht-server lite-client \ No newline at end of file diff --git a/.github/workflows/build-ton-linux-android-tonlib.yml b/.github/workflows/build-ton-linux-android-tonlib.yml new file mode 100644 index 00000000..bbd95661 --- /dev/null +++ b/.github/workflows/build-ton-linux-android-tonlib.yml @@ -0,0 +1,32 @@ +name: Tonlib Android + +on: [push,workflow_dispatch,workflow_call] + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - name: Install system libraries + run: | + sudo apt-get update + sudo apt-get install -y build-essential git cmake ninja-build automake libtool texinfo autoconf libgflags-dev \ + zlib1g-dev libssl-dev libreadline-dev libmicrohttpd-dev pkg-config libgsl-dev python3 python3-dev \ + libtool autoconf libsodium-dev libsecp256k1-dev liblz4-dev + + - name: Build TON + run: | + cp assembly/android/build-android-tonlib.sh . + chmod +x build-android-tonlib.sh + ./build-android-tonlib.sh -a + + - name: Upload artifacts + uses: actions/upload-artifact@master + with: + name: tonlib-android + path: artifacts diff --git a/.github/workflows/build-ton-linux-x86-64-shared.yml b/.github/workflows/build-ton-linux-x86-64-shared.yml new file mode 100644 index 00000000..ce0ade64 --- /dev/null +++ b/.github/workflows/build-ton-linux-x86-64-shared.yml @@ -0,0 +1,40 @@ +name: Ubuntu TON build (shared, x86-64) + +on: [push,workflow_dispatch,workflow_call] + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [ubuntu-20.04, ubuntu-22.04] + runs-on: ${{ matrix.os }} + + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - name: Install system libraries + run: | + sudo apt-get update + sudo apt-get install -y build-essential git cmake ninja-build zlib1g-dev libsecp256k1-dev libmicrohttpd-dev libsodium-dev liblz4-dev + + - name: Install clang-16 + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 16 all + + - name: Build TON + run: | + cp assembly/native/build-ubuntu-shared.sh . + chmod +x build-ubuntu-shared.sh + ./build-ubuntu-shared.sh -t -a + + - name: Upload artifacts + uses: actions/upload-artifact@master + with: + name: ton-binaries-${{ matrix.os }} + path: artifacts diff --git a/.github/workflows/build-ton-macos-x86-64-shared.yml b/.github/workflows/build-ton-macos-x86-64-shared.yml new file mode 100644 index 00000000..c9331e3b --- /dev/null +++ b/.github/workflows/build-ton-macos-x86-64-shared.yml @@ -0,0 +1,25 @@ +name: MacOS TON build (shared, x86-64) + +on: [push,workflow_dispatch,workflow_call] + +jobs: + build: + runs-on: macos-12 + + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - name: Build TON + run: | + cp assembly/native/build-macos-shared.sh . + chmod +x build-macos-shared.sh + ./build-macos-shared.sh -t -a + + - name: Upload artifacts + uses: actions/upload-artifact@master + with: + name: ton-binaries-macos-12 + path: artifacts diff --git a/.github/workflows/build-ton-wasm-emscripten.yml b/.github/workflows/build-ton-wasm-emscripten.yml new file mode 100644 index 00000000..92107ffd --- /dev/null +++ b/.github/workflows/build-ton-wasm-emscripten.yml @@ -0,0 +1,30 @@ +name: Emscripten TON build (wasm) + +on: [push,workflow_dispatch,workflow_call] + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - name: Install system libraries + run: | + sudo apt-get update + sudo apt-get install -y build-essential git openssl cmake ninja-build zlib1g-dev libssl-dev libsecp256k1-dev libmicrohttpd-dev libsodium-dev liblz4-dev + + - name: Build TON WASM artifacts + run: | + cd assembly/wasm + chmod +x fift-func-wasm-build-ubuntu.sh + ./fift-func-wasm-build-ubuntu.sh -a + + - name: Upload artifacts + uses: actions/upload-artifact@master + with: + name: ton-wasm-binaries + path: artifacts diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 00000000..50d2661b --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,396 @@ +name: Create release + +on: [workflow_dispatch] + +permissions: write-all + +jobs: + create-release: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + + - name: Download Linux x86-64 artifacts + uses: dawidd6/action-download-artifact@v2 + with: + workflow: ton-x86-64-linux.yml + path: artifacts + workflow_conclusion: success + skip_unpack: true + + - name: Download and unzip Linux x86-64 artifacts + uses: dawidd6/action-download-artifact@v2 + with: + workflow: ton-x86-64-linux.yml + path: artifacts + workflow_conclusion: success + skip_unpack: false + + - name: Download Mac x86-64 artifacts + uses: dawidd6/action-download-artifact@v2 + with: + workflow: ton-x86-64-macos.yml + path: artifacts + workflow_conclusion: success + skip_unpack: true + + - name: Download and unzip Mac x86-64 artifacts + uses: dawidd6/action-download-artifact@v2 + with: + workflow: ton-x86-64-macos.yml + path: artifacts + workflow_conclusion: success + skip_unpack: false + + - name: Download Windows artifacts + uses: dawidd6/action-download-artifact@v2 + with: + workflow: ton-x86-64-windows.yml + path: artifacts + workflow_conclusion: success + skip_unpack: true + + - name: Download and unzip Windows artifacts + uses: dawidd6/action-download-artifact@v2 + with: + workflow: ton-x86-64-windows.yml + path: artifacts + workflow_conclusion: success + skip_unpack: false + + - name: Download WASM artifacts + uses: dawidd6/action-download-artifact@v2 + with: + workflow: build-ton-wasm-emscripten.yml + path: artifacts + workflow_conclusion: success + skip_unpack: true + + - name: Show all artifacts + run: | + tree artifacts + + + # create release + + - name: Read Changelog.md and use it as a body of new release + id: read_release + shell: bash + run: | + r=$(cat recent_changelog.md) + r="${r//'%'/'%25'}" + r="${r//$'\n'/'%0A'}" + r="${r//$'\r'/'%0D'}" + echo "::set-output name=CHANGELOG_BODY::$r" + + - name: Get next tag + id: tag + run: | + git fetch --all --tags + git tag -l + NEW_TAG=v$(date +'%Y.%m') + FOUND=$(git tag -l | grep $NEW_TAG | wc -l) + if [ $FOUND -eq 0 ]; then + echo "TAG=$NEW_TAG" >> $GITHUB_OUTPUT + else + echo "TAG=$NEW_TAG-$FOUND" >> $GITHUB_OUTPUT + fi + + - name: Get registration token + id: getRegToken + run: | + curl -X POST -H \"Accept: application/vnd.github+json\" -H 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' https://api.github.com/repos/ton-blockchain/ton/actions/runners/registration-token + + - name: Create release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.tag.outputs.TAG }} + release_name: TON ${{ steps.tag.outputs.TAG }} + body: | + ${{ steps.read_release.outputs.CHANGELOG_BODY }} + draft: false + prerelease: false + + # upload + + # win + + - name: Upload Windows 2019 artifacts + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-win-binaries.zip + asset_name: ton-win-x86-64.zip + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Windows 2019 single artifact - fift + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-win-binaries/fift.exe + asset_name: fift.exe + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Windows 2019 single artifact - func + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-win-binaries/func.exe + asset_name: func.exe + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Windows 2019 single artifact - lite-client + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-win-binaries/lite-client.exe + asset_name: lite-client.exe + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Windows 2019 single artifact - rldp-http-proxy + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-win-binaries/rldp-http-proxy.exe + asset_name: rldp-http-proxy.exe + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Windows 2019 single artifact - http-proxy + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-win-binaries/http-proxy.exe + asset_name: http-proxy.exe + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Windows 2019 single artifact - storage-daemon-cli + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-win-binaries/storage-daemon-cli.exe + asset_name: storage-daemon-cli.exe + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Windows 2019 single artifact - storage-daemon + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-win-binaries/storage-daemon.exe + asset_name: storage-daemon.exe + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Windows 2019 single artifact - tonlibjson + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-win-binaries/tonlibjson.dll + asset_name: tonlibjson.dll + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Windows 2019 single artifact - libemulator + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-win-binaries/emulator.dll + asset_name: libemulator.dll + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Windows 2019 single artifact - tonlib-cli + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-win-binaries/tonlib-cli.exe + asset_name: tonlib-cli.exe + tag: ${{ steps.tag.outputs.TAG }} + + # mac x86-64 + + - name: Upload Mac x86-64 artifacts + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries.zip + asset_name: ton-mac-x86-64.zip + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Mac x86-64 single artifact - fift + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/fift + asset_name: fift-mac-x86-64 + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Mac x86-64 single artifact - func + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/func + asset_name: func-mac-x86-64 + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Mac x86-64 single artifact - lite-client + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/lite-client + asset_name: lite-client-mac-x86-64 + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Mac x86-64 single artifact - rldp-http-proxy + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/rldp-http-proxy + asset_name: rldp-http-proxy-mac-x86-64 + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Mac x86-64 single artifact - http-proxy + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/http-proxy + asset_name: http-proxy-mac-x86-64 + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Mac x86-64 single artifact - storage-daemon-cli + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/storage-daemon-cli + asset_name: storage-daemon-cli-mac-x86-64 + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Mac x86-64 single artifact - storage-daemon + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/storage-daemon + asset_name: storage-daemon-mac-x86-64 + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Mac x86-64 single artifact - tonlibjson + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/libtonlibjson.dylib + asset_name: tonlibjson-mac-x86-64.dylib + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Mac x86-64 single artifact - libemulator + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/libemulator.dylib + asset_name: libemulator-mac-x86-64.dylib + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Mac x86-64 single artifact - tonlib-cli + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-macos-binaries/tonlib-cli + asset_name: tonlib-cli-mac-x86-64 + tag: ${{ steps.tag.outputs.TAG }} + + # linux x86-64 + + - name: Upload Linux x86-64 artifacts + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries.zip + asset_name: ton-linux-x86_64.zip + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Linux x86-64 single artifact - fift + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/fift + asset_name: fift-linux-x86_64 + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Linux x86-64 single artifact - func + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/func + asset_name: func-linux-x86_64 + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Linux x86-64 single artifact - lite-client + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/lite-client + asset_name: lite-client-linux-x86_64 + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Linux x86-64 single artifact - rldp-http-proxy + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/rldp-http-proxy + asset_name: rldp-http-proxy-linux-x86_64 + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Linux x86-64 single artifact - http-proxy + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/http-proxy + asset_name: http-proxy-linux-x86_64 + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Linux x86-64 single artifact - storage-daemon-cli + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/storage-daemon-cli + asset_name: storage-daemon-cli-linux-x86_64 + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Linux x86-64 single artifact - storage-daemon + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/storage-daemon + asset_name: storage-daemon-linux-x86_64 + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Linux x86-64 single artifact - tonlibjson + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/libtonlibjson.so + asset_name: tonlibjson-linux-x86_64.so + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Linux x86-64 single artifact - libemulator + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/libemulator.so + asset_name: libemulator-linux-x86_64.so + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload Linux x86-64 single artifact - tonlib-cli + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-x86_64-linux-binaries/tonlib-cli + asset_name: tonlib-cli-linux-x86_64 + tag: ${{ steps.tag.outputs.TAG }} + + - name: Upload WASM artifacts + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: artifacts/ton-wasm-binaries.zip + asset_name: ton-wasm-binaries.zip + tag: ${{ steps.tag.outputs.TAG }} diff --git a/.github/workflows/docker-ubuntu-image.yml b/.github/workflows/docker-ubuntu-image.yml new file mode 100644 index 00000000..449711d8 --- /dev/null +++ b/.github/workflows/docker-ubuntu-image.yml @@ -0,0 +1,41 @@ +name: Docker Ubuntu 22.04 image + +on: + workflow_dispatch: + push: + branches: + - 'master' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push: + runs-on: ubuntu-22.04 + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + with: + push: true + context: ./ + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest diff --git a/.github/workflows/ton-ccpcheck.yml b/.github/workflows/ton-ccpcheck.yml new file mode 100644 index 00000000..d2d8cf70 --- /dev/null +++ b/.github/workflows/ton-ccpcheck.yml @@ -0,0 +1,26 @@ +name: TON Static Code Analysis + +on: [push,workflow_dispatch,workflow_call] + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - name: Run Cppcheck + uses: Bedzior/run-cppcheck@master + with: + enabled checks: all + enable inconclusive: true + generate report: true + + - name: Upload report + uses: actions/upload-artifact@v1 + with: + name: ton-ccpcheck-report + path: output diff --git a/.github/workflows/ton-x86-64-linux.yml b/.github/workflows/ton-x86-64-linux.yml new file mode 100644 index 00000000..abbe1cca --- /dev/null +++ b/.github/workflows/ton-x86-64-linux.yml @@ -0,0 +1,41 @@ +name: Ubuntu TON build (portable, x86-64) + +on: [push,workflow_dispatch,workflow_call] + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - run: | + sudo apt update + sudo apt install -y apt-utils + + - uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - uses: cachix/install-nix-action@v23 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + + - name: Build TON + run: | + cp assembly/nix/build-linux-x86-64-nix.sh . + chmod +x build-linux-x86-64-nix.sh + ./build-linux-x86-64-nix.sh -t + + - name: Simple binaries test + run: | + sudo mv /nix/store /nix/store2 + artifacts/validator-engine -V + artifacts/lite-client -V + artifacts/fift -V + artifacts/func -V + + - name: Upload artifacts + uses: actions/upload-artifact@master + with: + name: ton-x86_64-linux-binaries + path: artifacts diff --git a/.github/workflows/ton-x86-64-macos.yml b/.github/workflows/ton-x86-64-macos.yml new file mode 100644 index 00000000..8c71f34a --- /dev/null +++ b/.github/workflows/ton-x86-64-macos.yml @@ -0,0 +1,37 @@ +name: MacOS TON build (portable, x86-64) + +on: [push,workflow_dispatch,workflow_call] + +jobs: + build: + runs-on: macos-12 + + steps: + - uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - uses: cachix/install-nix-action@v23 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + + - name: Build TON + run: | + cp assembly/nix/build-macos-nix.sh . + chmod +x build-macos-nix.sh + ./build-macos-nix.sh -t + + - name: Simple binaries test + run: | + sudo mv /nix/store /nix/store2 + artifacts/validator-engine -V + artifacts/lite-client -V + artifacts/fift -V + artifacts/func -V + + - name: Upload artifacts + uses: actions/upload-artifact@master + with: + name: ton-x86_64-macos-binaries + path: artifacts diff --git a/.github/workflows/ton-x86-64-windows.yml b/.github/workflows/ton-x86-64-windows.yml new file mode 100644 index 00000000..67026183 --- /dev/null +++ b/.github/workflows/ton-x86-64-windows.yml @@ -0,0 +1,34 @@ +name: Windows TON build (portable, x86-64) + +on: [push,workflow_dispatch,workflow_call] + +defaults: + run: + shell: cmd + +jobs: + build: + + runs-on: windows-2022 + + steps: + - name: Get Current OS version + run: | + systeminfo | findstr /B /C:"OS Name" /C:"OS Version" + + - name: Check out current repository + uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - name: Build TON + run: | + copy assembly\native\build-windows-github.bat . + copy assembly\native\build-windows.bat . + build-windows-github.bat Enterprise + + - name: Upload artifacts + uses: actions/upload-artifact@master + with: + name: ton-win-binaries + path: artifacts diff --git a/.gitignore b/.gitignore index 44ed769e..536918ab 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,19 @@ tl/generate/auto/ compile_commands.json crypto/block/block-auto.cpp crypto/block/block-auto.h -crypto/smartcont/*-code.fif crypto/smartcont/auto/ test/regression-tests.cache/ *.swp **/*build*/ +.idea +.vscode +zlib/ +libsodium/ +libmicrohttpd-0.9.77-w32-bin/ +readline-5.0-1-lib/ +secp256k1/ +openssl-3.1.4/ +libsodium-1.0.18-stable-msvc.zip +libmicrohttpd-0.9.77-w32-bin.zip +openssl-3.1.4.zip +readline-5.0-1-lib.zip diff --git a/.gitmodules b/.gitmodules index e6a47e8b..f201ed73 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "third-party/libraptorq"] path = third-party/libraptorq url = https://github.com/ton-blockchain/libRaptorQ +[submodule "third-party/blst"] + path = third-party/blst + url = https://github.com/supranational/blst.git diff --git a/CMake/BuildBLST.cmake b/CMake/BuildBLST.cmake new file mode 100644 index 00000000..1cf2366c --- /dev/null +++ b/CMake/BuildBLST.cmake @@ -0,0 +1,30 @@ +set(BLST_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third-party/blst) +set(BLST_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/third-party/blst) +set(BLST_INCLUDE_DIR ${BLST_SOURCE_DIR}/bindings) + +if (NOT BLST_LIB) + if (WIN32) + set(BLST_LIB ${BLST_BINARY_DIR}/blst.lib) + set(BLST_BUILD_COMMAND ${BLST_SOURCE_DIR}/build.bat) + else() + set(BLST_LIB ${BLST_BINARY_DIR}/libblst.a) + if (PORTABLE) + set(BLST_BUILD_COMMAND ${BLST_SOURCE_DIR}/build.sh -D__BLST_PORTABLE__) + else() + set(BLST_BUILD_COMMAND ${BLST_SOURCE_DIR}/build.sh) + endif() + endif() + + file(MAKE_DIRECTORY ${BLST_BINARY_DIR}) + add_custom_command( + WORKING_DIRECTORY ${BLST_BINARY_DIR} + COMMAND ${BLST_BUILD_COMMAND} + COMMENT "Build blst" + DEPENDS ${BLST_SOURCE_DIR} + OUTPUT ${BLST_LIB} + ) +else() + message(STATUS "Use BLST: ${BLST_LIB}") +endif() + +add_custom_target(blst DEPENDS ${BLST_LIB}) diff --git a/CMake/FindMHD.cmake b/CMake/FindMHD.cmake index 822714a2..7d6dd5fd 100644 --- a/CMake/FindMHD.cmake +++ b/CMake/FindMHD.cmake @@ -2,37 +2,27 @@ # Once done this will define # # MHD_FOUND - system has MHD -# MHD_INCLUDE_DIRS - the MHD include directory +# MHD_INCLUDE_DIR - the MHD include directory # MHD_LIBRARY - Link these to use MHD -find_path( - MHD_INCLUDE_DIR - NAMES microhttpd.h - DOC "microhttpd include dir" -) - -find_library( - MHD_LIBRARY - NAMES microhttpd microhttpd-10 libmicrohttpd libmicrohttpd-dll - DOC "microhttpd library" -) - -set(MHD_INCLUDE_DIRS ${MHD_INCLUDE_DIR}) -set(MHD_LIBRARIES ${MHD_LIBRARY}) - -# debug library on windows -# same naming convention as in qt (appending debug library with d) -# boost is using the same "hack" as us with "optimized" and "debug" -# official MHD project actually uses _d suffix -if (MSVC) - find_library( - MHD_LIBRARY_DEBUG - NAMES microhttpd_d microhttpd-10_d libmicrohttpd_d libmicrohttpd-dll_d - DOC "mhd debug library" +if (NOT MHD_LIBRARY) + find_path( + MHD_INCLUDE_DIR + NAMES microhttpd.h + DOC "microhttpd include dir" ) - set(MHD_LIBRARIES optimized ${MHD_LIBRARIES} debug ${MHD_LIBRARY_DEBUG}) + + find_library( + MHD_LIBRARY + NAMES microhttpd microhttpd-10 libmicrohttpd libmicrohttpd-dll + DOC "microhttpd library" + ) +endif() + +if (MHD_LIBRARY) + message(STATUS "Found MHD: ${MHD_LIBRARY}") endif() include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(mhd DEFAULT_MSG MHD_INCLUDE_DIR MHD_LIBRARY) +find_package_handle_standard_args(MHD DEFAULT_MSG MHD_INCLUDE_DIR MHD_LIBRARY) mark_as_advanced(MHD_INCLUDE_DIR MHD_LIBRARY) diff --git a/CMake/FindSecp256k1.cmake b/CMake/FindSecp256k1.cmake new file mode 100644 index 00000000..68a37c71 --- /dev/null +++ b/CMake/FindSecp256k1.cmake @@ -0,0 +1,28 @@ +# - Try to find SECP256K1 +# Once done this will define +# +# SECP256K1_FOUND - system has SECP256K1 +# SECP256K1_INCLUDE_DIR - the SECP256K1 include directory +# SECP256K1_LIBRARY - Link these to use SECP256K1 + +if (NOT SECP256K1_LIBRARY) + find_path( + SECP256K1_INCLUDE_DIR + NAMES secp256k1_recovery.h + DOC "secp256k1_recovery.h include dir" + ) + + find_library( + SECP256K1_LIBRARY + NAMES secp256k1 libsecp256k1 + DOC "secp256k1 library" + ) +endif() + +if (SECP256K1_LIBRARY) + message(STATUS "Found Secp256k1: ${SECP256K1_LIBRARY}") +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Secp256k1 DEFAULT_MSG SECP256K1_INCLUDE_DIR SECP256K1_LIBRARY) +mark_as_advanced(SECP256K1_INCLUDE_DIR SECP256K1_LIBRARY) diff --git a/CMake/FindSodium.cmake b/CMake/FindSodium.cmake new file mode 100644 index 00000000..85194ee2 --- /dev/null +++ b/CMake/FindSodium.cmake @@ -0,0 +1,298 @@ +# Written in 2016 by Henrik Steffen Gaßmann +# +# To the extent possible under law, the author(s) have dedicated all +# copyright and related and neighboring rights to this software to the +# public domain worldwide. This software is distributed without any warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication +# along with this software. If not, see +# +# http://creativecommons.org/publicdomain/zero/1.0/ +# +######################################################################## +# Tries to find the local libsodium installation. +# +# On Windows the SODIUM_DIR environment variable is used as a default +# hint which can be overridden by setting the corresponding cmake variable. +# +# Once done the following variables will be defined: +# +# SODIUM_FOUND +# SODIUM_INCLUDE_DIR +# SODIUM_LIBRARY_DEBUG +# SODIUM_LIBRARY_RELEASE +# +# +# Furthermore an imported "sodium" target is created. +# + + +if (CMAKE_C_COMPILER_ID STREQUAL "GNU" + OR CMAKE_C_COMPILER_ID STREQUAL "Clang") + set(_GCC_COMPATIBLE 1) +endif() + +# static library option +if (NOT DEFINED SODIUM_USE_STATIC_LIBS) + option(SODIUM_USE_STATIC_LIBS "enable to statically link against sodium" OFF) +endif() +if(NOT (SODIUM_USE_STATIC_LIBS EQUAL SODIUM_USE_STATIC_LIBS_LAST)) + unset(sodium_LIBRARY CACHE) + unset(SODIUM_LIBRARY_DEBUG CACHE) + unset(SODIUM_LIBRARY_RELEASE CACHE) + unset(sodium_DLL_DEBUG CACHE) + unset(sodium_DLL_RELEASE CACHE) + set(SODIUM_USE_STATIC_LIBS_LAST ${SODIUM_USE_STATIC_LIBS} CACHE INTERNAL "internal change tracking variable") +endif() + + +######################################################################## +# UNIX +if (UNIX) + # import pkg-config + find_package(PkgConfig QUIET) + if (PKG_CONFIG_FOUND) + pkg_check_modules(sodium_PKG QUIET libsodium) + endif() + + if(SODIUM_USE_STATIC_LIBS) + foreach(_libname ${sodium_PKG_STATIC_LIBRARIES}) + if (NOT _libname MATCHES "^lib.*\\.a$") # ignore strings already ending with .a + list(INSERT sodium_PKG_STATIC_LIBRARIES 0 "lib${_libname}.a") + endif() + endforeach() + list(REMOVE_DUPLICATES sodium_PKG_STATIC_LIBRARIES) + + # if pkgconfig for libsodium doesn't provide + # static lib info, then override PKG_STATIC here.. + if (NOT sodium_PKG_STATIC_FOUND) + set(sodium_PKG_STATIC_LIBRARIES libsodium.a) + endif() + + set(XPREFIX sodium_PKG_STATIC) + else() + if (NOT sodium_PKG_FOUND) + set(sodium_PKG_LIBRARIES sodium) + endif() + + set(XPREFIX sodium_PKG) + endif() + + find_path(SODIUM_INCLUDE_DIR sodium.h + HINTS ${${XPREFIX}_INCLUDE_DIRS} + ) + find_library(SODIUM_LIBRARY_DEBUG NAMES ${${XPREFIX}_LIBRARIES} + HINTS ${${XPREFIX}_LIBRARY_DIRS} + ) + find_library(SODIUM_LIBRARY_RELEASE NAMES ${${XPREFIX}_LIBRARIES} + HINTS ${${XPREFIX}_LIBRARY_DIRS} + ) + + ######################################################################## + # Windows +elseif (WIN32) + set(SODIUM_DIR "$ENV{SODIUM_DIR}" CACHE FILEPATH "sodium install directory") + mark_as_advanced(SODIUM_DIR) + + find_path(SODIUM_INCLUDE_DIR + NAMES sodium.h + HINTS ${SODIUM_DIR} + PATH_SUFFIXES include + ) + + if (MSVC) + # detect target architecture + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/arch.cpp" [=[ + #if defined _M_IX86 + #error ARCH_VALUE x86_32 + #elif defined _M_X64 + #error ARCH_VALUE x86_64 + #endif + #error ARCH_VALUE unknown + ]=]) + try_compile(_UNUSED_VAR "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/arch.cpp" + OUTPUT_VARIABLE _COMPILATION_LOG + ) + string(REGEX REPLACE ".*ARCH_VALUE ([a-zA-Z0-9_]+).*" "\\1" _TARGET_ARCH "${_COMPILATION_LOG}") + + # construct library path + if (_TARGET_ARCH STREQUAL "x86_32") + string(APPEND _PLATFORM_PATH "Win32") + elseif(_TARGET_ARCH STREQUAL "x86_64") + string(APPEND _PLATFORM_PATH "x64") + else() + message(FATAL_ERROR "the ${_TARGET_ARCH} architecture is not supported by Findsodium.cmake.") + endif() + string(APPEND _PLATFORM_PATH "/$$CONFIG$$") + + if (MSVC_VERSION LESS 1900) + math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 60") + else() + math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 50") + endif() + string(APPEND _PLATFORM_PATH "/v${_VS_VERSION}") + + if (SODIUM_USE_STATIC_LIBS) + string(APPEND _PLATFORM_PATH "/static") + else() + string(APPEND _PLATFORM_PATH "/dynamic") + endif() + + string(REPLACE "$$CONFIG$$" "Debug" _DEBUG_PATH_SUFFIX "${_PLATFORM_PATH}") + string(REPLACE "$$CONFIG$$" "Release" _RELEASE_PATH_SUFFIX "${_PLATFORM_PATH}") + + find_library(SODIUM_LIBRARY_DEBUG libsodium.lib + HINTS ${SODIUM_DIR} + PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX} + ) + find_library(SODIUM_LIBRARY_RELEASE libsodium.lib + HINTS ${SODIUM_DIR} + PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX} + ) + if (NOT SODIUM_USE_STATIC_LIBS) + set(CMAKE_FIND_LIBRARY_SUFFIXES_BCK ${CMAKE_FIND_LIBRARY_SUFFIXES}) + set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll") + find_library(sodium_DLL_DEBUG libsodium + HINTS ${SODIUM_DIR} + PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX} + ) + find_library(sodium_DLL_RELEASE libsodium + HINTS ${SODIUM_DIR} + PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX} + ) + set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_BCK}) + endif() + + elseif(_GCC_COMPATIBLE) + if (SODIUM_USE_STATIC_LIBS) + find_library(SODIUM_LIBRARY_DEBUG libsodium.a + HINTS ${SODIUM_DIR} + PATH_SUFFIXES lib + ) + find_library(SODIUM_LIBRARY_RELEASE libsodium.a + HINTS ${SODIUM_DIR} + PATH_SUFFIXES lib + ) + else() + find_library(SODIUM_LIBRARY_DEBUG libsodium.dll.a + HINTS ${SODIUM_DIR} + PATH_SUFFIXES lib + ) + find_library(SODIUM_LIBRARY_RELEASE libsodium.dll.a + HINTS ${SODIUM_DIR} + PATH_SUFFIXES lib + ) + + file(GLOB _DLL + LIST_DIRECTORIES false + RELATIVE "${SODIUM_DIR}/bin" + "${SODIUM_DIR}/bin/libsodium*.dll" + ) + find_library(sodium_DLL_DEBUG ${_DLL} libsodium + HINTS ${SODIUM_DIR} + PATH_SUFFIXES bin + ) + find_library(sodium_DLL_RELEASE ${_DLL} libsodium + HINTS ${SODIUM_DIR} + PATH_SUFFIXES bin + ) + endif() + else() + message(FATAL_ERROR "this platform is not supported by FindSodium.cmake") + endif() + + + ######################################################################## + # unsupported +else() + message(FATAL_ERROR "this platform is not supported by FindSodium.cmake") +endif() + + +######################################################################## +# common stuff + +# extract sodium version +if (SODIUM_INCLUDE_DIR) + set(_VERSION_HEADER "${_INCLUDE_DIR}/sodium/version.h") + if (EXISTS _VERSION_HEADER) + file(READ "${_VERSION_HEADER}" _VERSION_HEADER_CONTENT) + string(REGEX REPLACE ".*#[ \t]*define[ \t]*SODIUM_VERSION_STRING[ \t]*\"([^\n]*)\".*" "\\1" + sodium_VERSION "${_VERSION_HEADER_CONTENT}") + set(sodium_VERSION "${sodium_VERSION}" PARENT_SCOPE) + endif() +endif() + +# communicate results +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + Sodium # The name must be either uppercase or match the filename case. + REQUIRED_VARS + SODIUM_LIBRARY_RELEASE + SODIUM_LIBRARY_DEBUG + SODIUM_INCLUDE_DIR + VERSION_VAR + sodium_VERSION +) + +if(SODIUM_FOUND) + set(SODIUM_LIBRARIES + optimized ${SODIUM_LIBRARY_RELEASE} debug ${SODIUM_LIBRARY_DEBUG}) +endif() + +# mark file paths as advanced +mark_as_advanced(SODIUM_INCLUDE_DIR) +mark_as_advanced(SODIUM_LIBRARY_DEBUG) +mark_as_advanced(SODIUM_LIBRARY_RELEASE) +if (WIN32) + mark_as_advanced(sodium_DLL_DEBUG) + mark_as_advanced(sodium_DLL_RELEASE) +endif() + +# create imported target +if(SODIUM_USE_STATIC_LIBS) + set(_LIB_TYPE STATIC) +else() + set(_LIB_TYPE SHARED) +endif() + +if(NOT TARGET sodium) + add_library(sodium ${_LIB_TYPE} IMPORTED) +endif() + +set_target_properties(sodium PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${SODIUM_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + ) + +if (SODIUM_USE_STATIC_LIBS) + set_target_properties(sodium PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "SODIUM_STATIC" + IMPORTED_LOCATION "${SODIUM_LIBRARY_RELEASE}" + IMPORTED_LOCATION_DEBUG "${SODIUM_LIBRARY_DEBUG}" + ) +else() + if (UNIX) + set_target_properties(sodium PROPERTIES + IMPORTED_LOCATION "${SODIUM_LIBRARY_RELEASE}" + IMPORTED_LOCATION_DEBUG "${SODIUM_LIBRARY_DEBUG}" + ) + elseif (WIN32) + set_target_properties(sodium PROPERTIES + IMPORTED_IMPLIB "${SODIUM_LIBRARY_RELEASE}" + IMPORTED_IMPLIB_DEBUG "${SODIUM_LIBRARY_DEBUG}" + ) + if (NOT (sodium_DLL_DEBUG MATCHES ".*-NOTFOUND")) + set_target_properties(sodium PROPERTIES + IMPORTED_LOCATION_DEBUG "${sodium_DLL_DEBUG}" + ) + endif() + if (NOT (sodium_DLL_RELEASE MATCHES ".*-NOTFOUND")) + set_target_properties(sodium PROPERTIES + IMPORTED_LOCATION_RELWITHDEBINFO "${sodium_DLL_RELEASE}" + IMPORTED_LOCATION_MINSIZEREL "${sodium_DLL_RELEASE}" + IMPORTED_LOCATION_RELEASE "${sodium_DLL_RELEASE}" + ) + endif() + endif() +endif() \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d59081b..89be3238 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,19 @@ -cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(TON VERSION 0.5 LANGUAGES C CXX) set(CMAKE_POSITION_INDEPENDENT_CODE ON) #set(OPENSSL_USE_STATIC_LIBS TRUE) +# Define the two required variables before including the source code for watching a git repository. +set(PRE_CONFIGURE_FILE "git.cc.in") +set(POST_CONFIGURE_FILE "${CMAKE_CURRENT_BINARY_DIR}/git.cc") +include(git_watcher.cmake) + +# Create a library out of the compiled post-configure file. +add_library(git STATIC ${POST_CONFIGURE_FILE}) +target_include_directories(git PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +add_dependencies(git check_git) + # Prevent in-source build get_filename_component(TON_REAL_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" REALPATH) get_filename_component(TON_REAL_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" REALPATH) @@ -69,8 +79,16 @@ else() set(HAVE_SSE42 FALSE) endif() +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) +set(CMAKE_CXX_EXTENSIONS FALSE) + #BEGIN internal +option(USE_EMSCRIPTEN "Use \"ON\" for config building wasm." OFF) option(TON_ONLY_TONLIB "Use \"ON\" to build only tonlib." OFF) +if (USE_EMSCRIPTEN) + set(TON_ONLY_TONLIB true) +endif() if (TON_ONLY_TONLIB) set(NOT_TON_ONLY_TONLIB false) else() @@ -87,8 +105,18 @@ option(TON_USE_TSAN "Use \"ON\" to enable ThreadSanitizer." OFF) option(TON_USE_UBSAN "Use \"ON\" to enable UndefinedBehaviorSanitizer." OFF) set(TON_ARCH "native" CACHE STRING "Architecture, will be passed to -march=") +#BEGIN M1 support +EXECUTE_PROCESS( COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE ARCHITECTURE ) + +if ((ARCHITECTURE MATCHES "arm64") AND (CMAKE_SYSTEM_NAME STREQUAL "Darwin") AND + (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13.0)) # only clang 13+ supports cpu=apple-m1 + set(TON_ARCH "apple-m1") +endif() +#END M1 support + if (TON_USE_ABSEIL) message("Add abseil-cpp") + set(ABSL_PROPAGATE_CXX_STD TRUE) add_subdirectory(third-party/abseil-cpp EXCLUDE_FROM_ALL) set(ABSL_FOUND 1) endif() @@ -108,13 +136,23 @@ set(CRC32C_BUILD_BENCHMARKS OFF CACHE BOOL "Build CRC32C's benchmarks") set(CRC32C_USE_GLOG OFF CACHE BOOL "Build CRC32C's tests with Google Logging") set(CRC32C_INSTALL OFF CACHE BOOL "Install CRC32C's header and library") message("Add crc32c") -add_subdirectory(third-party/crc32c EXCLUDE_FROM_ALL) +if (NOT MSVC) + set(OLD_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) + # fix aarch64 build @ crc32c/src/crc32c_arm64_linux_check.h + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=address") + add_subdirectory(third-party/crc32c EXCLUDE_FROM_ALL) + set(CMAKE_CXX_FLAGS ${OLD_CMAKE_CXX_FLAGS}) + unset(OLD_CMAKE_CXX_FLAGS) +else() + add_subdirectory(third-party/crc32c EXCLUDE_FROM_ALL) +endif() set(CRC32C_FOUND 1) if (TON_USE_ROCKSDB) - if (ANDROID) + if (ANDROID) set(PORTABLE ON CACHE BOOL "portable") endif() + set(WITH_GFLAGS OFF CACHE BOOL "build with GFlags") set(WITH_TESTS OFF CACHE BOOL "build with tests") set(WITH_TOOLS OFF CACHE BOOL "build with tools") set(FAIL_ON_WARNINGS OFF CACHE BOOL "fail on warnings") @@ -144,6 +182,8 @@ endif() message("Add ton") set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake" ${CMAKE_MODULE_PATH}) +include(BuildBLST) + # Configure CCache if available find_program(CCACHE_FOUND ccache) #set(CCACHE_FOUND 0) @@ -167,33 +207,27 @@ endif() include(CheckCXXCompilerFlag) -if (GCC OR CLANG OR INTEL) - if (WIN32 AND INTEL) - set(STD14_FLAG /Qstd=c++14) - else() - set(STD14_FLAG -std=c++14) - endif() - check_cxx_compiler_flag(${STD14_FLAG} HAVE_STD14) - if (NOT HAVE_STD14) - string(REPLACE "c++14" "c++1y" STD14_FLAG "${STD14_FLAG}") - check_cxx_compiler_flag(${STD14_FLAG} HAVE_STD1Y) - set(HAVE_STD14 ${HAVE_STD1Y}) - endif() -elseif (MSVC) - set(HAVE_STD14 MSVC_VERSION>=1900) -endif() - -if (NOT HAVE_STD14) - message(FATAL_ERROR "No C++14 support in the compiler. Please upgrade the compiler.") -endif() - set(CMAKE_THREAD_PREFER_PTHREAD ON) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) -find_package(ZLIB REQUIRED) +find_package(PkgConfig REQUIRED) + +if (NOT ZLIB_FOUND) + find_package(ZLIB REQUIRED) +else() + message(STATUS "Using zlib ${ZLIB_LIBRARIES}") +endif() + if (TON_ARCH AND NOT MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=${TON_ARCH}") + CHECK_CXX_COMPILER_FLAG( "-march=${TON_ARCH}" COMPILER_OPT_ARCH_SUPPORTED ) + if (TON_ARCH STREQUAL "apple-m1") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=${TON_ARCH}") + elseif(COMPILER_OPT_ARCH_SUPPORTED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=${TON_ARCH}") + elseif(NOT TON_ARCH STREQUAL "native") + message(FATAL_ERROR "Compiler doesn't support arch ${TON_ARCH}") + endif() endif() if (THREADS_HAVE_PTHREAD_ARG) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") @@ -227,21 +261,26 @@ if (MSVC) add_definitions(-D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /W4 /wd4100 /wd4127 /wd4324 /wd4456 /wd4457 /wd4458 /wd4505 /wd4702") elseif (CLANG OR GCC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${STD14_FLAG} -fno-omit-frame-pointer") + if (GCC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstrong-eval-order=some") + endif() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer") if (APPLE) #use "-Wl,-exported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/export_list" for exported symbols set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fvisibility=hidden -Wl,-dead_strip,-x,-S") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fvisibility=hidden -Wl,-dead_strip,-x,-S") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -fvisibility=hidden -Wl,-dead_strip,-x,-S") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffunction-sections -fdata-sections") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections -Wl,--exclude-libs,ALL") + if (NOT USE_EMSCRIPTEN) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections -Wl,--exclude-libs,ALL") + endif() set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") if (NOT TON_USE_ASAN AND NOT TON_USE_TSAN AND NOT MEMPROF) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--exclude-libs,ALL") + if (NOT USE_EMSCRIPTEN) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--exclude-libs,ALL") + endif() endif() endif() -elseif (INTEL) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${STD14_FLAG}") endif() if (WIN32) @@ -255,14 +294,27 @@ if (NOT ANDROID) # _FILE_OFFSET_BITS is broken in ndk r15 and r15b and doesn't w add_definitions(-D_FILE_OFFSET_BITS=64) endif() +set(INTERNAL_COMPILE "0") +#BEGIN internal + add_definitions(-D_INTERNAL_COMPILE=1) + set(INTERNAL_COMPILE "1") +#END internal + +set(TONLIB_COMPILE "0") +#BEGIN tonlib + add_definitions(-D_TONLIB_COMPILE=1) + set(TONLIB_COMPILE "1") +#END tonlib + include(AddCXXCompilerFlag) if (MSVC) add_cxx_compiler_flag("/experimental:external /external:anglebrackets /external:W0") endif() if (NOT MSVC) add_cxx_compiler_flag("-Wall") + add_cxx_compiler_flag("-Wextra") endif() -add_cxx_compiler_flag("-Wextra") + add_cxx_compiler_flag("-Wimplicit-fallthrough=2") add_cxx_compiler_flag("-Wpointer-arith") add_cxx_compiler_flag("-Wcast-qual") @@ -323,6 +375,10 @@ if (LATEX_FOUND) add_latex_document(doc/tvm.tex TARGET_NAME ton_vm_description) add_latex_document(doc/tblkch.tex TARGET_NAME ton_blockchain_description) add_latex_document(doc/fiftbase.tex TARGET_NAME fift_basic_description) + add_latex_document(doc/catchain.tex TARGET_NAME catchain_consensus_description) +endif() +if (NOT LATEX_FOUND) + message(STATUS "Could NOT find LATEX (this is NOT an error)") endif() #END internal @@ -352,6 +408,7 @@ add_subdirectory(tl-utils) add_subdirectory(adnl) add_subdirectory(crypto) add_subdirectory(lite-client) +add_subdirectory(emulator) #BEGIN tonlib add_subdirectory(tonlib) @@ -364,16 +421,21 @@ add_subdirectory(tdfec) add_subdirectory(keyring) add_subdirectory(fec) add_subdirectory(rldp) +add_subdirectory(rldp2) add_subdirectory(dht) add_subdirectory(overlay) add_subdirectory(catchain) add_subdirectory(validator-session) add_subdirectory(validator) add_subdirectory(blockchain-explorer) +add_subdirectory(storage) add_subdirectory(validator-engine) add_subdirectory(validator-engine-console) +add_subdirectory(create-hardfork) add_subdirectory(dht-server) add_subdirectory(utils) +add_subdirectory(http) +add_subdirectory(rldp-http-proxy) endif() #END internal @@ -394,6 +456,13 @@ target_link_libraries(test-vm PRIVATE ton_crypto fift-lib) add_executable(test-smartcont test/test-td-main.cpp ${SMARTCONT_TEST_SOURCE}) target_link_libraries(test-smartcont PRIVATE smc-envelope fift-lib ton_db) +add_executable(test-bigint ${BIGINT_TEST_SOURCE}) +target_link_libraries(test-bigint PRIVATE ton_crypto) + +if (WINGETOPT_FOUND) + target_link_libraries_system(test-bigint wingetopt) +endif() + add_executable(test-cells test/test-td-main.cpp ${CELLS_TEST_SOURCE}) target_link_libraries(test-cells PRIVATE ton_crypto) @@ -428,7 +497,13 @@ endif() #BEGIN internal if (NOT TON_ONLY_TONLIB) add_executable(test-db test/test-td-main.cpp ${TONDB_TEST_SOURCE}) -target_link_libraries(test-db PRIVATE ton_db memprof) +target_link_libraries(test-db PRIVATE ton_db memprof tdfec) + +add_executable(test-storage test/test-td-main.cpp ${STORAGE_TEST_SOURCE}) +target_link_libraries(test-storage PRIVATE storage ton_db memprof tl_api tl-utils fec rldp2) + +add_executable(test-rocksdb test/test-rocksdb.cpp) +target_link_libraries(test-rocksdb PRIVATE memprof tddb tdutils) add_executable(test-tddb test/test-td-main.cpp ${TDDB_TEST_SOURCE}) target_link_libraries(test-tddb PRIVATE tdutils tddb ${CMAKE_THREAD_LIBS_INIT} memprof) @@ -449,29 +524,20 @@ add_executable(test-dht test/test-dht.cpp) target_link_libraries(test-dht adnl adnltest dht tl_api) add_executable(test-rldp test/test-rldp.cpp) target_link_libraries(test-rldp adnl adnltest dht rldp tl_api) +add_executable(test-rldp2 test/test-rldp2.cpp) +target_link_libraries(test-rldp2 adnl adnltest dht rldp2 tl_api) add_executable(test-validator-session-state test/test-validator-session-state.cpp) target_link_libraries(test-validator-session-state adnl dht rldp validatorsession tl_api) -#add_executable(test-node test/test-node.cpp) -#target_link_libraries(test-node overlay tdutils tdactor adnl tl_api dht -# catchain validatorsession) - add_executable(test-catchain test/test-catchain.cpp) target_link_libraries(test-catchain overlay tdutils tdactor adnl adnltest rldp tl_api dht catchain ) -#add_executable(test-validator-session test/test-validator-session.cpp) -#target_link_libraries(test-validator-session overlay tdutils tdactor adnl tl_api dht -# catchain validatorsession) add_executable(test-ton-collator test/test-ton-collator.cpp) target_link_libraries(test-ton-collator overlay tdutils tdactor adnl tl_api dht catchain validatorsession validator-disk ton_validator validator-disk ) -#add_executable(test-validator test/test-validator.cpp) -#target_link_libraries(test-validator overlay tdutils tdactor adnl tl_api dht -# rldp catchain validatorsession ton-node validator ton_validator validator memprof ${JEMALLOC_LIBRARIES}) -#add_executable(test-ext-server test/test-ext-server.cpp) -#target_link_libraries(test-ext-server tdutils tdactor adnl tl_api dht ) -#add_executable(test-ext-client test/test-ext-client.cpp) -#target_link_libraries(test-ext-client tdutils tdactor adnl tl_api tl-lite-utils) + +add_executable(test-http test/test-http.cpp) +target_link_libraries(test-http PRIVATE tonhttp) get_directory_property(HAS_PARENT PARENT_DIRECTORY) if (HAS_PARENT) @@ -483,6 +549,7 @@ if (HAS_PARENT) ${FEC_TEST_SOURCE} ${ED25519_TEST_SOURCE} ${TONDB_TEST_SOURCE} + ${BIGNUM_TEST_SOURCE} ${CELLS_TEST_SOURCE} # ${TONVM_TEST_SOURCE} ${FIFT_TEST_SOURCE} ${TONLIB_ONLINE_TEST_SOURCE} PARENT_SCOPE) endif() @@ -496,6 +563,7 @@ set(TEST_OPTIONS "--regression ${CMAKE_CURRENT_SOURCE_DIR}/test/regression-tests separate_arguments(TEST_OPTIONS) add_test(test-ed25519-crypto crypto/test-ed25519-crypto) add_test(test-ed25519 test-ed25519) +add_test(test-bigint test-bigint) add_test(test-vm test-vm ${TEST_OPTIONS}) add_test(test-fift test-fift ${TEST_OPTIONS}) add_test(test-cells test-cells ${TEST_OPTIONS}) @@ -508,11 +576,52 @@ add_test(test-tdutils test-tdutils) add_test(test-tonlib-offline test-tonlib-offline) #END tonlib +# FunC tests +if (NOT NIX) + if (MSVC) + set(PYTHON_VER "python") + else() + set(PYTHON_VER "python3") + endif() + add_test( + NAME test-func + COMMAND ${PYTHON_VER} run_tests.py tests/ + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/crypto/func/auto-tests) + if (WIN32) + set_property(TEST test-func PROPERTY ENVIRONMENT + "FUNC_EXECUTABLE=${CMAKE_CURRENT_BINARY_DIR}/crypto/func.exe" + "FIFT_EXECUTABLE=${CMAKE_CURRENT_BINARY_DIR}/crypto/fift.exe" + "FIFTPATH=${CMAKE_CURRENT_SOURCE_DIR}/crypto/fift/lib/") + else() + set_property(TEST test-func PROPERTY ENVIRONMENT + "FUNC_EXECUTABLE=${CMAKE_CURRENT_BINARY_DIR}/crypto/func" + "FIFT_EXECUTABLE=${CMAKE_CURRENT_BINARY_DIR}/crypto/fift" + "FIFTPATH=${CMAKE_CURRENT_SOURCE_DIR}/crypto/fift/lib/") + endif() + + add_test( + NAME test-func-legacy + COMMAND ${PYTHON_VER} legacy_tester.py + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/crypto/func/auto-tests) + if (WIN32) + set_property(TEST test-func-legacy PROPERTY ENVIRONMENT + "FUNC_EXECUTABLE=${CMAKE_CURRENT_BINARY_DIR}/crypto/func.exe" + "FIFT_EXECUTABLE=${CMAKE_CURRENT_BINARY_DIR}/crypto/fift.exe" + "FIFTPATH=${CMAKE_CURRENT_SOURCE_DIR}/crypto/fift/lib/") + else() + set_property(TEST test-func-legacy PROPERTY ENVIRONMENT + "FUNC_EXECUTABLE=${CMAKE_CURRENT_BINARY_DIR}/crypto/func" + "FIFT_EXECUTABLE=${CMAKE_CURRENT_BINARY_DIR}/crypto/fift" + "FIFTPATH=${CMAKE_CURRENT_SOURCE_DIR}/crypto/fift/lib/") + endif() +endif() + #BEGIN internal if (NOT TON_ONLY_TONLIB) add_test(test-adnl test-adnl) add_test(test-dht test-dht) add_test(test-rldp test-rldp) +add_test(test-rldp2 test-rldp2) add_test(test-validator-session-state test-validator-session-state) add_test(test-catchain test-catchain) @@ -521,4 +630,3 @@ add_test(test-tddb test-tddb ${TEST_OPTIONS}) add_test(test-db test-db ${TEST_OPTIONS}) endif() #END internal - diff --git a/Changelog.md b/Changelog.md new file mode 100644 index 00000000..a0241055 --- /dev/null +++ b/Changelog.md @@ -0,0 +1,178 @@ +## 2024.04 Update + +1. Emulator: Single call optimized runGetMethod added +2. Tonlib: a series of proof improvements, also breaking Change in `liteServer.getAllShardsInfo` method (see below) +3. DB: usage statistics now collected, outdated persistent states are not serialized +4. LS: fast `getOutMsgQueueSizes` added, preliminary support of non-final block requests +5. Network: lz4 compression of block candidates (disabled by default). +6. Overlays: add custom overlays +7. Transaction Executor: fixed issue with due_payment collection + +* `liteServer.getAllShardsInfo` method was updated for better efficiency. Previously, field proof contained BoC with two roots: one for BlockState from block's root and another for ShardHashes from BlockState. Now, it returns a single-root proof BoC, specifically the merkle proof of ShardHashes directly from the block's root, streamlining data access and integrity. Checking of the proof requires to check that ShardHashes in the `data` correspond to ShardHashes from the block. + +Besides the work of the core team, this update is based on the efforts of @akifoq (due_payment issue). + +## 2024.03 Update + +1. Preparatory (not enabled yet) code for pre-compiled smart-contract. +2. Minor fixes for fee-related opcodes. + +## 2024.02 Update + +1. Improvement of validator synchronisation: + * Better handling of block broadcasts -> faster sync + * Additional separate overlay among validators as second option for synchronisation +2. Improvements in LS: + * c7 and library context is fully filled up for server-side rungetmethod + * Cache for runmethods and successfull external messages + * Logging of LS requests statistic +3. Precise control of open files: + * almost instantaneous validator start + * `--max-archive-fd` option + * autoremoval of not used temp archive files + * `--archive-preload-period` option +4. Preparatory (not enabled yet) code for addition on new TVM instructions for cheaper fee calculation onchain. + +## 2024.01 Update + +1. Fixes in how gas in transactions on special accounts is accounted in block limit. Previously, gas was counted as usual, so to conduct elections that costs >30m gas block limit in masterchain was set to 37m gas. To lower the limit for safety reasons it is proposed to caunt gas on special accounts separately. Besides `gas_max` is set to `special_gas_limit` for all types of transactions on special accounts. New behavior is activated through setting `version >= 5` in `ConfigParam 8;`. + * Besides update of config temporally increases gas limit on `EQD_v9j1rlsuHHw2FIhcsCFFSD367ldfDdCKcsNmNpIRzUlu` to `special_gas_limit`, see [details](https://t.me/tonstatus/88). +2. Improvements in LS behavior + * Improved detection of the state with all shards applied to decrease rate of `Block is not applied` error + * Better error logs: `block not in db` and `block is not applied` separation + * Fix error in proof generation for blocks after merge + * Fix most of `block is not applied` issues related to sending too recent block in Proofs + * LS now check external messages till `accept_message` (`set_gas`). +3. Improvements in DHT work and storage, CellDb, config.json ammendment, peer misbehavior detection, validator session stats collection, emulator. +4. Change in CTOS and XLOAD behavior activated through setting `version >= 5` in `ConfigParam 8;`: + * Loading "nested libraries" (i.e. a library cell that points to another library cell) throws an exception. + * Loading a library consumes gas for cell load only once (for the library cell), not twice (both for the library cell and the cell in the library). + * `XLOAD` now works differently. When it takes a library cell, it returns the cell that it points to. This allows loading "nested libraries", if needed. + +Besides the work of the Core team, this update is based on the efforts of @XaBbl4 (peer misbehavior detection) and @akifoq (CTOS behavior and gas limit scheme for special accounts). + +## 2023.12 Update + +1. Optimized message queue handling, now queue cleaning speed doesn't depend on total queue size + * Cleaning delivered messages using lt augmentation instead of random search / consequtive walk + * Keeping root cell of queue message in memory until outdated (caching) +2. Changes to block collation/validation limits +3. Stop accepting new external message if message queue is overloaded +4. Introducing conditions for shard split/merge based on queue size + +Read [more](https://blog.ton.org/technical-report-december-5-inscriptions-launch-on-ton) on that update. + +## 2023.11 Update + +1. New TVM Functionality. (Disabled by default) +2. A series of emulator improvements: libraries support, higher max stack size, etc +3. A series of tonlib and tonlib-cli improvements: wallet-v4 support, getconfig, showtransactions, etc +4. Changes to public libraries: now contract can not publish more than 256 libraries (config parameter) and contracts can not be deployed with public libraries in initstate (instead contracts need explicitly publish all libraries) +5. Changes to storage due payment: now due payment is collected in Storage Phase, however for bouncable messages fee amount can not exceed balance of account prior to message. + + +Besides the work of the core team, this update is based on the efforts of @aleksej-paschenko (emulator improvements), @akifoq (security improvements), Trail of Bits auditor as well as all participants of [TEP-88 discussion](https://github.com/ton-blockchain/TEPs/pull/88). + +## 2023.10 Update +1. A series of additional security checks in node: special cells in action list, init state in external messages, peers data prior to saving to disk. +2. Human-readable timestamps in explorer + +Besides the work of the core team, this update is based on the efforts of @akifoq and @mr-tron. + +## 2023.06 Update +1. (disabled by default) New deflation mechanisms: partial fee burning and blackhole address +2. Storage-contract improvement + +Besides the work of the core team, this update is based on the efforts of @DearJohnDoe from Tonbyte (Storage-contract improvement). + +## 2023.05 Update +1. Archive manager optimization +2. A series of catchain (basic consensus protocol) security improvements +3. Update for Fift libraries and FunC: better error-handling, fixes for `catch` stack recovery +4. A series of out message queue handling optimization (already deployed during emergency upgrades between releases) +5. Improvement of binaries portability + +Besides the work of the core team, this update is based on the efforts of @aleksej-paschenko (portability improvement), [Disintar team](https://github.com/disintar/) (archive manager optimization) and [sec3-service](https://github.com/sec3-service) security auditors (funC improvements). + +## 2023.04 Update +1. CPU load optimization: previous DHT reconnect policy was too aggressive +2. Network throughput improvements: granular control on external message broadcast, optimize celldb GC, adjust state serialization and block downloading timings, rldp2 for states and archives +3. Update for Fift (namespaces) and Fift libraries (list of improvements: https://github.com/ton-blockchain/ton/issues/631) +4. Better handling of incorrect inputs in funC: fix UB and prevent crashes on some inputs, improve optimizing int consts and unused variables in FunC, fix analyzing repeat loop. FunC version is increase to 0.4.3. +5. `listBlockTransactionsExt` in liteserver added +6. Tvm emulator improvements + +Besides the work of the core team, this update is based on the efforts of @krigga (tvm emulator improvement), @ex3ndr (`PUSHSLICE` fift-asm improvement) and [sec3-service](https://github.com/sec3-service) security auditors (funC improvements). + +## 2023.03 Update +1. Improvement of ADNL connection stability +2. Transaction emulator support and getAccountStateByTransaction method +3. Fixes of typos, undefined behavior and timer warnings +4. Handling incorrect integer literal values in funC; funC version bumped to 0.4.2 +5. FunC Mathlib + +## 2023.01 Update +1. Added ConfigParam 44: `SuspendedAddressList`. Upon being set this config suspends initialisation of **uninit** addresses from the list for given time. +2. FunC: `v0.4.1` added pragmas for precise control of computation order +3. FunC: fixed compiler crashes for some exotic inputs +4. FunC: added legacy tester, a collection of smart-contracts which is used to check whether compilator update change compilation result +5. Improved archive manager: proper handling of recently garbage-collected blocks + +## 2022.12 Update +Node update: +1. Improvements of ton-proxy: fixed few bugs, improved stability +2. Improved collator/validator checks, added optimization of storage stat calculation, generation and validation of new blocks is made safer +3. Some previously hard-coded parameters such as split/merge timings, max sizes and depths of internal and external messages, and others now can be updated by validators through setting ConfigParams. Max contract size added to configs. +4. Tonlib: updated raw.getTransactions (now it contains InitState), fixed long bytestrings truncation +5. abseil-cpp is updated to newer versions +6. Added configs for Token Bridge +7. LiteServers: a few bug fixes, added liteServer.getAccountStatePrunned method, improved work with not yet applied blocks. +8. Improved DHT: works for some NAT configurations, optimized excessive requests, added option for DHT network segregation. +9. FunC v0.4.0: added try/catch statements, added throw_arg functions, allowed in-place modification of global variables, forbidden ambiguous modification of local variables after it's usage in the same expression. +10. TON Storage: added storage-daemon (create, download bag of Files, storage-provider staff), added storage-daemon-cli + +Besides the work of the core team, this update is based on the efforts of @vtamara (help with abseil-cpp upgrade), @krigga(in-place modification of global variables) and third-party security auditors. + +## 2022.10 Update +* Added extended block creation and general perfomance stats gathering +* Forbidden report data on blocks not committed to the master chain for LS +* Improved debug in TVM +* FunC 0.3.0: multi-line asms, bitwise operations for constants, duplication of identical definition for constants and asms now allowed +* New tonlib methods: sendMessageReturnHash, getTransactionsV2, getMasterchainBlockSignatures, getShardBlockProof, getLibraries. +* Fixed bugs related to invalid TVM output (c4, c5, libaries) and non-validated network data; avoided too deep recursion in libraries loading +* Fixed multiple undefined behavior issues +* Added build of FunC and Fift to WASM + +Besides the work of the core team, this update is based on the efforts of @tvorogme (debug improvements), @AlexeyFSL (WASM builds) and third-party security auditors. + +## 2022.08 Update +* Blockchain state serialization now works via separate db-handler which simplfies memory clearing after serialization +* CellDB now works asynchronously which substantially increase database access throughput +* Abseil-cpp and crc32 updated: solve issues with compilation on recent OS distributives +* Fixed a series of UBs and issues for exotic endianness hosts +* Added detailed network stats for overlays (can be accessed via `validator-console`) +* Improved auto-builds for wide range of systems. +* Added extended error information for unaccepted external messages: `exit_code` and TVM trace (where applicable). +* [Improved catchain DoS resistance](https://github.com/ton-blockchain/ton/blob/master/doc/catchain-dos.md) +* A series of FunC improvements, summarized [here](https://github.com/ton-blockchain/ton/pull/378) +#### Update delay +Update coincided with persistent state serialization event which lead to block production speed deterioration (issue substantially mitigated in update itself). This phenomena was aggravated by the fact that after update some validators lost ability to participate in block creation. The last was caused by threshold based hardcoded protocol version bump, where threshold was set in such manner (based on block height with value higher than 9m), that it eluded detection in private net tests. The update was temporarily paused and resumed after persistent state serialization ended and issues with block creation were resolved. + +Besides the work of the core team, this update is based on the efforts of @awesome-doge (help with abseil-cpp upgrade), @rec00rsiff (noted issues for exotic endianess and implemented network stats) and third-party security auditors. + +## 2022.05 Update +* Initial synchronization improved: adjusted timeouts for state download and the way of choosing which state to download. Nodes with low network speed and/or bad connectivity will synchronize faster and consistently. +* Improved peer-to-peer network stability and DDoS resistance: now peers will only relay valid messages to the network. Large messages, which require splitting for relaying, will be retranslated as well, but only after the node gets all parts, and reassembles and checks them. Validators may sign certificates for network peers, which allow relaying large messages by parts without checks. It is used now by validators to faster relay new blocks. Sign and import certificate commands are exposed via `validator-engine-console`. +* Fixed some rare edge cases in TVM arithmetic operations related to big numbers (`2**63+`) +* Improved fixes used to combat wrong activate-destruct-activate contract behavior last November. +* Improved tonlib: support libraries (with client-side caching), getmethods completely fill c7 register, getmethods support slice arguments, improved messages listing for transactions, added extended block header params, added getConfig method. +* RocksDB updated to a newer version. +* Improved persistent state serialization: memory usage during serialization was optimized; the start of serialization on different nodes was sparsed. +* FunC update: support for string literals and constants (including precompiled constant expressions), semver, `include` expressions. +* Fixed rarely manifested bugs in `Asm.fif`. +* LiteClient supports key as cli parameter. +* Improved Liteserver DoS resistance for running getmethods. + +Besides the work of the core team, this update is based on the efforts of @tvorogme (added support for slice arguments and noted bugs in Asm.fif), @akifoq (fixed bug in Asm.fif), @cryshado (noted strange behavior of LS, which, upon inspection, turned out to be a vector of DoS attack). + + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..76c06b35 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +FROM ubuntu:22.04 as builder +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git ninja-build libsecp256k1-dev libsodium-dev libmicrohttpd-dev liblz4-dev pkg-config autoconf automake libtool && \ + rm -rf /var/lib/apt/lists/* +ENV CC clang +ENV CXX clang++ +ENV CCACHE_DISABLE 1 + +WORKDIR / +RUN mkdir ton +WORKDIR /ton + +COPY ./ ./ + +RUN mkdir build && \ + cd build && \ + cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DPORTABLE=1 -DTON_ARCH= .. && \ + ninja storage-daemon storage-daemon-cli tonlibjson fift func validator-engine validator-engine-console generate-random-id dht-server lite-client + +FROM ubuntu:22.04 +RUN apt-get update && \ + apt-get install -y wget libatomic1 openssl libsecp256k1-dev libsodium-dev libmicrohttpd-dev liblz4-dev && \ + rm -rf /var/lib/apt/lists/* + +RUN mkdir -p /var/ton-work/db && \ + mkdir -p /var/ton-work/db/static + +COPY --from=builder /ton/build/storage/storage-daemon/storage-daemon /usr/local/bin/ +COPY --from=builder /ton/build/storage/storage-daemon/storage-daemon-cli /usr/local/bin/ +COPY --from=builder /ton/build/lite-client/lite-client /usr/local/bin/ +COPY --from=builder /ton/build/validator-engine/validator-engine /usr/local/bin/ +COPY --from=builder /ton/build/validator-engine-console/validator-engine-console /usr/local/bin/ +COPY --from=builder /ton/build/utils/generate-random-id /usr/local/bin/ + +WORKDIR /var/ton-work/db +COPY ./docker/init.sh ./docker/control.template ./ +RUN chmod +x init.sh + +ENTRYPOINT ["/var/ton-work/db/init.sh"] diff --git a/GPLv2 b/GPLv2 index c2a8d683..69910f2e 100644 --- a/GPLv2 +++ b/GPLv2 @@ -1,4 +1,4 @@ -/* +/* This file is part of TON Blockchain source code. TON Blockchain is free software; you can redistribute it and/or @@ -14,14 +14,14 @@ You should have received a copy of the GNU General Public License along with TON Blockchain. If not, see . - In addition, as a special exception, the copyright holders give permission - to link the code of portions of this program with the OpenSSL library. - You must obey the GNU General Public License in all respects for all - of the code used other than OpenSSL. If you modify file(s) with this - exception, you may extend this exception to your version of the file(s), - but you are not obligated to do so. If you do not wish to do so, delete this - exception statement from your version. If you delete this exception statement + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ diff --git a/LGPLv2 b/LGPLv2 index d93ab1b9..ab2bc125 100644 --- a/LGPLv2 +++ b/LGPLv2 @@ -14,5 +14,5 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ diff --git a/README.md b/README.md new file mode 100644 index 00000000..96516d44 --- /dev/null +++ b/README.md @@ -0,0 +1,159 @@ +
+ + + + TON logo + + +

Reference implementation of TON Node and tools

+
+
+ +## + +

+ + Ton Research + + + Telegram Community Group + + + Telegram Foundation Group + + + Telegram Community Chat + +

+ +

+ + Twitter Group + + + TON Overflow Group + + + Stack Overflow Group + +

+ + + +Main TON monorepo, which includes the code of the node/validator, lite-client, tonlib, FunC compiler, etc. + +## The Open Network + +__The Open Network (TON)__ is a fast, secure, scalable blockchain focused on handling _millions of transactions per second_ (TPS) with the goal of reaching hundreds of millions of blockchain users. +- To learn more about different aspects of TON blockchain and its underlying ecosystem check [documentation](https://ton.org/docs) +- To run node, validator or lite-server check [Participate section](https://ton.org/docs/participate/nodes/run-node) +- To develop decentralised apps check [Tutorials](https://ton.org/docs/develop/smart-contracts/), [FunC docs](https://ton.org/docs/develop/func/overview) and [DApp tutorials](https://ton.org/docs/develop/dapps/) +- To work on TON check [wallets](https://ton.app/wallets), [explorers](https://ton.app/explorers), [DEXes](https://ton.app/dex) and [utilities](https://ton.app/utilities) +- To interact with TON check [APIs](https://ton.org/docs/develop/dapps/apis/) + +## Updates flow + +* **master branch** - mainnet is running on this stable branch. + + Only emergency updates, urgent updates, or updates that do not affect the main codebase (GitHub workflows / docker images / documentation) are committed directly to this branch. + +* **testnet branch** - testnet is running on this branch. The branch contains a set of new updates. After testing, the testnet branch is merged into the master branch and then a new set of updates is added to testnet branch. + +* **backlog** - other branches that are candidates to getting into the testnet branch in the next iteration. + +Usually, the response to your pull request will indicate which section it falls into. + + +## "Soft" Pull Request rules + +* Thou shall not merge your own PRs, at least one person should review the PR and merge it (4-eyes rule) +* Thou shall make sure that workflows are cleanly completed for your PR before considering merge + +## Build TON blockchain + +### Ubuntu 20.4, 22.04 (x86-64, aarch64) +Install additional system libraries +```bash + sudo apt-get update + sudo apt-get install -y build-essential git cmake ninja-build zlib1g-dev libsecp256k1-dev libmicrohttpd-dev libsodium-dev + + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 16 all +``` +Compile TON binaries +```bash + cp assembly/native/build-ubuntu-shared.sh . + chmod +x build-ubuntu-shared.sh + ./build-ubuntu-shared.sh +``` + +### MacOS 11, 12 (x86-64, aarch64) +```bash + cp assembly/native/build-macos-shared.sh . + chmod +x build-macos-shared.sh + ./build-macos-shared.sh +``` + +### Windows 10, 11, Server (x86-64) +You need to install `MS Visual Studio 2022` first. +Go to https://www.visualstudio.com/downloads/ and download `MS Visual Studio 2022 Community`. + +Launch installer and select `Desktop development with C++`. +After installation, also make sure that `cmake` is globally available by adding +`C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin` to the system `PATH` (adjust the path per your needs). + +Open an elevated (Run as Administrator) `x86-64 Native Tools Command Prompt for VS 2022`, go to the root folder and execute: +```bash + copy assembly\native\build-windows.bat . + build-windows.bat +``` + +### Building TON to WebAssembly +Install additional system libraries on Ubuntu +```bash + sudo apt-get update + sudo apt-get install -y build-essential git cmake ninja-build zlib1g-dev libsecp256k1-dev libmicrohttpd-dev libsodium-dev + + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 16 all +``` +Compile TON binaries with emscripten +```bash + cd assembly/wasm + chmod +x fift-func-wasm-build-ubuntu.sh + ./fift-func-wasm-build-ubuntu.sh +``` + +### Building TON tonlib library for Android (arm64-v8a, armeabi-v7a, x86, x86-64) +Install additional system libraries on Ubuntu +```bash + sudo apt-get update + sudo apt-get install -y build-essential git cmake ninja-build automake libtool texinfo autoconf libgflags-dev \ + zlib1g-dev libssl-dev libreadline-dev libmicrohttpd-dev pkg-config libgsl-dev python3 python3-dev \ + libtool autoconf libsodium-dev libsecp256k1-dev +``` +Compile TON tonlib library +```bash + cp assembly/android/build-android-tonlib.sh . + chmod +x build-android-tonlib.sh + ./build-android-tonlib.sh +``` + +### Build TON portable binaries with Nix package manager +You need to install Nix first. +```bash + sh <(curl -L https://nixos.org/nix/install) --daemon +``` +Then compile TON with Nix by executing below command from the root folder: +```bash + cp -r assembly/nix/* . + export NIX_PATH=nixpkgs=https://github.com/nixOS/nixpkgs/archive/23.05.tar.gz + nix-build linux-x86-64-static.nix +``` +More examples for other platforms can be found under `assembly/nix`. + +## Running tests + +Tests are executed by running `ctest` in the build directory. See `doc/Tests.md` for more information. diff --git a/adnl/CMakeLists.txt b/adnl/CMakeLists.txt index 04c73044..217a9624 100644 --- a/adnl/CMakeLists.txt +++ b/adnl/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) #BEGIN internal if (NOT TON_ONLY_TONLIB) @@ -42,6 +42,7 @@ set(ADNL_SOURCE adnl-message.cpp adnl-network-manager.cpp adnl-node.cpp + adnl-node-id.cpp adnl-packet.cpp adnl-peer-table.cpp adnl-peer.cpp @@ -88,20 +89,22 @@ target_link_libraries(adnl PUBLIC tdactor ton_crypto tl_api tdnet tddb keys keyr add_executable(adnl-proxy ${ADNL_PROXY_SOURCE}) target_include_directories(adnl-proxy PUBLIC $) target_link_libraries(adnl-proxy PUBLIC tdactor ton_crypto tl_api tdnet common - tl-utils) + tl-utils git) add_executable(adnl-pong adnl-pong.cpp) target_include_directories(adnl-pong PUBLIC $) target_link_libraries(adnl-pong PUBLIC tdactor ton_crypto tl_api tdnet common - tl-utils adnl dht) + tl-utils adnl dht git) add_library(adnltest STATIC ${ADNL_TEST_SOURCE}) target_include_directories(adnltest PUBLIC $) target_link_libraries(adnltest PUBLIC adnl ) + +install(TARGETS adnl-proxy RUNTIME DESTINATION bin) endif() #END internal add_library(adnllite STATIC ${ADNL_LITE_SOURCE}) target_include_directories(adnllite PUBLIC $) -target_link_libraries(adnllite PUBLIC tdactor ton_crypto tl_lite_api tdnet keys ) +target_link_libraries(adnllite PUBLIC tdactor ton_crypto tl_lite_api tdnet keys) diff --git a/adnl/adnl-address-list.cpp b/adnl/adnl-address-list.cpp index ab395144..912943a8 100644 --- a/adnl/adnl-address-list.cpp +++ b/adnl/adnl-address-list.cpp @@ -14,13 +14,14 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-address-list.hpp" #include "adnl-peer-table.h" #include "auto/tl/ton_api.hpp" #include "td/utils/overloaded.h" #include "td/net/UdpServer.h" +#include "keys/encryptor.h" namespace ton { @@ -38,6 +39,9 @@ class AdnlNetworkConnectionUdp : public AdnlNetworkConnection { void start_up() override { callback_->on_change_state(true); } + void get_ip_str(td::Promise promise) override { + promise.set_value(PSTRING() << addr_.get_ip_str().str() << ":" << addr_.get_port()); + } AdnlNetworkConnectionUdp(td::actor::ActorId network_manager, td::uint32 ip, td::uint16 port, std::unique_ptr callback); @@ -50,6 +54,62 @@ class AdnlNetworkConnectionUdp : public AdnlNetworkConnection { std::unique_ptr callback_; }; +class AdnlNetworkConnectionTunnel : public AdnlNetworkConnection { + public: + void send(AdnlNodeIdShort src, AdnlNodeIdShort dst, td::uint32 priority, td::BufferSlice message) override { + if (!encryptor_) { + VLOG(ADNL_INFO) << "tunnel: message [" << src << "->" << dst << "to bad tunnel. dropping"; + return; + } + auto dataR = encryptor_->encrypt(message.as_slice()); + if (dataR.is_error()) { + VLOG(ADNL_INFO) << "tunnel: message [" << src << "->" << dst << ": failed to encrypt: " << dataR.move_as_error(); + return; + } + auto data = dataR.move_as_ok(); + td::BufferSlice enc_message{data.size() + 32}; + auto S = enc_message.as_slice(); + S.copy_from(pub_key_hash_.as_slice()); + S.remove_prefix(32); + S.copy_from(data.as_slice()); + td::actor::send_closure(adnl_, &Adnl::send_message_ex, src, adnl_id_, std::move(enc_message), + Adnl::SendFlags::direct_only); + } + bool is_alive() const override { + return ready_.load(std::memory_order_consume); + } + bool is_active() const override { + return ready_.load(std::memory_order_consume); + } + void start_up() override { + auto R = pub_key_.create_encryptor(); + if (R.is_error()) { + VLOG(ADNL_INFO) << "tunnel: bad public key: " << R.move_as_error(); + return; + } + encryptor_ = R.move_as_ok(); + pub_key_hash_ = pub_key_.compute_short_id(); + //ready_.store(true, std::memory_order_release); + } + void get_ip_str(td::Promise promise) override { + promise.set_value("tunnel"); + } + + AdnlNetworkConnectionTunnel(td::actor::ActorId network_manager, td::actor::ActorId adnl, + adnl::AdnlNodeIdShort adnl_id, PublicKey pubkey, + std::unique_ptr callback); + + private: + td::actor::ActorId network_manager_; + td::actor::ActorId adnl_; + AdnlNodeIdShort adnl_id_; + PublicKey pub_key_; + PublicKeyHash pub_key_hash_; + std::unique_ptr encryptor_; + std::atomic ready_{false}; + std::unique_ptr callback_; +}; + void AdnlNetworkConnectionUdp::send(AdnlNodeIdShort src, AdnlNodeIdShort dst, td::uint32 priority, td::BufferSlice message) { LOG_CHECK(message.size() <= AdnlNetworkManager::get_mtu()) << "dst=" << addr_ << " size=" << message.size(); @@ -71,12 +131,23 @@ AdnlNetworkConnectionUdp::AdnlNetworkConnectionUdp(td::actor::ActorId network_manager, + td::actor::ActorId adnl, adnl::AdnlNodeIdShort adnl_id, + PublicKey pubkey, + std::unique_ptr callback) + : network_manager_(std::move(network_manager)) + , adnl_(std::move(adnl)) + , adnl_id_(adnl_id) + , pub_key_(std::move(pubkey)) + , callback_(std::move(callback)) { +} + AdnlAddressImpl::Hash AdnlAddressImpl::get_hash() const { return get_tl_object_sha_bits256(tl()); } td::actor::ActorOwn AdnlAddressUdp::create_connection( - td::actor::ActorId network_manager, + td::actor::ActorId network_manager, td::actor::ActorId adnl, std::unique_ptr callback) const { return td::actor::create_actor("udpconn", network_manager, ip_, port_, std::move(callback)); } @@ -87,7 +158,7 @@ AdnlAddressUdp::AdnlAddressUdp(const ton_api::adnl_address_udp &obj) { } td::actor::ActorOwn AdnlAddressUdp6::create_connection( - td::actor::ActorId network_manager, + td::actor::ActorId network_manager, td::actor::ActorId adnl, std::unique_ptr callback) const { return td::actor::create_actor("udpconn", network_manager, ip_, port_, std::move(callback)); } @@ -97,16 +168,25 @@ AdnlAddressUdp6::AdnlAddressUdp6(const ton_api::adnl_address_udp6 &obj) { port_ = static_cast(obj.port_); } +td::actor::ActorOwn AdnlAddressTunnel::create_connection( + td::actor::ActorId network_manager, td::actor::ActorId adnl, + std::unique_ptr callback) const { + return td::actor::create_actor("tunnelconn", network_manager, adnl, adnl_id_, pub_key_, + std::move(callback)); +} +AdnlAddressTunnel::AdnlAddressTunnel(const ton_api::adnl_address_tunnel &obj) { + adnl_id_ = AdnlNodeIdShort{obj.to_}; + pub_key_ = ton::PublicKey{obj.pubkey_}; +} + td::Ref AdnlAddressImpl::create(const tl_object_ptr &addr) { td::Ref res = td::Ref{}; - ton_api::downcast_call(*const_cast(addr.get()), - td::overloaded( - [&](const ton_api::adnl_address_udp &obj) { - res = td::Ref{true, obj}; - }, - [&](const ton_api::adnl_address_udp6 &obj) { - res = td::Ref{true, obj}; - })); + ton_api::downcast_call( + *const_cast(addr.get()), + td::overloaded([&](const ton_api::adnl_address_udp &obj) { res = td::make_ref(obj); }, + [&](const ton_api::adnl_address_udp6 &obj) { res = td::make_ref(obj); }, + [&](const ton_api::adnl_address_tunnel &obj) { res = td::make_ref(obj); }, + [&](const ton_api::adnl_address_reverse &obj) { res = td::make_ref(); })); return res; } @@ -123,7 +203,12 @@ AdnlAddressList::AdnlAddressList(const tl_object_ptr version_ = static_cast(addrs->version_); std::vector> vec; for (auto &addr : addrs->addrs_) { - vec.push_back(AdnlAddressImpl::create(addr)); + auto obj = AdnlAddressImpl::create(addr); + if (obj->is_reverse()) { + has_reverse_ = true; + } else { + vec.push_back(std::move(obj)); + } } addrs_ = std::move(vec); reinit_date_ = addrs->reinit_date_; @@ -136,6 +221,9 @@ tl_object_ptr AdnlAddressList::tl() const { for (auto &v : addrs_) { addrs.emplace_back(v->tl()); } + if (has_reverse_) { + addrs.push_back(create_tl_object()); + } return create_tl_object(std::move(addrs), version_, reinit_date_, priority_, expire_at_); } @@ -155,6 +243,16 @@ td::Result AdnlAddressList::create(const tl_object_ptr(addr.get_ipv4(), static_cast(addr.get_port())); + addrs_.push_back(std::move(r)); + return td::Status::OK(); + } else { + return td::Status::Error(ErrorCode::protoviolation, "only works with ipv4"); + } +} + } // namespace adnl } // namespace ton diff --git a/adnl/adnl-address-list.h b/adnl/adnl-address-list.h index d13fce6f..ebc7473a 100644 --- a/adnl/adnl-address-list.h +++ b/adnl/adnl-address-list.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -25,6 +25,8 @@ namespace ton { namespace adnl { +class Adnl; + class AdnlAddressImpl : public td::CntObject { public: using Hash = td::Bits256; @@ -35,8 +37,11 @@ class AdnlAddressImpl : public td::CntObject { virtual td::uint32 serialized_size() const = 0; virtual tl_object_ptr tl() const = 0; virtual td::actor::ActorOwn create_connection( - td::actor::ActorId network_manager, + td::actor::ActorId network_manager, td::actor::ActorId adnl, std::unique_ptr callback) const = 0; + virtual bool is_reverse() const { + return false; + } static td::Ref create(const tl_object_ptr &addr); }; @@ -52,6 +57,7 @@ class AdnlAddressList { td::int32 priority_; td::int32 expire_at_; std::vector addrs_; + bool has_reverse_{false}; public: static constexpr td::uint32 max_serialized_size() { @@ -88,9 +94,10 @@ class AdnlAddressList { void add_addr(AdnlAddress addr) { addrs_.push_back(addr); } + void update(td::IPAddress addr); bool public_only() const; td::uint32 size() const { - return static_cast(addrs_.size()); + return td::narrow_cast(addrs_.size()); } td::uint32 serialized_size() const; tl_object_ptr tl() const; @@ -98,6 +105,14 @@ class AdnlAddressList { } static td::Result create(const tl_object_ptr &addr_list); + td::Status add_udp_address(td::IPAddress addr); + + void set_reverse(bool x = true) { + has_reverse_ = x; + } + bool has_reverse() const { + return has_reverse_; + } }; } // namespace adnl diff --git a/adnl/adnl-address-list.hpp b/adnl/adnl-address-list.hpp index f66f2351..0c869378 100644 --- a/adnl/adnl-address-list.hpp +++ b/adnl/adnl-address-list.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -51,7 +51,7 @@ class AdnlAddressUdp : public AdnlAddressImpl { return create_tl_object(ip_, port_); } td::actor::ActorOwn create_connection( - td::actor::ActorId network_manager, + td::actor::ActorId network_manager, td::actor::ActorId adnl, std::unique_ptr callback) const override; }; @@ -81,10 +81,66 @@ class AdnlAddressUdp6 : public AdnlAddressImpl { return create_tl_object(ip_, port_); } td::actor::ActorOwn create_connection( - td::actor::ActorId network_manager, + td::actor::ActorId network_manager, td::actor::ActorId adnl, std::unique_ptr callback) const override; }; +class AdnlAddressTunnel : public AdnlAddressImpl { + private: + AdnlNodeIdShort adnl_id_; + PublicKey pub_key_; + + public: + explicit AdnlAddressTunnel(const ton_api::adnl_address_tunnel &obj); + + AdnlAddressTunnel(AdnlNodeIdShort adnl_id, PublicKey pub_key) + : adnl_id_(std::move(adnl_id)), pub_key_(std::move(pub_key)) { + } + + AdnlAddressTunnel *make_copy() const override { + return new AdnlAddressTunnel{*this}; + } + + bool is_public() const override { + return false; + } + td::uint32 serialized_size() const override { + return 4 + 32 + pub_key_.serialized_size(); + } + + tl_object_ptr tl() const override { + return create_tl_object(adnl_id_.bits256_value(), pub_key_.tl()); + } + td::actor::ActorOwn create_connection( + td::actor::ActorId network_manager, td::actor::ActorId adnl, + std::unique_ptr callback) const override; +}; + +class AdnlAddressReverse : public AdnlAddressImpl { + public: + AdnlAddressReverse *make_copy() const override { + return new AdnlAddressReverse(); + } + bool is_public() const override { + return true; + } + td::uint32 serialized_size() const override { + return 4; + } + tl_object_ptr tl() const override { + return create_tl_object(); + } + td::actor::ActorOwn create_connection( + td::actor::ActorId network_manager, td::actor::ActorId adnl, + std::unique_ptr callback) const override { + LOG(ERROR) << "Cannot create connection for AdnlAddressReverse"; + return {}; + } + bool is_reverse() const override { + return true; + } +}; + } // namespace adnl } // namespace ton diff --git a/adnl/adnl-channel.cpp b/adnl/adnl-channel.cpp index 9314d54e..5c8229ca 100644 --- a/adnl/adnl-channel.cpp +++ b/adnl/adnl-channel.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-channel.hpp" #include "adnl-peer.h" @@ -111,13 +111,15 @@ void AdnlChannelImpl::send_message(td::uint32 priority, td::actor::ActorId R) { + [peer = peer_pair_, channel_id = channel_in_id_, addr, id = print_id()](td::Result R) { if (R.is_error()) { VLOG(ADNL_WARNING) << id << ": dropping IN message: can not decrypt: " << R.move_as_error(); } else { - td::actor::send_closure(peer, &AdnlPeerPair::receive_packet_from_channel, channel_id, R.move_as_ok()); + auto packet = R.move_as_ok(); + packet.set_remote_addr(addr); + td::actor::send_closure(peer, &AdnlPeerPair::receive_packet_from_channel, channel_id, std::move(packet)); } }); diff --git a/adnl/adnl-channel.h b/adnl/adnl-channel.h index e56f040c..94b5877d 100644 --- a/adnl/adnl-channel.h +++ b/adnl/adnl-channel.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -35,7 +35,7 @@ class AdnlChannel : public td::actor::Actor { AdnlNodeIdShort local_id, AdnlNodeIdShort peer_id, AdnlChannelIdShort &out_id, AdnlChannelIdShort &in_id, td::actor::ActorId peer_pair); - virtual void receive(td::BufferSlice data) = 0; + virtual void receive(td::IPAddress addr, td::BufferSlice data) = 0; virtual void send_message(td::uint32 priority, td::actor::ActorId conn, td::BufferSlice data) = 0; virtual ~AdnlChannel() = default; diff --git a/adnl/adnl-channel.hpp b/adnl/adnl-channel.hpp index 1499f5c4..0c30fbd5 100644 --- a/adnl/adnl-channel.hpp +++ b/adnl/adnl-channel.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -33,7 +33,7 @@ class AdnlChannelImpl : public AdnlChannel { AdnlChannelIdShort in_id, AdnlChannelIdShort out_id, std::unique_ptr encryptor, std::unique_ptr decryptor); void decrypt(td::BufferSlice data, td::Promise promise); - void receive(td::BufferSlice data) override; + void receive(td::IPAddress addr, td::BufferSlice data) override; void send_message(td::uint32 priority, td::actor::ActorId conn, td::BufferSlice data) override; struct AdnlChannelPrintId { diff --git a/adnl/adnl-db.cpp b/adnl/adnl-db.cpp index 7043199e..c86f3f7e 100644 --- a/adnl/adnl-db.cpp +++ b/adnl/adnl-db.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-db.hpp" #include "td/db/RocksDb.h" diff --git a/adnl/adnl-db.h b/adnl/adnl-db.h index d7bf9fd7..f9d61eb2 100644 --- a/adnl/adnl-db.h +++ b/adnl/adnl-db.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-db.hpp b/adnl/adnl-db.hpp index 077279fd..0f493745 100644 --- a/adnl/adnl-db.hpp +++ b/adnl/adnl-db.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-ext-client.cpp b/adnl/adnl-ext-client.cpp index 1d10283b..9602b521 100644 --- a/adnl/adnl-ext-client.cpp +++ b/adnl/adnl-ext-client.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-ext-client.hpp" #include "adnl-ext-client.h" diff --git a/adnl/adnl-ext-client.h b/adnl/adnl-ext-client.h index bd60f202..b9a5d570 100644 --- a/adnl/adnl-ext-client.h +++ b/adnl/adnl-ext-client.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-ext-client.hpp b/adnl/adnl-ext-client.hpp index d58247fc..13339725 100644 --- a/adnl/adnl-ext-client.hpp +++ b/adnl/adnl-ext-client.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -80,6 +80,9 @@ class AdnlExtClientImpl : public AdnlExtClient { if (!conn_.empty() && conn_.get() == conn) { callback_->on_stop_ready(); conn_ = {}; + for (auto& q : out_queries_) { + td::actor::send_closure(q.second, &AdnlQuery::set_error, td::Status::Error(ErrorCode::cancelled)); + } alarm_timestamp() = next_create_at_; try_stop(); } diff --git a/adnl/adnl-ext-connection.cpp b/adnl/adnl-ext-connection.cpp index ffae186b..06926f4d 100644 --- a/adnl/adnl-ext-connection.cpp +++ b/adnl/adnl-ext-connection.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-ext-connection.hpp" diff --git a/adnl/adnl-ext-connection.hpp b/adnl/adnl-ext-connection.hpp index 0b7f1012..ce3c29b3 100644 --- a/adnl/adnl-ext-connection.hpp +++ b/adnl/adnl-ext-connection.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-ext-server.cpp b/adnl/adnl-ext-server.cpp index a23d8610..ed04469c 100644 --- a/adnl/adnl-ext-server.cpp +++ b/adnl/adnl-ext-server.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-ext-server.hpp" #include "keys/encryptor.h" diff --git a/adnl/adnl-ext-server.h b/adnl/adnl-ext-server.h index 10e0ca9c..618b2fb4 100644 --- a/adnl/adnl-ext-server.h +++ b/adnl/adnl-ext-server.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-ext-server.hpp b/adnl/adnl-ext-server.hpp index 7ffa9e69..b4604a76 100644 --- a/adnl/adnl-ext-server.hpp +++ b/adnl/adnl-ext-server.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-local-id.cpp b/adnl/adnl-local-id.cpp index 79b2bcd2..b4818276 100644 --- a/adnl/adnl-local-id.cpp +++ b/adnl/adnl-local-id.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "td/utils/crypto.h" #include "td/utils/Random.h" @@ -40,13 +40,15 @@ AdnlAddressList AdnlLocalId::get_addr_list() const { return addr_list_; } -void AdnlLocalId::receive(td::BufferSlice data) { +void AdnlLocalId::receive(td::IPAddress addr, td::BufferSlice data) { auto P = td::PromiseCreator::lambda( - [peer_table = peer_table_, dst = short_id_, id = print_id()](td::Result R) { + [peer_table = peer_table_, dst = short_id_, addr, id = print_id()](td::Result R) { if (R.is_error()) { VLOG(ADNL_WARNING) << id << ": dropping IN message: cannot decrypt: " << R.move_as_error(); } else { - td::actor::send_closure(peer_table, &AdnlPeerTable::receive_decrypted_packet, dst, R.move_as_ok()); + auto packet = R.move_as_ok(); + packet.set_remote_addr(addr); + td::actor::send_closure(peer_table, &AdnlPeerTable::receive_decrypted_packet, dst, std::move(packet)); } }); @@ -77,7 +79,9 @@ void AdnlLocalId::deliver_query(AdnlNodeIdShort src, td::BufferSlice data, td::P } VLOG(ADNL_INFO) << this << ": dropping IN message from " << src << ": no callbacks for custom query. firstint=" << td::TlParser(s.as_slice()).fetch_int(); - promise.set_error(td::Status::Error(ErrorCode::warning, "no callbacks for query")); + promise.set_error(td::Status::Error(ErrorCode::warning, PSTRING() << "dropping IN message from " << src + << ": no callbacks for custom query. firstint=" + << td::TlParser(s.as_slice()).fetch_int())); } void AdnlLocalId::subscribe(std::string prefix, std::unique_ptr callback) { @@ -117,7 +121,7 @@ void AdnlLocalId::update_address_list(AdnlAddressList addr_list) { } void AdnlLocalId::publish_address_list() { - if (dht_node_.empty() || addr_list_.empty()) { + if (dht_node_.empty() || addr_list_.empty() || (addr_list_.size() == 0 && !addr_list_.has_reverse())) { VLOG(ADNL_NOTICE) << this << ": skipping public addr list, because localid (or dht node) not fully initialized"; return; } @@ -171,19 +175,33 @@ void AdnlLocalId::publish_address_list() { td::actor::send_closure(keyring_, &keyring::Keyring::sign_message, short_id_.pubkey_hash(), std::move(B), std::move(P)); + + if (addr_list_.has_reverse()) { + td::actor::send_closure( + dht_node_, &dht::Dht::register_reverse_connection, id_, [print_id = print_id()](td::Result R) { + if (R.is_error()) { + VLOG(ADNL_NOTICE) << print_id << ": failed to register reverse connection in DHT: " << R.move_as_error(); + } else { + VLOG(ADNL_INFO) << print_id << ": registered reverse connection"; + } + }); + } } -AdnlLocalId::AdnlLocalId(AdnlNodeIdFull id, AdnlAddressList addr_list, td::actor::ActorId peer_table, - td::actor::ActorId keyring, td::actor::ActorId dht_node) { - id_ = std::move(id); +AdnlLocalId::AdnlLocalId(AdnlNodeIdFull id, AdnlAddressList addr_list, td::uint32 mode, + td::actor::ActorId peer_table, td::actor::ActorId keyring, + td::actor::ActorId dht_node) + : peer_table_(std::move(peer_table)) + , keyring_(std::move(keyring)) + , dht_node_(std::move(dht_node)) + , addr_list_(std::move(addr_list)) + , id_(std::move(id)) + , mode_(mode) { short_id_ = id_.compute_short_id(); - addr_list_ = std::move(addr_list); - if (addr_list_.addrs().size() > 0) { + if (!addr_list_.empty()) { + addr_list_.set_reinit_date(Adnl::adnl_start_time()); addr_list_.set_version(static_cast(td::Clocks::system())); } - peer_table_ = peer_table; - keyring_ = keyring; - dht_node_ = dht_node; VLOG(ADNL_INFO) << this << ": created local id " << short_id_; } diff --git a/adnl/adnl-local-id.h b/adnl/adnl-local-id.h index 07326f79..c9ecfff1 100644 --- a/adnl/adnl-local-id.h +++ b/adnl/adnl-local-id.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -54,7 +54,7 @@ class AdnlLocalId : public td::actor::Actor { void decrypt_message(td::BufferSlice data, td::Promise promise); void deliver(AdnlNodeIdShort src, td::BufferSlice data); void deliver_query(AdnlNodeIdShort src, td::BufferSlice data, td::Promise promise); - void receive(td::BufferSlice data); + void receive(td::IPAddress addr, td::BufferSlice data); void subscribe(std::string prefix, std::unique_ptr callback); void unsubscribe(std::string prefix); @@ -67,8 +67,9 @@ class AdnlLocalId : public td::actor::Actor { void sign_batch_async(std::vector data, td::Promise>> promise); - AdnlLocalId(AdnlNodeIdFull id, AdnlAddressList addr_list, td::actor::ActorId peer_table, - td::actor::ActorId keyring, td::actor::ActorId dht_node); + AdnlLocalId(AdnlNodeIdFull id, AdnlAddressList addr_list, td::uint32 mode, + td::actor::ActorId peer_table, td::actor::ActorId keyring, + td::actor::ActorId dht_node); void start_up() override; void alarm() override; @@ -76,6 +77,10 @@ class AdnlLocalId : public td::actor::Actor { void update_packet(AdnlPacket packet, bool update_id, bool sign, td::int32 update_addr_list_if, td::int32 update_priority_addr_list_if, td::Promise promise); + td::uint32 get_mode() { + return mode_; + } + struct PrintId { AdnlNodeIdShort id; }; @@ -94,6 +99,8 @@ class AdnlLocalId : public td::actor::Actor { AdnlNodeIdFull id_; AdnlNodeIdShort short_id_; + td::uint32 mode_; + void publish_address_list(); }; diff --git a/adnl/adnl-message.cpp b/adnl/adnl-message.cpp index 4e09429a..0d712978 100644 --- a/adnl/adnl-message.cpp +++ b/adnl/adnl-message.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl/adnl-message.h" #include "auto/tl/ton_api.hpp" diff --git a/adnl/adnl-message.h b/adnl/adnl-message.h index baf5cf4c..43849e98 100644 --- a/adnl/adnl-message.h +++ b/adnl/adnl-message.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -249,6 +249,40 @@ class AdnlMessage { } }; +class OutboundAdnlMessage { + public: + template + OutboundAdnlMessage(T m, td::uint32 flags) : message_{std::move(m)}, flags_(flags) { + } + td::uint32 flags() const { + return flags_; + } + void set_flags(td::uint32 f) { + flags_ = f; + } + tl_object_ptr tl() const { + return message_.tl(); + } + td::uint32 size() const { + return message_.size(); + } + template + void visit(F &&f) { + message_.visit(std::move(f)); + } + template + void visit(F &&f) const { + message_.visit(std::move(f)); + } + AdnlMessage release() { + return std::move(message_); + } + + private: + AdnlMessage message_; + td::uint32 flags_; +}; + class AdnlMessageList { public: AdnlMessageList() { @@ -291,6 +325,48 @@ class AdnlMessageList { std::vector messages_; }; +class OutboundAdnlMessageList { + public: + OutboundAdnlMessageList() { + } + OutboundAdnlMessageList(tl_object_ptr message, td::uint32 flags) { + auto msg = OutboundAdnlMessage{std::move(message), flags}; + messages_.emplace_back(std::move(msg)); + } + OutboundAdnlMessageList(std::vector> messages, td::uint32 flags) { + for (auto &message : messages) { + messages_.push_back(OutboundAdnlMessage{std::move(message), flags}); + } + } + void push_back(OutboundAdnlMessage message) { + messages_.push_back(std::move(message)); + } + + td::uint32 size() const { + return static_cast(messages_.size()); + } + tl_object_ptr one_message() const { + CHECK(size() == 1); + return messages_[0].tl(); + } + std::vector> mult_messages() const { + std::vector> vec; + for (auto &m : messages_) { + vec.emplace_back(m.tl()); + } + return vec; + } + static std::vector> empty_vector() { + return std::vector>{}; + } + auto &vector() { + return messages_; + } + + private: + std::vector messages_; +}; + } // namespace adnl } // namespace ton diff --git a/adnl/adnl-network-manager.cpp b/adnl/adnl-network-manager.cpp index c5c25463..077fb939 100644 --- a/adnl/adnl-network-manager.cpp +++ b/adnl/adnl-network-manager.cpp @@ -14,11 +14,15 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-network-manager.hpp" #include "adnl-peer-table.h" +#include "auto/tl/ton_api.hpp" + +#include "td/utils/overloaded.h" + namespace ton { namespace adnl { @@ -27,25 +31,84 @@ td::actor::ActorOwn AdnlNetworkManager::create(td::uint16 po return td::actor::create_actor("NetworkManager", port); } -void AdnlNetworkManagerImpl::add_listening_udp_port(td::uint16 port) { +AdnlNetworkManagerImpl::OutDesc *AdnlNetworkManagerImpl::choose_out_iface(td::uint8 cat, td::uint32 priority) { + auto it = out_desc_.upper_bound(priority); + while (true) { + if (it == out_desc_.begin()) { + return nullptr; + } + it--; + + auto &v = it->second; + for (auto &x : v) { + if (x.cat_mask.test(cat)) { + return &x; + } + } + } +} + +size_t AdnlNetworkManagerImpl::add_listening_udp_port(td::uint16 port) { + auto it = port_2_socket_.find(port); + if (it != port_2_socket_.end()) { + return it->second; + } class Callback : public td::UdpServer::Callback { public: - Callback(td::actor::ActorShared manager) : manager_(std::move(manager)) { + Callback(td::actor::ActorShared manager, size_t idx) + : manager_(std::move(manager)), idx_(idx) { } private: td::actor::ActorShared manager_; + size_t idx_; void on_udp_message(td::UdpMessage udp_message) override { - td::actor::send_closure_later(manager_, &AdnlNetworkManagerImpl::receive_udp_message, std::move(udp_message)); + td::actor::send_closure_later(manager_, &AdnlNetworkManagerImpl::receive_udp_message, std::move(udp_message), + idx_); } }; - auto X = td::UdpServer::create("udp server", port, std::make_unique(actor_shared(this))); + auto idx = udp_sockets_.size(); + auto X = td::UdpServer::create("udp server", port, std::make_unique(actor_shared(this), idx)); X.ensure(); - udp_servers_.emplace(port, X.move_as_ok()); + port_2_socket_[port] = idx; + udp_sockets_.push_back(UdpSocketDesc{port, X.move_as_ok()}); + return idx; } -void AdnlNetworkManagerImpl::receive_udp_message(td::UdpMessage message) { +void AdnlNetworkManagerImpl::add_self_addr(td::IPAddress addr, AdnlCategoryMask cat_mask, td::uint32 priority) { + auto port = td::narrow_cast(addr.get_port()); + size_t idx = add_listening_udp_port(port); + add_in_addr(InDesc{port, nullptr, cat_mask}, idx); + auto d = OutDesc{port, td::IPAddress{}, nullptr, idx}; + for (auto &it : out_desc_[priority]) { + if (it == d) { + it.cat_mask |= cat_mask; + return; + } + } + + d.cat_mask = cat_mask; + out_desc_[priority].push_back(std::move(d)); +} + +void AdnlNetworkManagerImpl::add_proxy_addr(td::IPAddress addr, td::uint16 local_port, std::shared_ptr proxy, + AdnlCategoryMask cat_mask, td::uint32 priority) { + size_t idx = add_listening_udp_port(local_port); + add_in_addr(InDesc{local_port, proxy, cat_mask}, idx); + auto d = OutDesc{local_port, addr, proxy, idx}; + for (auto &it : out_desc_[priority]) { + if (it == d) { + it.cat_mask |= cat_mask; + return; + } + } + d.cat_mask = cat_mask; + proxy_register(d); + out_desc_[priority].push_back(std::move(d)); +} + +void AdnlNetworkManagerImpl::receive_udp_message(td::UdpMessage message, size_t idx) { if (!callback_) { LOG(ERROR) << this << ": dropping IN message [?->?]: peer table unitialized"; return; @@ -54,58 +117,180 @@ void AdnlNetworkManagerImpl::receive_udp_message(td::UdpMessage message) { VLOG(ADNL_WARNING) << this << ": dropping ERROR message: " << message.error; return; } + if (message.data.size() < 32) { + VLOG(ADNL_WARNING) << this << ": received too small proxy packet of size " << message.data.size(); + return; + } + if (message.data.size() >= get_mtu() + 128) { + VLOG(ADNL_NOTICE) << this << ": received huge packet of size " << message.data.size(); + } + CHECK(idx < udp_sockets_.size()); + auto &socket = udp_sockets_[idx]; + AdnlCategoryMask cat_mask; + bool from_proxy = false; + if (socket.allow_proxy) { + td::Bits256 x; + x.as_slice().copy_from(message.data.as_slice().truncate(32)); + auto it = proxy_addrs_.find(x); + if (it != proxy_addrs_.end()) { + from_proxy = true; + CHECK(it->second < in_desc_.size()); + auto &proxy_iface = in_desc_[it->second]; + CHECK(proxy_iface.is_proxy()); + auto R = in_desc_[it->second].proxy->decrypt(std::move(message.data)); + if (R.is_error()) { + VLOG(ADNL_WARNING) << this << ": failed to decrypt proxy mesage: " << R.move_as_error(); + return; + } + auto packet = R.move_as_ok(); + if (packet.flags & 1) { + message.address.init_host_port(td::IPAddress::ipv4_to_str(packet.ip), packet.port).ensure(); + } else { + message.address = td::IPAddress{}; + } + if ((packet.flags & 6) == 6) { + if (proxy_iface.received.packet_is_delivered(packet.adnl_start_time, packet.seqno)) { + VLOG(ADNL_WARNING) << this << ": dropping duplicate proxy packet"; + return; + } + } + if (packet.flags & 8) { + if (packet.date < td::Clocks::system() - 60 || packet.date > td::Clocks::system() + 60) { + VLOG(ADNL_WARNING) << this << ": dropping proxy packet: bad time " << packet.date; + return; + } + } + if (!(packet.flags & (1 << 16))) { + VLOG(ADNL_WARNING) << this << ": dropping proxy packet: packet has outbound flag"; + return; + } + if (packet.flags & (1 << 17)) { + auto F = fetch_tl_object(std::move(packet.data), true); + if (F.is_error()) { + VLOG(ADNL_WARNING) << this << ": dropping proxy packet: bad control packet"; + return; + } + ton_api::downcast_call(*F.move_as_ok().get(), + td::overloaded( + [&](const ton_api::adnl_proxyControlPacketPing &f) { + auto &v = *proxy_iface.out_desc; + auto data = + create_serialize_tl_object(f.id_); + AdnlProxy::Packet p; + p.flags = 6 | (1 << 17); + p.ip = 0; + p.port = 0; + p.data = std::move(data); + p.adnl_start_time = Adnl::adnl_start_time(); + p.seqno = ++v.out_seqno; + + auto enc = v.proxy->encrypt(std::move(p)); + + td::UdpMessage M; + M.address = v.proxy_addr; + M.data = std::move(enc); + + td::actor::send_closure(socket.server, &td::UdpServer::send, std::move(M)); + }, + [&](const ton_api::adnl_proxyControlPacketPong &f) {}, + [&](const ton_api::adnl_proxyControlPacketRegister &f) {})); + return; + } + message.data = std::move(packet.data); + cat_mask = in_desc_[it->second].cat_mask; + } + } + if (!from_proxy) { + if (socket.in_desc == std::numeric_limits::max()) { + VLOG(ADNL_WARNING) << this << ": received bad packet to proxy-only listenung port"; + return; + } + cat_mask = in_desc_[socket.in_desc].cat_mask; + } if (message.data.size() >= get_mtu()) { VLOG(ADNL_NOTICE) << this << ": received huge packet of size " << message.data.size(); } received_messages_++; if (received_messages_ % 64 == 0) { - VLOG(ADNL_DEBUG) << this << ": received " << received_messages_ << "udp messages"; + VLOG(ADNL_DEBUG) << this << ": received " << received_messages_ << " udp messages"; } VLOG(ADNL_EXTRA_DEBUG) << this << ": received message of size " << message.data.size(); - callback_->receive_packet(message.address, std::move(message.data)); + callback_->receive_packet(message.address, cat_mask, std::move(message.data)); } void AdnlNetworkManagerImpl::send_udp_packet(AdnlNodeIdShort src_id, AdnlNodeIdShort dst_id, td::IPAddress dst_addr, td::uint32 priority, td::BufferSlice data) { - auto randseed = 1; // use DST? - while (priority > 0) { - if (out_desc_[priority].size() > 0) { - break; - } - priority--; - } - if (out_desc_[priority].size() == 0) { - VLOG(ADNL_WARNING) << this << ": dropping OUT message [" << src_id << "->" << dst_id << "]: no out desc"; + auto it = adnl_id_2_cat_.find(src_id); + if (it == adnl_id_2_cat_.end()) { + VLOG(ADNL_WARNING) << this << ": dropping OUT message [" << src_id << "->" << dst_id << "]: unknown src"; return; } - auto &dv = out_desc_[priority]; - auto &v = dv[randseed % dv.size()]; + auto out = choose_out_iface(it->second, priority); + if (!out) { + VLOG(ADNL_WARNING) << this << ": dropping OUT message [" << src_id << "->" << dst_id << "]: no out rules"; + return; + } + + auto &v = *out; + auto &socket = udp_sockets_[v.socket_idx]; if (!v.is_proxy()) { - auto it = udp_servers_.find(static_cast(v.addr.get_port())); - CHECK(it != udp_servers_.end()); - td::UdpMessage M; M.address = dst_addr; M.data = std::move(data); CHECK(M.data.size() <= get_mtu()); - td::actor::send_closure(it->second, &td::UdpServer::send, std::move(M)); + td::actor::send_closure(socket.server, &td::UdpServer::send, std::move(M)); } else { - auto it = udp_servers_.find(out_udp_port_); - CHECK(it != udp_servers_.end()); + AdnlProxy::Packet p; + p.flags = 7; + p.ip = dst_addr.get_ipv4(); + p.port = static_cast(dst_addr.get_port()); + p.data = std::move(data); + p.adnl_start_time = Adnl::adnl_start_time(); + p.seqno = ++v.out_seqno; - auto enc = v.proxy->encrypt( - AdnlProxy::Packet{dst_addr.get_ipv4(), static_cast(dst_addr.get_port()), std::move(data)}); + auto enc = v.proxy->encrypt(std::move(p)); td::UdpMessage M; - M.address = v.addr; + M.address = v.proxy_addr; M.data = std::move(enc); - td::actor::send_closure(it->second, &td::UdpServer::send, std::move(M)); + td::actor::send_closure(socket.server, &td::UdpServer::send, std::move(M)); + } +} + +void AdnlNetworkManagerImpl::proxy_register(OutDesc &desc) { + auto data = create_serialize_tl_object(0, 0); + AdnlProxy::Packet p; + p.flags = 6 | (1 << 17); + p.ip = 0; + p.port = 0; + p.data = std::move(data); + p.adnl_start_time = Adnl::adnl_start_time(); + p.seqno = ++desc.out_seqno; + + auto enc = desc.proxy->encrypt(std::move(p)); + + td::UdpMessage M; + M.address = desc.proxy_addr; + M.data = std::move(enc); + + auto &socket = udp_sockets_[desc.socket_idx]; + td::actor::send_closure(socket.server, &td::UdpServer::send, std::move(M)); +} + +void AdnlNetworkManagerImpl::alarm() { + alarm_timestamp() = td::Timestamp::in(60.0); + for (auto &vec : out_desc_) { + for (auto &desc : vec.second) { + if (desc.is_proxy()) { + proxy_register(desc); + } + } } } diff --git a/adnl/adnl-network-manager.h b/adnl/adnl-network-manager.h index c090aa2a..67cf602a 100644 --- a/adnl/adnl-network-manager.h +++ b/adnl/adnl-network-manager.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -26,6 +26,8 @@ #include "adnl-node-id.hpp" #include "adnl-proxy-types.h" +#include + namespace td { class UdpServer; } @@ -36,6 +38,8 @@ namespace adnl { class AdnlPeerTable; +using AdnlCategoryMask = std::bitset<256>; + class AdnlNetworkConnection : public td::actor::Actor { public: class Callback { @@ -46,6 +50,8 @@ class AdnlNetworkConnection : public td::actor::Actor { virtual void send(AdnlNodeIdShort src, AdnlNodeIdShort dst, td::uint32 priority, td::BufferSlice message) = 0; virtual bool is_alive() const = 0; virtual bool is_active() const = 0; + + virtual void get_ip_str(td::Promise promise) = 0; virtual ~AdnlNetworkConnection() = default; }; @@ -56,7 +62,7 @@ class AdnlNetworkManager : public td::actor::Actor { public: virtual ~Callback() = default; //virtual void receive_packet(td::IPAddress addr, ConnHandle conn_handle, td::BufferSlice data) = 0; - virtual void receive_packet(td::IPAddress addr, td::BufferSlice data) = 0; + virtual void receive_packet(td::IPAddress addr, AdnlCategoryMask cat_mask, td::BufferSlice data) = 0; }; static td::actor::ActorOwn create(td::uint16 out_port); @@ -64,14 +70,16 @@ class AdnlNetworkManager : public td::actor::Actor { virtual void install_callback(std::unique_ptr callback) = 0; - virtual void add_self_addr(td::IPAddress addr, td::uint32 priority) = 0; - virtual void add_proxy_addr(td::IPAddress addr, std::shared_ptr proxy, td::uint32 priority) = 0; + virtual void add_self_addr(td::IPAddress addr, AdnlCategoryMask cat_mask, td::uint32 priority) = 0; + virtual void add_proxy_addr(td::IPAddress addr, td::uint16 local_port, std::shared_ptr proxy, + AdnlCategoryMask cat_mask, td::uint32 priority) = 0; virtual void send_udp_packet(AdnlNodeIdShort src_id, AdnlNodeIdShort dst_id, td::IPAddress dst_addr, td::uint32 priority, td::BufferSlice data) = 0; //virtual void send_tcp_packet(AdnlNodeIdShort src_id, AdnlNodeIdShort dst_id, td::IPAddress dst_addr, // td::uint32 priority, td::BufferSlice data) = 0; //virtual void send_answer_packet(AdnlNodeIdShort src_id, AdnlNodeIdShort dst_id, td::IPAddress dst_addr, // ConnHandle conn_handle, td::uint32 priority, td::BufferSlice data) = 0; + virtual void set_local_id_category(AdnlNodeIdShort id, td::uint8 cat) = 0; static constexpr td::uint32 get_mtu() { return 1440; diff --git a/adnl/adnl-network-manager.hpp b/adnl/adnl-network-manager.hpp index 818a322f..a77be19d 100644 --- a/adnl/adnl-network-manager.hpp +++ b/adnl/adnl-network-manager.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -25,6 +25,7 @@ #include "td/actor/PromiseFuture.h" #include "adnl-network-manager.h" +#include "adnl-received-mask.h" #include @@ -41,16 +42,61 @@ class AdnlPeerTable; class AdnlNetworkManagerImpl : public AdnlNetworkManager { public: struct OutDesc { - td::IPAddress addr; + td::uint16 port; + td::IPAddress proxy_addr; std::shared_ptr proxy; + size_t socket_idx; + td::int64 out_seqno{0}; + AdnlCategoryMask cat_mask{0}; bool is_proxy() const { return proxy != nullptr; } bool operator==(const OutDesc &with) const { - return addr == with.addr && is_proxy() == with.is_proxy(); + if (port != with.port) { + return false; + } + if (!is_proxy()) { + return !with.is_proxy(); + } + if (!with.is_proxy()) { + return false; + } + return proxy_addr == with.proxy_addr && proxy->id() == with.proxy->id(); } }; + struct InDesc { + td::uint16 port; + std::shared_ptr proxy; + AdnlCategoryMask cat_mask; + AdnlReceivedMaskVersion received{}; + OutDesc *out_desc = nullptr; + bool is_proxy() const { + return proxy != nullptr; + } + bool operator==(const InDesc &with) const { + if (port != with.port) { + return false; + } + if (!is_proxy()) { + return !with.is_proxy(); + } + if (!with.is_proxy()) { + return false; + } + return proxy->id() == with.proxy->id(); + } + }; + struct UdpSocketDesc { + UdpSocketDesc(td::uint16 port, td::actor::ActorOwn server) : port(port), server(std::move(server)) { + } + td::uint16 port; + td::actor::ActorOwn server; + size_t in_desc{std::numeric_limits::max()}; + bool allow_proxy{false}; + }; + + OutDesc *choose_out_iface(td::uint8 cat, td::uint32 priority); AdnlNetworkManagerImpl(td::uint16 out_udp_port) : out_udp_port_(out_udp_port) { } @@ -59,45 +105,60 @@ class AdnlNetworkManagerImpl : public AdnlNetworkManager { callback_ = std::move(callback); } - void add_self_addr(td::IPAddress addr, td::uint32 priority) override { - auto x = OutDesc{addr, nullptr}; - auto &v = out_desc_[priority]; - for (auto &y : v) { - if (x == y) { + void alarm() override; + void start_up() override { + alarm_timestamp() = td::Timestamp::in(60.0); + } + + void add_in_addr(InDesc desc, size_t socket_idx) { + for (size_t idx = 0; idx < in_desc_.size(); idx++) { + if (in_desc_[idx] == desc) { + in_desc_[idx].cat_mask |= desc.cat_mask; return; } } - out_desc_[priority].push_back(std::move(x)); - add_listening_udp_port(static_cast(addr.get_port())); - } - void add_proxy_addr(td::IPAddress addr, std::shared_ptr proxy, td::uint32 priority) override { - auto x = OutDesc{addr, std::move(proxy)}; - auto &v = out_desc_[priority]; - for (auto &y : v) { - if (x == y) { - return; - } - } - out_desc_[priority].push_back(std::move(x)); - if (!udp_servers_.count(out_udp_port_)) { - add_listening_udp_port(out_udp_port_); + if (desc.is_proxy()) { + udp_sockets_[socket_idx].allow_proxy = true; + proxy_addrs_[desc.proxy->id()] = in_desc_.size(); + } else { + CHECK(udp_sockets_[socket_idx].in_desc == std::numeric_limits::max()); + udp_sockets_[socket_idx].in_desc = in_desc_.size(); } + in_desc_.push_back(std::move(desc)); } + + void add_self_addr(td::IPAddress addr, AdnlCategoryMask cat_mask, td::uint32 priority) override; + void add_proxy_addr(td::IPAddress addr, td::uint16 local_port, std::shared_ptr proxy, + AdnlCategoryMask cat_mask, td::uint32 priority) override; void send_udp_packet(AdnlNodeIdShort src_id, AdnlNodeIdShort dst_id, td::IPAddress dst_addr, td::uint32 priority, td::BufferSlice data) override; - void add_listening_udp_port(td::uint16 port); - void receive_udp_message(td::UdpMessage message); + void set_local_id_category(AdnlNodeIdShort id, td::uint8 cat) override { + if (cat == 255) { + adnl_id_2_cat_.erase(id); + } else { + adnl_id_2_cat_[id] = cat; + } + } + + size_t add_listening_udp_port(td::uint16 port); + void receive_udp_message(td::UdpMessage message, size_t idx); + void proxy_register(OutDesc &desc); private: std::unique_ptr callback_; std::map> out_desc_; + std::vector in_desc_; + std::map proxy_addrs_; td::uint64 received_messages_ = 0; td::uint64 sent_messages_ = 0; - std::map> udp_servers_; + std::vector udp_sockets_; + std::map port_2_socket_; + + std::map adnl_id_2_cat_; td::uint16 out_udp_port_; }; diff --git a/adnl/adnl-node-id.cpp b/adnl/adnl-node-id.cpp new file mode 100644 index 00000000..d3830aea --- /dev/null +++ b/adnl/adnl-node-id.cpp @@ -0,0 +1,35 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ + +#include "adnl-node-id.hpp" + +#include "common/util.h" + +namespace ton { +namespace adnl { +td::Result AdnlNodeIdShort::parse(td::Slice id) { + TRY_RESULT(str, td::adnl_id_decode(id)); + return AdnlNodeIdShort(str); +} + +std::string AdnlNodeIdShort::serialize() { + return adnl_id_encode(hash_.as_slice()).move_as_ok(); +} +} // namespace adnl +} // namespace ton diff --git a/adnl/adnl-node-id.hpp b/adnl/adnl-node-id.hpp index 84f425f8..2d3ade16 100644 --- a/adnl/adnl-node-id.hpp +++ b/adnl/adnl-node-id.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -75,6 +75,10 @@ class AdnlNodeIdShort { return hash_.is_zero(); } + static td::Result parse(td::Slice key); + + std::string serialize(); + private: PublicKeyHash hash_; }; diff --git a/adnl/adnl-node.cpp b/adnl/adnl-node.cpp index fe14f0ed..4bf080a8 100644 --- a/adnl/adnl-node.cpp +++ b/adnl/adnl-node.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-node.h" diff --git a/adnl/adnl-node.h b/adnl/adnl-node.h index 509e64f5..0fc6c2cf 100644 --- a/adnl/adnl-node.h +++ b/adnl/adnl-node.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-packet.cpp b/adnl/adnl-packet.cpp index 6043d287..b12493e7 100644 --- a/adnl/adnl-packet.cpp +++ b/adnl/adnl-packet.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-packet.h" #include "td/utils/Random.h" diff --git a/adnl/adnl-packet.h b/adnl/adnl-packet.h index 6459e06b..363a74c2 100644 --- a/adnl/adnl-packet.h +++ b/adnl/adnl-packet.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -132,6 +132,9 @@ class AdnlPacket { auto signature() const { return signature_.clone(); } + auto remote_addr() const { + return remote_addr_; + } void init_random(); @@ -188,6 +191,10 @@ class AdnlPacket { flags_ |= Flags::f_reinit_date; } + void set_remote_addr(td::IPAddress addr) { + remote_addr_ = addr; + } + private: td::BufferSlice rand1_; td::uint32 flags_{0}; @@ -204,6 +211,8 @@ class AdnlPacket { td::int32 dst_reinit_date_{0}; td::BufferSlice signature_; td::BufferSlice rand2_; + + td::IPAddress remote_addr_; }; } // namespace adnl diff --git a/adnl/adnl-peer-table.cpp b/adnl/adnl-peer-table.cpp index 04a77684..54891515 100644 --- a/adnl/adnl-peer-table.cpp +++ b/adnl/adnl-peer-table.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-peer-table.hpp" #include "adnl-peer.h" @@ -30,6 +30,7 @@ #include "utils.hpp" #include "adnl-query.h" #include "adnl-ext-client.h" +#include "adnl-tunnel.h" namespace ton { @@ -49,7 +50,7 @@ td::actor::ActorOwn Adnl::create(std::string db, td::actor::ActorId(td::actor::create_actor("PeerTable", db, keyring)); } -void AdnlPeerTableImpl::receive_packet(td::BufferSlice data) { +void AdnlPeerTableImpl::receive_packet(td::IPAddress addr, AdnlCategoryMask cat_mask, td::BufferSlice data) { if (data.size() < 32) { VLOG(ADNL_WARNING) << this << ": dropping IN message [?->?]: message too short: len=" << data.size(); return; @@ -58,16 +59,24 @@ void AdnlPeerTableImpl::receive_packet(td::BufferSlice data) { AdnlNodeIdShort dst{data.as_slice().truncate(32)}; data.confirm_read(32); - auto it = local_ids_own_.find(dst); - if (it != local_ids_own_.end()) { - td::actor::send_closure(it->second, &AdnlLocalId::receive, std::move(data)); + auto it = local_ids_.find(dst); + if (it != local_ids_.end()) { + if (!cat_mask.test(it->second.cat)) { + VLOG(ADNL_WARNING) << this << ": dropping IN message [?->" << dst << "]: category mismatch"; + return; + } + td::actor::send_closure(it->second.local_id, &AdnlLocalId::receive, addr, std::move(data)); return; } AdnlChannelIdShort dst_chan_id{dst.pubkey_hash()}; auto it2 = channels_.find(dst_chan_id); if (it2 != channels_.end()) { - td::actor::send_closure(it2->second, &AdnlChannel::receive, std::move(data)); + if (!cat_mask.test(it2->second.second)) { + VLOG(ADNL_WARNING) << this << ": dropping IN message to channel [?->" << dst << "]: category mismatch"; + return; + } + td::actor::send_closure(it2->second.first, &AdnlChannel::receive, addr, std::move(data)); return; } @@ -103,21 +112,22 @@ void AdnlPeerTableImpl::receive_decrypted_packet(AdnlNodeIdShort dst, AdnlPacket CHECK(it != peers_.end()); } - auto it2 = local_ids_own_.find(dst); - if (it2 == local_ids_own_.end()) { + auto it2 = local_ids_.find(dst); + if (it2 == local_ids_.end()) { VLOG(ADNL_ERROR) << this << ": dropping IN message [" << packet.from_short() << "->" << dst << "]: unknown dst (but how did we decrypt message?)"; return; } - td::actor::send_closure(it->second, &AdnlPeer::receive_packet, dst, it2->second.get(), std::move(packet)); + td::actor::send_closure(it->second, &AdnlPeer::receive_packet, dst, it2->second.mode, it2->second.local_id.get(), + std::move(packet)); } void AdnlPeerTableImpl::add_peer(AdnlNodeIdShort local_id, AdnlNodeIdFull id, AdnlAddressList addr_list) { auto id_short = id.compute_short_id(); VLOG(ADNL_DEBUG) << this << ": adding peer " << id_short << " for local id " << local_id; - auto it2 = local_ids_own_.find(local_id); - CHECK(it2 != local_ids_own_.end()); + auto it2 = local_ids_.find(local_id); + CHECK(it2 != local_ids_.end()); auto it = peers_.find(id_short); if (it == peers_.end()) { @@ -126,7 +136,8 @@ void AdnlPeerTableImpl::add_peer(AdnlNodeIdShort local_id, AdnlNodeIdFull id, Ad } td::actor::send_closure(it->second, &AdnlPeer::update_id, std::move(id)); if (!addr_list.empty()) { - td::actor::send_closure(it->second, &AdnlPeer::update_addr_list, local_id, it2->second.get(), std::move(addr_list)); + td::actor::send_closure(it->second, &AdnlPeer::update_addr_list, local_id, it2->second.mode, + it2->second.local_id.get(), std::move(addr_list)); } } @@ -136,20 +147,22 @@ void AdnlPeerTableImpl::add_static_nodes_from_config(AdnlNodesList nodes) { } } -void AdnlPeerTableImpl::send_message_in(AdnlNodeIdShort src, AdnlNodeIdShort dst, AdnlMessage message) { +void AdnlPeerTableImpl::send_message_in(AdnlNodeIdShort src, AdnlNodeIdShort dst, AdnlMessage message, + td::uint32 flags) { auto it = peers_.find(dst); if (it == peers_.end()) { it = peers_.emplace(dst, AdnlPeer::create(network_manager_, actor_id(this), dht_node_, dst)).first; } - auto it2 = local_ids_own_.find(src); - if (it2 == local_ids_own_.end()) { + auto it2 = local_ids_.find(src); + if (it2 == local_ids_.end()) { LOG(ERROR) << this << ": dropping OUT message [" << src << "->" << dst << "]: unknown src"; return; } - td::actor::send_closure(it->second, &AdnlPeer::send_one_message, src, it2->second.get(), std::move(message)); + td::actor::send_closure(it->second, &AdnlPeer::send_one_message, src, it2->second.mode, it2->second.local_id.get(), + OutboundAdnlMessage{std::move(message), flags}); } void AdnlPeerTableImpl::answer_query(AdnlNodeIdShort src, AdnlNodeIdShort dst, AdnlQueryId query_id, @@ -159,7 +172,7 @@ void AdnlPeerTableImpl::answer_query(AdnlNodeIdShort src, AdnlNodeIdShort dst, A << "]: message too big: size=" << data.size(); return; } - send_message_in(src, dst, adnlmessage::AdnlMessageAnswer{query_id, std::move(data)}); + send_message_in(src, dst, adnlmessage::AdnlMessageAnswer{query_id, std::move(data)}, 0); } void AdnlPeerTableImpl::send_query(AdnlNodeIdShort src, AdnlNodeIdShort dst, std::string name, @@ -175,59 +188,70 @@ void AdnlPeerTableImpl::send_query(AdnlNodeIdShort src, AdnlNodeIdShort dst, std it = peers_.emplace(dst, AdnlPeer::create(network_manager_, actor_id(this), dht_node_, dst)).first; } - auto it2 = local_ids_own_.find(src); - if (it2 == local_ids_own_.end()) { + auto it2 = local_ids_.find(src); + if (it2 == local_ids_.end()) { LOG(ERROR) << this << ": dropping OUT message [" << src << "->" << dst << "]: unknown src"; return; } - td::actor::send_closure(it->second, &AdnlPeer::send_query, src, it2->second.get(), name, std::move(promise), timeout, - std::move(data)); + td::actor::send_closure(it->second, &AdnlPeer::send_query, src, it2->second.mode, it2->second.local_id.get(), name, + std::move(promise), timeout, std::move(data), 0); } -void AdnlPeerTableImpl::add_id(AdnlNodeIdFull id, AdnlAddressList addr_list) { +void AdnlPeerTableImpl::add_id_ex(AdnlNodeIdFull id, AdnlAddressList addr_list, td::uint8 cat, td::uint32 mode) { auto a = id.compute_short_id(); VLOG(ADNL_INFO) << "adnl: adding local id " << a; - auto it = local_ids_own_.find(a); + auto it = local_ids_.find(a); - if (it != local_ids_own_.end()) { - td::actor::send_closure(it->second, &AdnlLocalId::update_address_list, std::move(addr_list)); + if (it != local_ids_.end()) { + if (it->second.cat != cat) { + it->second.cat = cat; + if (!network_manager_.empty()) { + td::actor::send_closure(network_manager_, &AdnlNetworkManager::set_local_id_category, a, cat); + } + } + td::actor::send_closure(it->second.local_id, &AdnlLocalId::update_address_list, std::move(addr_list)); } else { - local_ids_own_[a] = td::actor::create_actor("localid", std::move(id), std::move(addr_list), - actor_id(this), keyring_, dht_node_); + local_ids_.emplace( + a, LocalIdInfo{td::actor::create_actor("localid", std::move(id), std::move(addr_list), mode, + actor_id(this), keyring_, dht_node_), + cat, mode}); + if (!network_manager_.empty()) { + td::actor::send_closure(network_manager_, &AdnlNetworkManager::set_local_id_category, a, cat); + } } } void AdnlPeerTableImpl::del_id(AdnlNodeIdShort id, td::Promise promise) { VLOG(ADNL_INFO) << "adnl: deleting local id " << id; - local_ids_own_.erase(id); + local_ids_.erase(id); promise.set_value(td::Unit()); } void AdnlPeerTableImpl::subscribe(AdnlNodeIdShort dst, std::string prefix, std::unique_ptr callback) { - auto it = local_ids_own_.find(dst); - LOG_CHECK(it != local_ids_own_.end()) << "dst=" << dst; + auto it = local_ids_.find(dst); + LOG_CHECK(it != local_ids_.end()) << "dst=" << dst; - td::actor::send_closure(it->second, &AdnlLocalId::subscribe, prefix, std::move(callback)); + td::actor::send_closure(it->second.local_id, &AdnlLocalId::subscribe, prefix, std::move(callback)); } void AdnlPeerTableImpl::unsubscribe(AdnlNodeIdShort dst, std::string prefix) { - auto it = local_ids_own_.find(dst); + auto it = local_ids_.find(dst); - if (it != local_ids_own_.end()) { - td::actor::send_closure(it->second, &AdnlLocalId::unsubscribe, prefix); + if (it != local_ids_.end()) { + td::actor::send_closure(it->second.local_id, &AdnlLocalId::unsubscribe, prefix); } } void AdnlPeerTableImpl::register_dht_node(td::actor::ActorId dht_node) { dht_node_ = dht_node; - for (auto it = peers_.begin(); it != peers_.end(); it++) { - td::actor::send_closure(it->second, &AdnlPeer::update_dht_node, dht_node_); + for (auto &peer : peers_) { + td::actor::send_closure(peer.second, &AdnlPeer::update_dht_node, dht_node_); } - for (auto it = local_ids_own_.begin(); it != local_ids_own_.end(); it++) { - td::actor::send_closure(it->second, &AdnlLocalId::update_dht_node, dht_node_); + for (auto &local_id : local_ids_) { + td::actor::send_closure(local_id.second.local_id, &AdnlLocalId::update_dht_node, dht_node_); } } @@ -236,8 +260,8 @@ void AdnlPeerTableImpl::register_network_manager(td::actor::ActorId id) : id_(id) { } @@ -248,28 +272,35 @@ void AdnlPeerTableImpl::register_network_manager(td::actor::ActorId(actor_id(this)); td::actor::send_closure(network_manager_, &AdnlNetworkManager::install_callback, std::move(cb)); + + for (auto &id : local_ids_) { + td::actor::send_closure(network_manager_, &AdnlNetworkManager::set_local_id_category, id.first, id.second.cat); + } } void AdnlPeerTableImpl::get_addr_list(AdnlNodeIdShort id, td::Promise promise) { - auto it = local_ids_own_.find(id); - if (it == local_ids_own_.end()) { + auto it = local_ids_.find(id); + if (it == local_ids_.end()) { promise.set_error(td::Status::Error(ErrorCode::notready)); return; } - td::actor::send_closure(it->second, &AdnlLocalId::get_addr_list_async, std::move(promise)); + td::actor::send_closure(it->second.local_id, &AdnlLocalId::get_addr_list_async, std::move(promise)); } void AdnlPeerTableImpl::get_self_node(AdnlNodeIdShort id, td::Promise promise) { - auto it = local_ids_own_.find(id); - if (it == local_ids_own_.end()) { + auto it = local_ids_.find(id); + if (it == local_ids_.end()) { promise.set_error(td::Status::Error(ErrorCode::notready)); return; } - td::actor::send_closure(it->second, &AdnlLocalId::get_self_node, std::move(promise)); + td::actor::send_closure(it->second.local_id, &AdnlLocalId::get_self_node, std::move(promise)); } -void AdnlPeerTableImpl::register_channel(AdnlChannelIdShort id, td::actor::ActorId channel) { - auto success = channels_.emplace(id, channel).second; +void AdnlPeerTableImpl::register_channel(AdnlChannelIdShort id, AdnlNodeIdShort local_id, + td::actor::ActorId channel) { + auto it = local_ids_.find(local_id); + auto cat = (it != local_ids_.end()) ? it->second.cat : 255; + auto success = channels_.emplace(id, std::make_pair(channel, cat)).second; CHECK(success); } @@ -309,16 +340,16 @@ AdnlPeerTableImpl::AdnlPeerTableImpl(std::string db_root, td::actor::ActorIdsecond, &AdnlLocalId::deliver, src, std::move(data)); + auto it = local_ids_.find(dst); + if (it != local_ids_.end()) { + td::actor::send_closure(it->second.local_id, &AdnlLocalId::deliver, src, std::move(data)); } } void AdnlPeerTableImpl::deliver_query(AdnlNodeIdShort src, AdnlNodeIdShort dst, td::BufferSlice data, td::Promise promise) { - auto it = local_ids_own_.find(dst); - if (it != local_ids_own_.end()) { - td::actor::send_closure(it->second, &AdnlLocalId::deliver_query, src, std::move(data), std::move(promise)); + auto it = local_ids_.find(dst); + if (it != local_ids_.end()) { + td::actor::send_closure(it->second.local_id, &AdnlLocalId::deliver_query, src, std::move(data), std::move(promise)); } else { LOG(WARNING) << "deliver query: unknown dst " << dst; promise.set_error(td::Status::Error(ErrorCode::notready, "cannot deliver: unknown DST")); @@ -327,9 +358,9 @@ void AdnlPeerTableImpl::deliver_query(AdnlNodeIdShort src, AdnlNodeIdShort dst, void AdnlPeerTableImpl::decrypt_message(AdnlNodeIdShort dst, td::BufferSlice data, td::Promise promise) { - auto it = local_ids_own_.find(dst); - if (it != local_ids_own_.end()) { - td::actor::send_closure(it->second, &AdnlLocalId::decrypt_message, std::move(data), std::move(promise)); + auto it = local_ids_.find(dst); + if (it != local_ids_.end()) { + td::actor::send_closure(it->second.local_id, &AdnlLocalId::decrypt_message, std::move(data), std::move(promise)); } else { LOG(WARNING) << "decrypt message: unknown dst " << dst; promise.set_error(td::Status::Error(ErrorCode::notready, "cannot decrypt: unknown DST")); @@ -341,6 +372,19 @@ void AdnlPeerTableImpl::create_ext_server(std::vector ids, std: promise.set_value(AdnlExtServerCreator::create(actor_id(this), std::move(ids), std::move(ports))); } +void AdnlPeerTableImpl::create_tunnel(AdnlNodeIdShort dst, td::uint32 size, + td::Promise, AdnlAddress>> promise) { +} + +void AdnlPeerTableImpl::get_conn_ip_str(AdnlNodeIdShort l_id, AdnlNodeIdShort p_id, td::Promise promise) { + auto it = peers_.find(p_id); + if (it == peers_.end()) { + promise.set_value("undefined"); + return; + } + td::actor::send_closure(it->second, &AdnlPeer::get_conn_ip_str, l_id, std::move(promise)); +} + } // namespace adnl } // namespace ton diff --git a/adnl/adnl-peer-table.h b/adnl/adnl-peer-table.h index 1050222c..cb7da613 100644 --- a/adnl/adnl-peer-table.h +++ b/adnl/adnl-peer-table.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -89,11 +89,12 @@ class AdnlPeerTable : public Adnl { virtual void answer_query(AdnlNodeIdShort src, AdnlNodeIdShort dst, AdnlQueryId query_id, td::BufferSlice data) = 0; - virtual void receive_packet(td::BufferSlice data) = 0; + virtual void receive_packet(td::IPAddress addr, AdnlCategoryMask cat_mask, td::BufferSlice data) = 0; virtual void receive_decrypted_packet(AdnlNodeIdShort dst, AdnlPacket packet) = 0; - virtual void send_message_in(AdnlNodeIdShort src, AdnlNodeIdShort dst, AdnlMessage message) = 0; + virtual void send_message_in(AdnlNodeIdShort src, AdnlNodeIdShort dst, AdnlMessage message, td::uint32 flags) = 0; - virtual void register_channel(AdnlChannelIdShort id, td::actor::ActorId channel) = 0; + virtual void register_channel(AdnlChannelIdShort id, AdnlNodeIdShort local_id, + td::actor::ActorId channel) = 0; virtual void unregister_channel(AdnlChannelIdShort id) = 0; virtual void add_static_node(AdnlNode node) = 0; @@ -109,6 +110,7 @@ class AdnlPeerTable : public Adnl { virtual void deliver_query(AdnlNodeIdShort src, AdnlNodeIdShort dst, td::BufferSlice data, td::Promise promise) = 0; virtual void decrypt_message(AdnlNodeIdShort dst, td::BufferSlice data, td::Promise promise) = 0; + virtual void get_conn_ip_str(AdnlNodeIdShort l_id, AdnlNodeIdShort p_id, td::Promise promise) = 0; }; } // namespace adnl diff --git a/adnl/adnl-peer-table.hpp b/adnl/adnl-peer-table.hpp index a4873727..1c30b84c 100644 --- a/adnl/adnl-peer-table.hpp +++ b/adnl/adnl-peer-table.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -43,16 +43,19 @@ class AdnlPeerTableImpl : public AdnlPeerTable { void add_peer(AdnlNodeIdShort local_id, AdnlNodeIdFull id, AdnlAddressList addr_list) override; void add_static_nodes_from_config(AdnlNodesList nodes) override; - void receive_packet(td::BufferSlice data) override; + void receive_packet(td::IPAddress addr, AdnlCategoryMask cat_mask, td::BufferSlice data) override; void receive_decrypted_packet(AdnlNodeIdShort dst, AdnlPacket data) override; - void send_message_in(AdnlNodeIdShort src, AdnlNodeIdShort dst, AdnlMessage message) override; + void send_message_in(AdnlNodeIdShort src, AdnlNodeIdShort dst, AdnlMessage message, td::uint32 flags) override; void send_message(AdnlNodeIdShort src, AdnlNodeIdShort dst, td::BufferSlice data) override { + send_message_ex(src, dst, std::move(data), 0); + } + void send_message_ex(AdnlNodeIdShort src, AdnlNodeIdShort dst, td::BufferSlice data, td::uint32 flags) override { if (data.size() > huge_packet_max_size()) { VLOG(ADNL_WARNING) << "dropping too big packet [" << src << "->" << dst << "]: size=" << data.size(); VLOG(ADNL_WARNING) << "DUMP: " << td::buffer_to_hex(data.as_slice().truncate(128)); return; } - send_message_in(src, dst, AdnlMessage{adnlmessage::AdnlMessageCustom{std::move(data)}}); + send_message_in(src, dst, AdnlMessage{adnlmessage::AdnlMessageCustom{std::move(data)}}, flags); } void answer_query(AdnlNodeIdShort src, AdnlNodeIdShort dst, AdnlQueryId query_id, td::BufferSlice data) override; void send_query(AdnlNodeIdShort src, AdnlNodeIdShort dst, std::string name, td::Promise promise, @@ -61,7 +64,7 @@ class AdnlPeerTableImpl : public AdnlPeerTable { td::Timestamp timeout, td::BufferSlice data, td::uint64 max_answer_size) override { send_query(src, dst, name, std::move(promise), timeout, std::move(data)); } - void add_id(AdnlNodeIdFull id, AdnlAddressList addr_list) override; + void add_id_ex(AdnlNodeIdFull id, AdnlAddressList addr_list, td::uint8 cat, td::uint32 mode) override; void del_id(AdnlNodeIdShort id, td::Promise promise) override; void subscribe(AdnlNodeIdShort dst, std::string prefix, std::unique_ptr callback) override; void unsubscribe(AdnlNodeIdShort dst, std::string prefix) override; @@ -70,9 +73,14 @@ class AdnlPeerTableImpl : public AdnlPeerTable { void get_addr_list(AdnlNodeIdShort id, td::Promise promise) override; void get_self_node(AdnlNodeIdShort id, td::Promise promise) override; void start_up() override; - void register_channel(AdnlChannelIdShort id, td::actor::ActorId channel) override; + void register_channel(AdnlChannelIdShort id, AdnlNodeIdShort local_id, + td::actor::ActorId channel) override; void unregister_channel(AdnlChannelIdShort id) override; + void check_id_exists(AdnlNodeIdShort id, td::Promise promise) override { + promise.set_value(local_ids_.count(id)); + } + void write_new_addr_list_to_db(AdnlNodeIdShort local_id, AdnlNodeIdShort peer_id, AdnlDbItem node, td::Promise promise) override; void get_addr_list_from_db(AdnlNodeIdShort local_id, AdnlNodeIdShort peer_id, @@ -96,12 +104,21 @@ class AdnlPeerTableImpl : public AdnlPeerTable { void create_ext_server(std::vector ids, std::vector ports, td::Promise> promise) override; + void create_tunnel(AdnlNodeIdShort dst, td::uint32 size, + td::Promise, AdnlAddress>> promise) override; + void get_conn_ip_str(AdnlNodeIdShort l_id, AdnlNodeIdShort p_id, td::Promise promise) override; + struct PrintId {}; PrintId print_id() const { return PrintId{}; } private: + struct LocalIdInfo { + td::actor::ActorOwn local_id; + td::uint8 cat; + td::uint32 mode; + }; td::actor::ActorId keyring_; td::actor::ActorId network_manager_; @@ -111,13 +128,14 @@ class AdnlPeerTableImpl : public AdnlPeerTable { void deliver_one_message(AdnlNodeIdShort src, AdnlNodeIdShort dst, AdnlMessage message); std::map> peers_; - std::map> local_ids_own_; - std::map> channels_; + std::map local_ids_; + std::map, td::uint8>> channels_; td::actor::ActorOwn db_; td::actor::ActorOwn ext_server_; + AdnlNodeIdShort proxy_addr_; //std::map> out_queries_; //td::uint64 last_query_id_ = 1; }; diff --git a/adnl/adnl-peer.cpp b/adnl/adnl-peer.cpp index ba077682..3e21a7f5 100644 --- a/adnl/adnl-peer.cpp +++ b/adnl/adnl-peer.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-peer.h" #include "adnl-peer.hpp" @@ -68,7 +68,9 @@ void AdnlPeerPairImpl::alarm() { } if (retry_send_at_ && retry_send_at_.is_in_past()) { retry_send_at_ = td::Timestamp::never(); - send_messages_in(std::move(pending_messages_), false); + auto messages = std::move(pending_messages_); + pending_messages_.clear(); + send_messages_in(std::move(messages), false); } alarm_timestamp().relax(next_dht_query_at_); alarm_timestamp().relax(next_db_update_at_); @@ -113,6 +115,9 @@ void AdnlPeerPairImpl::discover() { } void AdnlPeerPairImpl::receive_packet_checked(AdnlPacket packet) { + last_received_packet_ = td::Timestamp::now(); + try_reinit_at_ = td::Timestamp::never(); + request_reverse_ping_after_ = td::Timestamp::in(15.0); auto d = Adnl::adnl_start_time(); if (packet.dst_reinit_date() > d) { VLOG(ADNL_WARNING) << this << ": dropping IN message: too new our reinit date " << packet.dst_reinit_date(); @@ -131,18 +136,24 @@ void AdnlPeerPairImpl::receive_packet_checked(AdnlPacket packet) { } if (packet.dst_reinit_date() > 0 && packet.dst_reinit_date() < d) { if (!packet.addr_list().empty()) { - update_addr_list(packet.addr_list()); + auto addr_list = packet.addr_list(); + if (packet.remote_addr().is_valid() && addr_list.size() == 0) { + VLOG(ADNL_DEBUG) << "adding implicit address " << packet.remote_addr(); + addr_list.add_udp_address(packet.remote_addr()); + } + update_addr_list(std::move(addr_list)); } if (!packet.priority_addr_list().empty()) { update_addr_list(packet.priority_addr_list()); } - VLOG(ADNL_NOTICE) << this << ": dropping IN message old our reinit date " << packet.reinit_date() << " date=" << d; - auto M = AdnlMessage{adnlmessage::AdnlMessageNop{}}; + VLOG(ADNL_NOTICE) << this << ": dropping IN message old our reinit date " << packet.dst_reinit_date() + << " date=" << d; + auto M = OutboundAdnlMessage{adnlmessage::AdnlMessageNop{}, 0}; send_message(std::move(M)); return; } if (packet.seqno() > 0) { - if (received_packet(static_cast(packet.seqno()))) { + if (received_packet(packet.seqno())) { VLOG(ADNL_INFO) << this << ": dropping IN message: old seqno: " << packet.seqno() << " (current max " << in_seqno_ << ")"; return; @@ -159,7 +170,9 @@ void AdnlPeerPairImpl::receive_packet_checked(AdnlPacket packet) { // accepted // delivering - add_received_packet(static_cast(packet.seqno())); + if (packet.seqno() > 0) { + add_received_packet(packet.seqno()); + } if (packet.confirm_seqno() > ack_seqno_) { ack_seqno_ = packet.confirm_seqno(); @@ -174,7 +187,12 @@ void AdnlPeerPairImpl::receive_packet_checked(AdnlPacket packet) { } if (!packet.addr_list().empty()) { - update_addr_list(packet.addr_list()); + auto addr_list = packet.addr_list(); + if (packet.remote_addr().is_valid() && addr_list.size() == 0) { + VLOG(ADNL_DEBUG) << "adding implicit address " << packet.remote_addr(); + addr_list.add_udp_address(packet.remote_addr()); + } + update_addr_list(std::move(addr_list)); } if (!packet.priority_addr_list().empty()) { update_addr_list(packet.priority_addr_list()); @@ -194,7 +212,9 @@ void AdnlPeerPairImpl::receive_packet_from_channel(AdnlChannelIdShort id, AdnlPa VLOG(ADNL_NOTICE) << this << ": dropping IN message: outdated channel id" << id; return; } - channel_ready_ = true; + if (channel_inited_) { + channel_ready_ = true; + } receive_packet_checked(std::move(packet)); } @@ -219,113 +239,133 @@ void AdnlPeerPairImpl::deliver_message(AdnlMessage message) { message.visit([&](const auto &obj) { this->process_message(obj); }); } -void AdnlPeerPairImpl::send_messages_in(std::vector messages, bool allow_postpone) { - auto connR = get_conn(); - if (connR.is_error()) { - if (!allow_postpone) { - VLOG(ADNL_NOTICE) << this << ": dropping OUT messages: cannot get conn: " << connR.move_as_error(); +void AdnlPeerPairImpl::send_messages_in(std::vector messages, bool allow_postpone) { + for (td::int32 idx = 0; idx < 2; idx++) { + std::vector not_sent; + + auto connR = get_conn(idx == 1); + if (connR.is_error()) { + if (!allow_postpone) { + VLOG(ADNL_NOTICE) << this << ": dropping OUT messages: cannot get conn: " << connR.move_as_error(); + return; + } + VLOG(ADNL_INFO) << this << ": delaying OUT messages: cannot get conn: " << connR.move_as_error(); + if (!retry_send_at_) { + retry_send_at_.relax(td::Timestamp::in(10.0)); + alarm_timestamp().relax(retry_send_at_); + } + for (auto &m : messages) { + pending_messages_.push_back(std::move(m)); + } return; } - VLOG(ADNL_INFO) << this << ": delaying OUT messages: cannot get conn: " << connR.move_as_error(); - if (!retry_send_at_) { - retry_send_at_.relax(td::Timestamp::in(10.0)); - alarm_timestamp().relax(retry_send_at_); + auto C = connR.move_as_ok(); + bool is_direct = C.second; + auto conn = std::move(C.first); + if (idx == 1) { + CHECK(is_direct); } - for (auto &m : messages) { - pending_messages_.push_back(std::move(m)); + + size_t ptr = 0; + bool first = true; + do { + bool try_reinit = try_reinit_at_ && try_reinit_at_.is_in_past(); + bool via_channel = channel_ready_ && !try_reinit; + size_t s = (via_channel ? channel_packet_header_max_size() : packet_header_max_size()); + if (first) { + s += 2 * addr_list_max_size(); + } + + AdnlPacket packet; + packet.set_seqno(++out_seqno_); + packet.set_confirm_seqno(in_seqno_); + + if (first) { + if (!channel_inited_) { + auto M = adnlmessage::AdnlMessageCreateChannel{channel_pub_, channel_pk_date_}; + s += M.size(); + packet.add_message(std::move(M)); + } else if (!channel_ready_) { + auto M = adnlmessage::AdnlMessageConfirmChannel{channel_pub_, peer_channel_pub_, channel_pk_date_}; + s += M.size(); + packet.add_message(std::move(M)); + } + } + + if (!addr_list_.empty()) { + packet.set_received_addr_list_version(addr_list_.version()); + } + if (!priority_addr_list_.empty()) { + packet.set_received_priority_addr_list_version(priority_addr_list_.version()); + } + + while (ptr < messages.size()) { + auto &M = messages[ptr]; + if (!is_direct && (M.flags() & Adnl::SendFlags::direct_only)) { + not_sent.push_back(std::move(M)); + continue; + } + CHECK(M.size() <= get_mtu()); + if (s + M.size() <= AdnlNetworkManager::get_mtu()) { + s += M.size(); + packet.add_message(M.release()); + ptr++; + } else { + break; + } + } + + if (!via_channel) { + packet.set_reinit_date(Adnl::adnl_start_time(), reinit_date_); + packet.set_source(local_id_); + } + + if (!first) { + if (!channel_inited_) { + auto M = adnlmessage::AdnlMessageCreateChannel{channel_pub_, channel_pk_date_}; + if (s + M.size() <= AdnlNetworkManager::get_mtu()) { + s += M.size(); + packet.add_message(std::move(M)); + } + } else if (!channel_ready_) { + auto M = adnlmessage::AdnlMessageConfirmChannel{channel_pub_, peer_channel_pub_, channel_pk_date_}; + if (s + M.size() <= AdnlNetworkManager::get_mtu()) { + s += M.size(); + packet.add_message(std::move(M)); + } + } + } + + packet.run_basic_checks().ensure(); + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), conn, id = print_id(), + via_channel](td::Result res) { + if (res.is_error()) { + LOG(ERROR) << id << ": dropping OUT message: error while creating packet: " << res.move_as_error(); + } else { + td::actor::send_closure(SelfId, &AdnlPeerPairImpl::send_packet_continue, res.move_as_ok(), conn, via_channel); + } + }); + + td::actor::send_closure(local_actor_, &AdnlLocalId::update_packet, std::move(packet), + (!channel_ready_ && ack_seqno_ == 0 && in_seqno_ == 0) || try_reinit, !via_channel, + (first || s + addr_list_max_size() <= AdnlNetworkManager::get_mtu()) + ? (try_reinit ? 0 : peer_recv_addr_list_version_) + : 0x7fffffff, + (first || s + 2 * addr_list_max_size() <= AdnlNetworkManager::get_mtu()) + ? peer_recv_priority_addr_list_version_ + : 0x7fffffff, + std::move(P)); + first = false; + } while (ptr < messages.size()); + messages = std::move(not_sent); + if (!messages.size()) { + break; } - return; } - auto conn = connR.move_as_ok(); - - size_t ptr = 0; - bool first = true; - do { - size_t s = (channel_ready_ ? channel_packet_header_max_size() : packet_header_max_size()); - if (first) { - s += 2 * addr_list_max_size(); - } - - AdnlPacket packet; - packet.set_seqno(++out_seqno_); - packet.set_confirm_seqno(in_seqno_); - - if (first) { - if (!channel_inited_) { - auto M = adnlmessage::AdnlMessageCreateChannel{channel_pub_, channel_pk_date_}; - s += M.size(); - packet.add_message(std::move(M)); - } else if (!channel_ready_) { - auto M = adnlmessage::AdnlMessageConfirmChannel{channel_pub_, peer_channel_pub_, channel_pk_date_}; - s += M.size(); - packet.add_message(std::move(M)); - } - } - - if (!addr_list_.empty()) { - packet.set_received_addr_list_version(addr_list_.version()); - } - if (!priority_addr_list_.empty()) { - packet.set_received_priority_addr_list_version(priority_addr_list_.version()); - } - - while (ptr < messages.size()) { - auto &M = messages[ptr]; - CHECK(M.size() <= get_mtu()); - if (s + M.size() <= AdnlNetworkManager::get_mtu()) { - s += M.size(); - packet.add_message(std::move(M)); - ptr++; - } else { - break; - } - } - - if (!channel_ready_) { - packet.set_reinit_date(Adnl::adnl_start_time(), reinit_date_); - packet.set_source(local_id_); - } - - if (!first) { - if (!channel_inited_) { - auto M = adnlmessage::AdnlMessageCreateChannel{channel_pub_, channel_pk_date_}; - if (s + M.size() <= AdnlNetworkManager::get_mtu()) { - s += M.size(); - packet.add_message(std::move(M)); - } - } else if (!channel_ready_) { - auto M = adnlmessage::AdnlMessageConfirmChannel{channel_pub_, peer_channel_pub_, channel_pk_date_}; - if (s + M.size() <= AdnlNetworkManager::get_mtu()) { - s += M.size(); - packet.add_message(std::move(M)); - } - } - } - - packet.run_basic_checks().ensure(); - auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), conn, id = print_id(), - via_channel = channel_ready_](td::Result res) { - if (res.is_error()) { - LOG(ERROR) << id << ": dropping OUT message: error while creating packet: " << res.move_as_error(); - } else { - td::actor::send_closure(SelfId, &AdnlPeerPairImpl::send_packet_continue, res.move_as_ok(), conn, via_channel); - } - }); - - td::actor::send_closure( - local_actor_, &AdnlLocalId::update_packet, std::move(packet), - !channel_ready_ && ack_seqno_ == 0 && in_seqno_ == 0, !channel_ready_, - (first || s + addr_list_max_size() <= AdnlNetworkManager::get_mtu()) ? peer_recv_addr_list_version_ - : 0x7fffffff, - (first || s + 2 * addr_list_max_size() <= AdnlNetworkManager::get_mtu()) ? peer_recv_priority_addr_list_version_ - : 0x7fffffff, - std::move(P)); - first = false; - } while (ptr < messages.size()); } -void AdnlPeerPairImpl::send_messages(std::vector messages) { - std::vector new_vec; +void AdnlPeerPairImpl::send_messages(std::vector messages) { + std::vector new_vec; for (auto &M : messages) { if (M.size() <= get_mtu()) { new_vec.push_back(std::move(M)); @@ -345,7 +385,8 @@ void AdnlPeerPairImpl::send_messages(std::vector messages) { } B.confirm_read(data.size()); - new_vec.push_back(AdnlMessage{adnlmessage::AdnlMessagePart{hash, size, offset, std::move(data)}}); + new_vec.push_back( + OutboundAdnlMessage{adnlmessage::AdnlMessagePart{hash, size, offset, std::move(data)}, M.flags()}); offset += part_size; } } @@ -355,6 +396,9 @@ void AdnlPeerPairImpl::send_messages(std::vector messages) { void AdnlPeerPairImpl::send_packet_continue(AdnlPacket packet, td::actor::ActorId conn, bool via_channel) { + if (!try_reinit_at_ && last_received_packet_ < td::Timestamp::in(-5.0)) { + try_reinit_at_ = td::Timestamp::in(10.0); + } packet.run_basic_checks().ensure(); auto B = serialize_tl_object(packet.tl(), true); if (via_channel) { @@ -390,7 +434,7 @@ void AdnlPeerPairImpl::send_packet_continue(AdnlPacket packet, td::actor::ActorI } void AdnlPeerPairImpl::send_query(std::string name, td::Promise promise, td::Timestamp timeout, - td::BufferSlice data) { + td::BufferSlice data, td::uint32 flags) { AdnlQueryId id = AdnlQuery::random_query_id(); CHECK(out_queries_.count(id) == 0); @@ -400,7 +444,7 @@ void AdnlPeerPairImpl::send_query(std::string name, td::Promise out_queries_[id] = AdnlQuery::create(std::move(promise), std::move(P), name, timeout, id); - send_message(adnlmessage::AdnlMessageQuery{id, std::move(data)}); + send_message(OutboundAdnlMessage{adnlmessage::AdnlMessageQuery{id, std::move(data)}, flags}); } void AdnlPeerPairImpl::alarm_query(AdnlQueryId id) { @@ -408,7 +452,7 @@ void AdnlPeerPairImpl::alarm_query(AdnlQueryId id) { } AdnlPeerPairImpl::AdnlPeerPairImpl(td::actor::ActorId network_manager, - td::actor::ActorId peer_table, + td::actor::ActorId peer_table, td::uint32 local_mode, td::actor::ActorId local_actor, td::actor::ActorId peer, td::actor::ActorId dht_node, AdnlNodeIdShort local_id, AdnlNodeIdShort peer_id) { @@ -417,6 +461,7 @@ AdnlPeerPairImpl::AdnlPeerPairImpl(td::actor::ActorId networ local_actor_ = local_actor; peer_ = peer; dht_node_ = dht_node; + mode_ = local_mode; local_id_ = local_id; peer_id_short_ = peer_id; @@ -450,7 +495,8 @@ void AdnlPeerPairImpl::create_channel(pubkeys::Ed25519 pub, td::uint32 date) { channel_ = R.move_as_ok(); channel_inited_ = true; - td::actor::send_closure_later(peer_table_, &AdnlPeerTable::register_channel, channel_in_id_, channel_.get()); + td::actor::send_closure_later(peer_table_, &AdnlPeerTable::register_channel, channel_in_id_, local_id_, + channel_.get()); } else { VLOG(ADNL_WARNING) << this << ": failed to create channel: " << R.move_as_error(); } @@ -486,20 +532,20 @@ void AdnlPeerPairImpl::process_message(const adnlmessage::AdnlMessageReinit &mes } void AdnlPeerPairImpl::process_message(const adnlmessage::AdnlMessageQuery &message) { - auto P = td::PromiseCreator::lambda( - [SelfId = actor_id(this), query_id = message.query_id()](td::Result R) { - if (R.is_error()) { - LOG(WARNING) << "failed to answer query: " << R.move_as_error(); - } else { - auto data = R.move_as_ok(); - if (data.size() > Adnl::huge_packet_max_size()) { - LOG(WARNING) << "dropping too big answer query: size=" << data.size(); - } else { - td::actor::send_closure(SelfId, &AdnlPeerPairImpl::send_message, - AdnlMessage{adnlmessage::AdnlMessageAnswer{query_id, std::move(data)}}); - } - } - }); + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), query_id = message.query_id(), + flags = static_cast(0)](td::Result R) { + if (R.is_error()) { + LOG(WARNING) << "failed to answer query: " << R.move_as_error(); + } else { + auto data = R.move_as_ok(); + if (data.size() > Adnl::huge_packet_max_size()) { + LOG(WARNING) << "dropping too big answer query: size=" << data.size(); + } else { + td::actor::send_closure(SelfId, &AdnlPeerPairImpl::send_message, + OutboundAdnlMessage{adnlmessage::AdnlMessageAnswer{query_id, std::move(data)}, flags}); + } + } + }); td::actor::send_closure(local_actor_, &AdnlLocalId::deliver_query, peer_id_short_, message.data(), std::move(P)); } @@ -612,27 +658,32 @@ void AdnlPeerPairImpl::reinit(td::int32 date) { } } -td::Result> AdnlPeerPairImpl::get_conn() { +td::Result, bool>> AdnlPeerPairImpl::get_conn(bool direct_only) { if (!priority_addr_list_.empty() && priority_addr_list_.expire_at() < td::Clocks::system()) { priority_addr_list_ = AdnlAddressList{}; priority_conns_.clear(); } if (conns_.size() == 0 && priority_conns_.size() == 0) { - return td::Status::Error(ErrorCode::notready, PSTRING() - << "empty network information: version=" << addr_list_.version() - << " reinit_date=" << addr_list_.reinit_date() - << " real_reinit_date=" << reinit_date_); + if (has_reverse_addr_) { + request_reverse_ping(); + return td::Status::Error(ErrorCode::notready, "waiting for reverse ping"); + } else { + return td::Status::Error(ErrorCode::notready, PSTRING() + << "empty network information: version=" << addr_list_.version() + << " reinit_date=" << addr_list_.reinit_date() + << " real_reinit_date=" << reinit_date_); + } } for (auto &conn : priority_conns_) { - if (conn.ready()) { - return conn.conn.get(); + if (conn.ready() && (!direct_only || conn.is_direct())) { + return std::make_pair(conn.conn.get(), conn.is_direct()); } } for (auto &conn : conns_) { - if (conn.ready()) { - return conn.conn.get(); + if (conn.ready() && (!direct_only || conn.is_direct())) { + return std::make_pair(conn.conn.get(), conn.is_direct()); } } return td::Status::Error(ErrorCode::notready, "no active connections"); @@ -642,7 +693,7 @@ void AdnlPeerPairImpl::update_addr_list(AdnlAddressList addr_list) { if (addr_list.empty()) { return; } - CHECK(addr_list.size() > 0); + //CHECK(addr_list.size() > 0); if (addr_list.reinit_date() > td::Clocks::system() + 60) { VLOG(ADNL_WARNING) << "dropping addr list with too new reinit date"; @@ -670,24 +721,56 @@ void AdnlPeerPairImpl::update_addr_list(AdnlAddressList addr_list) { VLOG(ADNL_INFO) << this << ": updating addr list to version " << addr_list.version() << " size=" << addr_list.size(); const auto addrs = addr_list.addrs(); + has_reverse_addr_ = addr_list.has_reverse(); + if (has_reverse_addr_ && addrs.empty()) { + return; + } std::vector conns; - conns.resize(std::min(addr_list.size(), 3u)); auto &old_conns = priority ? priority_conns_ : conns_; - for (size_t i = 0; i < conns.size(); i++) { - auto &addr = addrs[i]; - auto hash = addr->get_hash(); - if (i < old_conns.size() && old_conns[i].addr->get_hash() == hash) { - conns[i] = std::move(old_conns[i]); - } else { - conns[i] = Conn{addr, actor_id(this), network_manager_}; + size_t idx = 0; + for (const auto &addr : addrs) { + if (addr->is_reverse()) { + continue; } + if ((mode_ & static_cast(AdnlLocalIdMode::direct_only)) && !addr->is_public()) { + continue; + } + auto hash = addr->get_hash(); + if (idx < old_conns.size() && old_conns[idx].addr->get_hash() == hash) { + conns.push_back(std::move(old_conns[idx])); + } else { + conns.push_back(Conn{addr, actor_id(this), network_manager_, peer_table_}); + } + idx++; } old_conns = std::move(conns); (priority ? priority_addr_list_ : addr_list_) = addr_list; } +void AdnlPeerPairImpl::get_conn_ip_str(td::Promise promise) { + if (conns_.size() == 0 && priority_conns_.size() == 0) { + promise.set_value("undefined"); + return; + } + + for (auto &conn : priority_conns_) { + if (conn.ready()) { + td::actor::send_closure(conn.conn, &AdnlNetworkConnection::get_ip_str, std::move(promise)); + return; + } + } + for (auto &conn : conns_) { + if (conn.ready()) { + td::actor::send_closure(conn.conn, &AdnlNetworkConnection::get_ip_str, std::move(promise)); + return; + } + } + + promise.set_value("undefined"); +} + void AdnlPeerImpl::update_id(AdnlNodeIdFull id) { CHECK(id.compute_short_id() == peer_id_short_); if (!peer_id_.empty()) { @@ -702,28 +785,29 @@ void AdnlPeerImpl::update_id(AdnlNodeIdFull id) { } void AdnlPeerPairImpl::Conn::create_conn(td::actor::ActorId peer, - td::actor::ActorId network_manager) { + td::actor::ActorId network_manager, + td::actor::ActorId adnl) { auto id = addr->get_hash(); - conn = addr->create_connection(network_manager, std::make_unique(peer, id)); + conn = addr->create_connection(network_manager, adnl, std::make_unique(peer, id)); } void AdnlPeerPairImpl::conn_change_state(AdnlConnectionIdShort id, bool ready) { if (ready) { if (pending_messages_.size() > 0) { - send_messages_in(std::move(pending_messages_), true); + auto messages = std::move(pending_messages_); + pending_messages_.clear(); + send_messages_in(std::move(messages), true); } } } -td::actor::ActorOwn AdnlPeerPair::create(td::actor::ActorId network_manager, - td::actor::ActorId peer_table, - td::actor::ActorId local_actor, - td::actor::ActorId peer_actor, - td::actor::ActorId dht_node, AdnlNodeIdShort local_id, - AdnlNodeIdShort peer_id) { - auto X = td::actor::create_actor("peerpair", network_manager, peer_table, local_actor, peer_actor, - dht_node, local_id, peer_id); +td::actor::ActorOwn AdnlPeerPair::create( + td::actor::ActorId network_manager, td::actor::ActorId peer_table, + td::uint32 local_mode, td::actor::ActorId local_actor, td::actor::ActorId peer_actor, + td::actor::ActorId dht_node, AdnlNodeIdShort local_id, AdnlNodeIdShort peer_id) { + auto X = td::actor::create_actor("peerpair", network_manager, peer_table, local_mode, local_actor, + peer_actor, dht_node, local_id, peer_id); return td::actor::ActorOwn(std::move(X)); } @@ -734,15 +818,16 @@ td::actor::ActorOwn AdnlPeer::create(td::actor::ActorId(std::move(X)); } -void AdnlPeerImpl::receive_packet(AdnlNodeIdShort dst, td::actor::ActorId dst_actor, AdnlPacket packet) { +void AdnlPeerImpl::receive_packet(AdnlNodeIdShort dst, td::uint32 dst_mode, td::actor::ActorId dst_actor, + AdnlPacket packet) { if (packet.inited_from()) { update_id(packet.from()); } auto it = peer_pairs_.find(dst); if (it == peer_pairs_.end()) { - auto X = - AdnlPeerPair::create(network_manager_, peer_table_, dst_actor, actor_id(this), dht_node_, dst, peer_id_short_); + auto X = AdnlPeerPair::create(network_manager_, peer_table_, dst_mode, dst_actor, actor_id(this), dht_node_, dst, + peer_id_short_); peer_pairs_.emplace(dst, std::move(X)); it = peer_pairs_.find(dst); CHECK(it != peer_pairs_.end()); @@ -752,15 +837,15 @@ void AdnlPeerImpl::receive_packet(AdnlNodeIdShort dst, td::actor::ActorIdsecond.get(), &AdnlPeerPair::receive_packet_checked, std::move(packet)); + td::actor::send_closure(it->second.get(), &AdnlPeerPair::receive_packet, std::move(packet)); } -void AdnlPeerImpl::send_messages(AdnlNodeIdShort src, td::actor::ActorId src_actor, - std::vector messages) { +void AdnlPeerImpl::send_messages(AdnlNodeIdShort src, td::uint32 src_mode, td::actor::ActorId src_actor, + std::vector messages) { auto it = peer_pairs_.find(src); if (it == peer_pairs_.end()) { - auto X = - AdnlPeerPair::create(network_manager_, peer_table_, src_actor, actor_id(this), dht_node_, src, peer_id_short_); + auto X = AdnlPeerPair::create(network_manager_, peer_table_, src_mode, src_actor, actor_id(this), dht_node_, src, + peer_id_short_); peer_pairs_.emplace(src, std::move(X)); it = peer_pairs_.find(src); CHECK(it != peer_pairs_.end()); @@ -773,12 +858,13 @@ void AdnlPeerImpl::send_messages(AdnlNodeIdShort src, td::actor::ActorIdsecond, &AdnlPeerPair::send_messages, std::move(messages)); } -void AdnlPeerImpl::send_query(AdnlNodeIdShort src, td::actor::ActorId src_actor, std::string name, - td::Promise promise, td::Timestamp timeout, td::BufferSlice data) { +void AdnlPeerImpl::send_query(AdnlNodeIdShort src, td::uint32 src_mode, td::actor::ActorId src_actor, + std::string name, td::Promise promise, td::Timestamp timeout, + td::BufferSlice data, td::uint32 flags) { auto it = peer_pairs_.find(src); if (it == peer_pairs_.end()) { - auto X = - AdnlPeerPair::create(network_manager_, peer_table_, src_actor, actor_id(this), dht_node_, src, peer_id_short_); + auto X = AdnlPeerPair::create(network_manager_, peer_table_, src_mode, src_actor, actor_id(this), dht_node_, src, + peer_id_short_); peer_pairs_.emplace(src, std::move(X)); it = peer_pairs_.find(src); CHECK(it != peer_pairs_.end()); @@ -788,7 +874,8 @@ void AdnlPeerImpl::send_query(AdnlNodeIdShort src, td::actor::ActorIdsecond, &AdnlPeerPair::send_query, name, std::move(promise), timeout, std::move(data)); + td::actor::send_closure(it->second, &AdnlPeerPair::send_query, name, std::move(promise), timeout, std::move(data), + flags); } void AdnlPeerImpl::del_local_id(AdnlNodeIdShort local_id) { @@ -802,12 +889,22 @@ void AdnlPeerImpl::update_dht_node(td::actor::ActorId dht_node) { } } -void AdnlPeerImpl::update_addr_list(AdnlNodeIdShort local_id, td::actor::ActorId local_actor, - AdnlAddressList addr_list) { +void AdnlPeerImpl::get_conn_ip_str(AdnlNodeIdShort l_id, td::Promise promise) { + auto it = peer_pairs_.find(l_id); + if (it == peer_pairs_.end()) { + promise.set_value("undefined"); + return; + } + + td::actor::send_closure(it->second, &AdnlPeerPair::get_conn_ip_str, std::move(promise)); +} + +void AdnlPeerImpl::update_addr_list(AdnlNodeIdShort local_id, td::uint32 local_mode, + td::actor::ActorId local_actor, AdnlAddressList addr_list) { auto it = peer_pairs_.find(local_id); if (it == peer_pairs_.end()) { - auto X = AdnlPeerPair::create(network_manager_, peer_table_, local_actor, actor_id(this), dht_node_, local_id, - peer_id_short_); + auto X = AdnlPeerPair::create(network_manager_, peer_table_, local_mode, local_actor, actor_id(this), dht_node_, + local_id, peer_id_short_); peer_pairs_.emplace(local_id, std::move(X)); it = peer_pairs_.find(local_id); CHECK(it != peer_pairs_.end()); @@ -873,6 +970,36 @@ void AdnlPeerPairImpl::update_peer_id(AdnlNodeIdFull id) { CHECK(!peer_id_.empty()); } +void AdnlPeerPairImpl::request_reverse_ping() { + if (request_reverse_ping_active_ || !request_reverse_ping_after_.is_in_past()) { + return; + } + VLOG(ADNL_INFO) << this << ": requesting reverse ping"; + request_reverse_ping_after_ = td::Timestamp::in(15.0); + request_reverse_ping_active_ = true; + td::actor::send_closure( + local_actor_, &AdnlLocalId::get_self_node, + [SelfId = actor_id(this), peer = peer_id_short_, dht = dht_node_](td::Result R) { + if (R.is_error()) { + td::actor::send_closure(SelfId, &AdnlPeerPairImpl::request_reverse_ping_result, R.move_as_error()); + return; + } + td::actor::send_closure( + dht, &dht::Dht::request_reverse_ping, R.move_as_ok(), peer, [SelfId](td::Result R) { + td::actor::send_closure(SelfId, &AdnlPeerPairImpl::request_reverse_ping_result, std::move(R)); + }); + }); +} + +void AdnlPeerPairImpl::request_reverse_ping_result(td::Result R) { + request_reverse_ping_active_ = false; + if (R.is_ok()) { + VLOG(ADNL_INFO) << this << ": reverse ping requested"; + } else { + VLOG(ADNL_INFO) << this << ": failed to request reverse ping: " << R.move_as_error(); + } +} + } // namespace adnl } // namespace ton diff --git a/adnl/adnl-peer.h b/adnl/adnl-peer.h index 91e8dd9b..8488e82e 100644 --- a/adnl/adnl-peer.h +++ b/adnl/adnl-peer.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -43,9 +43,9 @@ class AdnlPeerPair : public td::actor::Actor { virtual void receive_packet_checked(AdnlPacket packet) = 0; virtual void receive_packet(AdnlPacket packet) = 0; - virtual void send_messages(std::vector message) = 0; - inline void send_message(AdnlMessage message) { - std::vector vec; + virtual void send_messages(std::vector message) = 0; + inline void send_message(OutboundAdnlMessage message) { + std::vector vec; vec.push_back(std::move(message)); send_messages(std::move(vec)); } @@ -53,14 +53,15 @@ class AdnlPeerPair : public td::actor::Actor { return Adnl::get_mtu() + 128; } virtual void send_query(std::string name, td::Promise promise, td::Timestamp timeout, - td::BufferSlice data) = 0; + td::BufferSlice data, td::uint32 flags) = 0; virtual void alarm_query(AdnlQueryId query_id) = 0; virtual void update_dht_node(td::actor::ActorId dht_node) = 0; virtual void update_peer_id(AdnlNodeIdFull id) = 0; virtual void update_addr_list(AdnlAddressList addr_list) = 0; + virtual void get_conn_ip_str(td::Promise promise) = 0; static td::actor::ActorOwn create(td::actor::ActorId network_manager, - td::actor::ActorId peer_table, + td::actor::ActorId peer_table, td::uint32 local_mode, td::actor::ActorId local_actor, td::actor::ActorId peer_actor, td::actor::ActorId dht_node, AdnlNodeIdShort local_id, @@ -69,20 +70,24 @@ class AdnlPeerPair : public td::actor::Actor { class AdnlPeer : public td::actor::Actor { public: - virtual void receive_packet(AdnlNodeIdShort dst, td::actor::ActorId dst_actor, AdnlPacket message) = 0; - virtual void send_messages(AdnlNodeIdShort src, td::actor::ActorId src_actor, - std::vector messages) = 0; - virtual void send_query(AdnlNodeIdShort src, td::actor::ActorId src_actor, std::string name, - td::Promise promise, td::Timestamp timeout, td::BufferSlice data) = 0; - void send_one_message(AdnlNodeIdShort src, td::actor::ActorId src_actor, AdnlMessage message) { - std::vector vec; + virtual void receive_packet(AdnlNodeIdShort dst, td::uint32 dst_mode, td::actor::ActorId dst_actor, + AdnlPacket message) = 0; + virtual void send_messages(AdnlNodeIdShort src, td::uint32 src_mode, td::actor::ActorId src_actor, + std::vector messages) = 0; + virtual void send_query(AdnlNodeIdShort src, td::uint32 src_mode, td::actor::ActorId src_actor, + std::string name, td::Promise promise, td::Timestamp timeout, + td::BufferSlice data, td::uint32 flags) = 0; + void send_one_message(AdnlNodeIdShort src, td::uint32 src_mode, td::actor::ActorId src_actor, + OutboundAdnlMessage message) { + std::vector vec; vec.push_back(std::move(message)); - send_messages(src, src_actor, std::move(vec)); + send_messages(src, src_mode, src_actor, std::move(vec)); } - void send_message(AdnlNodeIdShort src, td::actor::ActorId src_actor, td::BufferSlice data) { - auto M = AdnlMessage{adnlmessage::AdnlMessageCustom{std::move(data)}}; - send_one_message(src, src_actor, std::move(M)); + void send_message(AdnlNodeIdShort src, td::uint32 src_mode, td::actor::ActorId src_actor, + td::BufferSlice data, td::uint32 flags) { + auto M = OutboundAdnlMessage{adnlmessage::AdnlMessageCustom{std::move(data)}, flags}; + send_one_message(src, src_mode, src_actor, std::move(M)); } static td::actor::ActorOwn create(td::actor::ActorId network_manager, @@ -91,9 +96,10 @@ class AdnlPeer : public td::actor::Actor { virtual void del_local_id(AdnlNodeIdShort local_id) = 0; virtual void update_id(AdnlNodeIdFull id) = 0; - virtual void update_addr_list(AdnlNodeIdShort local_id, td::actor::ActorId local_actor, - AdnlAddressList addr_list) = 0; + virtual void update_addr_list(AdnlNodeIdShort local_id, td::uint32 local_mode, + td::actor::ActorId local_actor, AdnlAddressList addr_list) = 0; virtual void update_dht_node(td::actor::ActorId dht_node) = 0; + virtual void get_conn_ip_str(AdnlNodeIdShort l_id, td::Promise promise) = 0; }; } // namespace adnl diff --git a/adnl/adnl-peer.hpp b/adnl/adnl-peer.hpp index 171be5ea..12ee01c6 100644 --- a/adnl/adnl-peer.hpp +++ b/adnl/adnl-peer.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -58,8 +58,9 @@ class AdnlPeerPairImpl : public AdnlPeerPair { } AdnlPeerPairImpl(td::actor::ActorId network_manager, td::actor::ActorId peer_table, - td::actor::ActorId local_actor, td::actor::ActorId peer, - td::actor::ActorId dht_node, AdnlNodeIdShort local_id, AdnlNodeIdShort peer_id); + td::uint32 local_mode, td::actor::ActorId local_actor, + td::actor::ActorId peer, td::actor::ActorId dht_node, AdnlNodeIdShort local_id, + AdnlNodeIdShort peer_id); void start_up() override; void alarm() override; @@ -70,11 +71,11 @@ class AdnlPeerPairImpl : public AdnlPeerPair { void receive_packet(AdnlPacket packet) override; void deliver_message(AdnlMessage message); - void send_messages_in(std::vector messages, bool allow_postpone); - void send_messages(std::vector messages) override; + void send_messages_in(std::vector messages, bool allow_postpone); + void send_messages(std::vector messages) override; void send_packet_continue(AdnlPacket packet, td::actor::ActorId conn, bool via_channel); - void send_query(std::string name, td::Promise promise, td::Timestamp timeout, - td::BufferSlice data) override; + void send_query(std::string name, td::Promise promise, td::Timestamp timeout, td::BufferSlice data, + td::uint32 flags) override; void alarm_query(AdnlQueryId id) override; @@ -87,6 +88,8 @@ class AdnlPeerPairImpl : public AdnlPeerPair { void update_addr_list(AdnlAddressList addr_list) override; void update_peer_id(AdnlNodeIdFull id) override; + void get_conn_ip_str(td::Promise promise) override; + void got_data_from_db(td::Result R); void got_data_from_static_nodes(td::Result R); void got_data_from_dht(td::Result R); @@ -120,10 +123,10 @@ class AdnlPeerPairImpl : public AdnlPeerPair { private: void reinit(td::int32 date); - td::Result> get_conn(); + td::Result, bool>> get_conn(bool direct_only); void create_channel(pubkeys::Ed25519 pub, td::uint32 date); - bool received_packet(td::uint32 seqno) const { + bool received_packet(td::uint64 seqno) const { CHECK(seqno > 0); if (seqno + 64 <= in_seqno_) { return true; @@ -134,7 +137,7 @@ class AdnlPeerPairImpl : public AdnlPeerPair { return recv_seqno_mask_ & (1ull << (in_seqno_ - seqno)); } - void add_received_packet(td::uint32 seqno) { + void add_received_packet(td::uint64 seqno) { CHECK(!received_packet(seqno)); if (seqno <= in_seqno_) { recv_seqno_mask_ |= (1ull << (in_seqno_ - seqno)); @@ -150,6 +153,9 @@ class AdnlPeerPairImpl : public AdnlPeerPair { } } + void request_reverse_ping(); + void request_reverse_ping_result(td::Result R); + struct Conn { class ConnCallback : public AdnlNetworkConnection::Callback { public: @@ -169,9 +175,9 @@ class AdnlPeerPairImpl : public AdnlPeerPair { td::actor::ActorOwn conn; Conn(AdnlAddress addr, td::actor::ActorId peer, - td::actor::ActorId network_manager) + td::actor::ActorId network_manager, td::actor::ActorId adnl) : addr(std::move(addr)) { - create_conn(peer, network_manager); + create_conn(peer, network_manager, adnl); } Conn() { } @@ -180,10 +186,15 @@ class AdnlPeerPairImpl : public AdnlPeerPair { return !conn.empty() && conn.get_actor_unsafe().is_active(); } - void create_conn(td::actor::ActorId peer, td::actor::ActorId network_manager); + bool is_direct() { + return addr->is_public(); + } + + void create_conn(td::actor::ActorId peer, td::actor::ActorId network_manager, + td::actor::ActorId adnl); }; - std::vector pending_messages_; + std::vector pending_messages_; td::actor::ActorId network_manager_; td::actor::ActorId peer_table_; @@ -232,6 +243,8 @@ class AdnlPeerPairImpl : public AdnlPeerPair { std::map> out_queries_; + td::uint32 mode_; + td::uint32 received_messages_ = 0; bool received_from_db_ = false; bool received_from_static_nodes_ = false; @@ -240,21 +253,31 @@ class AdnlPeerPairImpl : public AdnlPeerPair { td::Timestamp next_dht_query_at_ = td::Timestamp::never(); td::Timestamp next_db_update_at_ = td::Timestamp::never(); td::Timestamp retry_send_at_ = td::Timestamp::never(); + + td::Timestamp last_received_packet_ = td::Timestamp::never(); + td::Timestamp try_reinit_at_ = td::Timestamp::never(); + + bool has_reverse_addr_ = false; + td::Timestamp request_reverse_ping_after_ = td::Timestamp::now(); + bool request_reverse_ping_active_ = false; }; class AdnlPeerImpl : public AdnlPeer { public: - void receive_packet(AdnlNodeIdShort dst, td::actor::ActorId dst_actor, AdnlPacket packet) override; - void send_messages(AdnlNodeIdShort src, td::actor::ActorId src_actor, - std::vector messages) override; - void send_query(AdnlNodeIdShort src, td::actor::ActorId src_actor, std::string name, - td::Promise promise, td::Timestamp timeout, td::BufferSlice data) override; + void receive_packet(AdnlNodeIdShort dst, td::uint32 dst_mode, td::actor::ActorId dst_actor, + AdnlPacket packet) override; + void send_messages(AdnlNodeIdShort src, td::uint32 src_mode, td::actor::ActorId src_actor, + std::vector messages) override; + void send_query(AdnlNodeIdShort src, td::uint32 src_mode, td::actor::ActorId src_actor, std::string name, + td::Promise promise, td::Timestamp timeout, td::BufferSlice data, + td::uint32 flags) override; void del_local_id(AdnlNodeIdShort local_id) override; void update_id(AdnlNodeIdFull id) override; - void update_addr_list(AdnlNodeIdShort local_id, td::actor::ActorId local_actor, + void update_addr_list(AdnlNodeIdShort local_id, td::uint32 local_mode, td::actor::ActorId local_actor, AdnlAddressList addr_list) override; void update_dht_node(td::actor::ActorId dht_node) override; + void get_conn_ip_str(AdnlNodeIdShort l_id, td::Promise promise) override; //void check_signature(td::BufferSlice data, td::BufferSlice signature, td::Promise promise) override; AdnlPeerImpl(td::actor::ActorId network_manager, td::actor::ActorId peer_table, diff --git a/adnl/adnl-pong.cpp b/adnl/adnl-pong.cpp index e56023bb..434f0ef7 100644 --- a/adnl/adnl-pong.cpp +++ b/adnl/adnl-pong.cpp @@ -1,4 +1,4 @@ -/* +/* This file is part of TON Blockchain source code. TON Blockchain is free software; you can redistribute it and/or @@ -14,23 +14,23 @@ You should have received a copy of the GNU General Public License along with TON Blockchain. If not, see . - In addition, as a special exception, the copyright holders give permission - to link the code of portions of this program with the OpenSSL library. - You must obey the GNU General Public License in all respects for all - of the code used other than OpenSSL. If you modify file(s) with this - exception, you may extend this exception to your version of the file(s), - but you are not obligated to do so. If you do not wish to do so, delete this - exception statement from your version. If you delete this exception statement + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "td/actor/actor.h" #include "td/utils/buffer.h" #include "td/utils/port/IPAddress.h" #include "td/net/UdpServer.h" #include "td/utils/port/signals.h" -#include "td/utils/OptionsParser.h" +#include "td/utils/OptionParser.h" #include "td/utils/FileLog.h" #include "td/utils/port/path.h" #include "td/utils/port/user.h" @@ -41,6 +41,7 @@ #include "auto/tl/ton_api_json.h" #include "adnl/adnl.h" #include +#include "git.h" #if TD_DARWIN || TD_LINUX #include @@ -91,12 +92,15 @@ int main(int argc, char *argv[]) { std::string config = "/var/ton-work/etc/adnl-proxy.conf.json"; - td::OptionsParser p; + td::OptionParser p; p.set_description("adnl pinger"); p.add_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) { int v = VERBOSITY_NAME(FATAL) + (td::to_integer(arg)); SET_VERBOSITY_LEVEL(v); - return td::Status::OK(); + }); + p.add_option('V', "version", "shows adnl-pong build information", [&]() { + std::cout << "adnl-pong build information: [ Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate() << "]\n"; + std::exit(0); }); p.add_option('h', "help", "prints_help", [&]() { char b[10240]; @@ -104,7 +108,6 @@ int main(int argc, char *argv[]) { sb << p; std::cout << sb.as_cslice().c_str(); std::exit(2); - return td::Status::OK(); }); p.add_option('d', "daemonize", "set SIGHUP", [&]() { #if TD_DARWIN || TD_LINUX @@ -112,9 +115,8 @@ int main(int argc, char *argv[]) { setsid(); #endif td::set_signal_handler(td::SignalType::HangUp, force_rotate_logs).ensure(); - return td::Status::OK(); }); - p.add_option('l', "logname", "log to file", [&](td::Slice fname) { + p.add_checked_option('l', "logname", "log to file", [&](td::Slice fname) { auto F = std::make_unique(); TRY_STATUS(F->init(fname.str(), std::numeric_limits::max(), true)); logger_ = std::move(F); @@ -122,25 +124,26 @@ int main(int argc, char *argv[]) { return td::Status::OK(); }); td::uint32 threads = 7; - p.add_option('t', "threads", PSTRING() << "number of threads (default=" << threads << ")", [&](td::Slice fname) { - td::int32 v; - try { - v = std::stoi(fname.str()); - } catch (...) { - return td::Status::Error(ton::ErrorCode::error, "bad value for --threads: not a number"); - } - if (v < 1 || v > 256) { - return td::Status::Error(ton::ErrorCode::error, "bad value for --threads: should be in range [1..256]"); - } - threads = v; - return td::Status::OK(); - }); - p.add_option('u', "user", "change user", [&](td::Slice user) { return td::change_user(user); }); - p.add_option('k', "key", "private key", [&](td::Slice key) { + p.add_checked_option( + 't', "threads", PSTRING() << "number of threads (default=" << threads << ")", [&](td::Slice fname) { + td::int32 v; + try { + v = std::stoi(fname.str()); + } catch (...) { + return td::Status::Error(ton::ErrorCode::error, "bad value for --threads: not a number"); + } + if (v < 1 || v > 256) { + return td::Status::Error(ton::ErrorCode::error, "bad value for --threads: should be in range [1..256]"); + } + threads = v; + return td::Status::OK(); + }); + p.add_checked_option('u', "user", "change user", [&](td::Slice user) { return td::change_user(user.str()); }); + p.add_checked_option('k', "key", "private key", [&](td::Slice key) { TRY_RESULT_ASSIGN(pk, ton::PrivateKey::import(key)); return td::Status::OK(); }); - p.add_option('a', "addr", "ip:port of instance", [&](td::Slice key) { + p.add_checked_option('a', "addr", "ip:port of instance", [&](td::Slice key) { TRY_STATUS(addr.init_host_port(key.str())); return td::Status::OK(); }); @@ -170,7 +173,10 @@ int main(int argc, char *argv[]) { network_manager = ton::adnl::AdnlNetworkManager::create(static_cast(addr.get_port())); - td::actor::send_closure(network_manager, &ton::adnl::AdnlNetworkManager::add_self_addr, addr, 0); + ton::adnl::AdnlCategoryMask cat_mask; + cat_mask[0] = true; + td::actor::send_closure(network_manager, &ton::adnl::AdnlNetworkManager::add_self_addr, addr, std::move(cat_mask), + 0); auto tladdr = ton::create_tl_object(addr.get_ipv4(), addr.get_port()); auto addr_vec = std::vector>(); @@ -179,7 +185,8 @@ int main(int argc, char *argv[]) { std::move(addr_vec), ton::adnl::Adnl::adnl_start_time(), ton::adnl::Adnl::adnl_start_time(), 0, 2000000000); auto addrlist = ton::adnl::AdnlAddressList::create(tladdrlist).move_as_ok(); - td::actor::send_closure(adnl, &ton::adnl::Adnl::add_id, ton::adnl::AdnlNodeIdFull{pub}, std::move(addrlist)); + td::actor::send_closure(adnl, &ton::adnl::Adnl::add_id, ton::adnl::AdnlNodeIdFull{pub}, std::move(addrlist), + static_cast(0)); td::actor::send_closure(adnl, &ton::adnl::Adnl::subscribe, ton::adnl::AdnlNodeIdShort{pub.compute_short_id()}, ton::adnl::Adnl::int_to_bytestring(ton::ton_api::adnl_ping::ID), std::make_unique()); diff --git a/adnl/adnl-proxy-types.cpp b/adnl/adnl-proxy-types.cpp index 3ab3d6fb..8ca2def9 100644 --- a/adnl/adnl-proxy-types.cpp +++ b/adnl/adnl-proxy-types.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-proxy-types.hpp" #include "tl-utils/tl-utils.hpp" @@ -27,59 +27,84 @@ namespace ton { namespace adnl { +td::Result AdnlProxyNone::decrypt(td::BufferSlice packet) const { + if (packet.size() < 32) { + return td::Status::Error(ErrorCode::protoviolation, "bad signature"); + } + if (packet.as_slice().truncate(32) != id_.as_slice()) { + return td::Status::Error(ErrorCode::protoviolation, "bad proxy id"); + } + Packet p{}; + p.flags = 0; + p.ip = 0; + p.port = 0; + p.adnl_start_time = 0; + p.seqno = 0; + p.date = 0; + p.data = std::move(packet); + p.data.confirm_read(32); + return std::move(p); +} + td::BufferSlice AdnlProxyFast::encrypt(Packet packet) const { - auto date = static_cast(td::Clocks::system()); - auto signature = create_hash_tl_object( - packet.ip, packet.port, date, sha256_bits256(packet.data.as_slice()), shared_secret_); - - auto obj = create_serialize_tl_object(packet.ip, packet.port, date, signature); - td::BufferSlice res{32 + obj.size() + packet.data.size()}; - auto S = res.as_slice(); - S.copy_from(td::Bits256::zero().as_slice()); + if (!packet.date) { + packet.date = static_cast(td::Clocks::system()); + packet.flags |= 8; + } + auto obj = create_tl_object(id_, packet.flags, packet.ip, packet.port, + packet.adnl_start_time, packet.seqno, packet.date, + td::sha256_bits256(packet.data.as_slice())); + char data[64]; + td::MutableSlice S{data, 64}; + S.copy_from(get_tl_object_sha256(obj).as_slice()); S.remove_prefix(32); - S.copy_from(obj.as_slice()); - S.remove_prefix(obj.size()); - S.copy_from(packet.data.as_slice()); + S.copy_from(shared_secret_.as_slice()); - return res; + obj->signature_ = td::sha256_bits256(td::Slice(data, 64)); + + return serialize_tl_object(obj, false, std::move(packet.data)); } td::Result AdnlProxyFast::decrypt(td::BufferSlice packet) const { - if (packet.size() < 36) { - return td::Status::Error(ErrorCode::protoviolation, "too short packet"); + TRY_RESULT(obj, fetch_tl_prefix(packet, false)); + if (obj->proxy_id_ != id_) { + return td::Status::Error(ErrorCode::protoviolation, "bad proxy id"); } - td::Bits256 v; - v.as_slice().copy_from(packet.as_slice().truncate(32)); - if (!v.is_zero()) { - return td::Status::Error(ErrorCode::protoviolation, "non-zero DST"); - } - packet.confirm_read(32); + auto signature = std::move(obj->signature_); + obj->signature_ = td::sha256_bits256(packet.as_slice()); - TRY_RESULT(R, fetch_tl_prefix(packet, true)); + char data[64]; + td::MutableSlice S{data, 64}; + S.copy_from(get_tl_object_sha256(obj).as_slice()); + S.remove_prefix(32); + S.copy_from(shared_secret_.as_slice()); - if (R->date_ < td::Clocks::system() - 8) { - return td::Status::Error(ErrorCode::protoviolation, "too old date"); - } - - auto signature = create_hash_tl_object( - R->ip_, R->port_, R->date_, sha256_bits256(packet.as_slice()), shared_secret_); - if (signature != R->signature_) { + if (td::sha256_bits256(td::Slice(data, 64)) != signature) { return td::Status::Error(ErrorCode::protoviolation, "bad signature"); } - return Packet{static_cast(R->ip_), static_cast(R->port_), std::move(packet)}; + Packet p; + p.flags = obj->flags_; + p.ip = (p.flags & 1) ? obj->ip_ : 0; + p.port = (p.flags & 1) ? static_cast(obj->port_) : 0; + p.adnl_start_time = (p.flags & 2) ? obj->adnl_start_time_ : 0; + p.seqno = (p.flags & 4) ? obj->seqno_ : 0; + p.date = (p.flags & 8) ? obj->date_ : 0; + p.data = std::move(packet); + + return std::move(p); } td::Result> AdnlProxy::create(const ton_api::adnl_Proxy &proxy_type) { std::shared_ptr R; ton_api::downcast_call( const_cast(proxy_type), - td::overloaded([&](const ton_api::adnl_proxy_none &x) { R = std::make_shared(); }, + td::overloaded([&](const ton_api::adnl_proxy_none &x) { R = std::make_shared(x.id_); }, [&](const ton_api::adnl_proxy_fast &x) { - R = std::make_shared(x.shared_secret_.as_slice()); + R = std::make_shared(x.id_, x.shared_secret_.as_slice()); })); - return R; + return std::move(R); } } // namespace adnl diff --git a/adnl/adnl-proxy-types.h b/adnl/adnl-proxy-types.h index ef80d09e..f18d2dc0 100644 --- a/adnl/adnl-proxy-types.h +++ b/adnl/adnl-proxy-types.h @@ -14,11 +14,12 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "td/utils/buffer.h" +#include "td/utils/Status.h" #include "auto/tl/ton_api.h" namespace ton { @@ -28,14 +29,19 @@ namespace adnl { class AdnlProxy { public: struct Packet { + td::uint32 flags; td::uint32 ip; td::uint16 port; + td::int32 adnl_start_time; + td::int64 seqno; + td::int32 date{0}; td::BufferSlice data; }; virtual ~AdnlProxy() = default; virtual td::BufferSlice encrypt(Packet packet) const = 0; virtual td::Result decrypt(td::BufferSlice packet) const = 0; virtual tl_object_ptr tl() const = 0; + virtual const td::Bits256 &id() const = 0; static td::Result> create(const ton_api::adnl_Proxy &proxy_type); }; diff --git a/adnl/adnl-proxy-types.hpp b/adnl/adnl-proxy-types.hpp index 1b34cdc0..7b218288 100644 --- a/adnl/adnl-proxy-types.hpp +++ b/adnl/adnl-proxy-types.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -28,31 +28,42 @@ namespace adnl { class AdnlProxyNone : public AdnlProxy { public: - AdnlProxyNone() { + AdnlProxyNone(td::Bits256 id) : id_(id) { } td::BufferSlice encrypt(Packet packet) const override { - return std::move(packet.data); - } - td::Result decrypt(td::BufferSlice packet) const override { - return Packet{0, 0, std::move(packet)}; + td::BufferSlice d{packet.data.size() + 32}; + d.as_slice().copy_from(id_.as_slice()); + d.as_slice().remove_prefix(32).copy_from(packet.data.as_slice()); + return d; } + td::Result decrypt(td::BufferSlice packet) const override; tl_object_ptr tl() const override { - return create_tl_object(); + return create_tl_object(id_); } + const td::Bits256 &id() const override { + return id_; + } + + private: + td::Bits256 id_; }; class AdnlProxyFast : public AdnlProxy { public: - AdnlProxyFast(td::Slice shared_secret) - : shared_secret_(sha256_bits256(shared_secret)), shared_secret_raw_(shared_secret) { + AdnlProxyFast(td::Bits256 id, td::Slice shared_secret) + : id_(id), shared_secret_(sha256_bits256(shared_secret)), shared_secret_raw_(shared_secret) { } td::BufferSlice encrypt(Packet packet) const override; td::Result decrypt(td::BufferSlice packet) const override; tl_object_ptr tl() const override { - return create_tl_object(shared_secret_raw_.clone_as_buffer_slice()); + return create_tl_object(id_, shared_secret_raw_.clone_as_buffer_slice()); + } + const td::Bits256 &id() const override { + return id_; } private: + td::Bits256 id_; td::Bits256 shared_secret_; td::SharedSlice shared_secret_raw_; }; diff --git a/adnl/adnl-proxy.cpp b/adnl/adnl-proxy.cpp index ecfd17f1..81ec8dcd 100644 --- a/adnl/adnl-proxy.cpp +++ b/adnl/adnl-proxy.cpp @@ -1,4 +1,4 @@ -/* +/* This file is part of TON Blockchain source code. TON Blockchain is free software; you can redistribute it and/or @@ -14,33 +14,36 @@ You should have received a copy of the GNU General Public License along with TON Blockchain. If not, see . - In addition, as a special exception, the copyright holders give permission - to link the code of portions of this program with the OpenSSL library. - You must obey the GNU General Public License in all respects for all - of the code used other than OpenSSL. If you modify file(s) with this - exception, you may extend this exception to your version of the file(s), - but you are not obligated to do so. If you do not wish to do so, delete this - exception statement from your version. If you delete this exception statement + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "td/actor/actor.h" #include "td/utils/buffer.h" #include "td/utils/port/IPAddress.h" #include "td/net/UdpServer.h" #include "td/utils/port/signals.h" -#include "td/utils/OptionsParser.h" +#include "td/utils/OptionParser.h" #include "td/utils/FileLog.h" #include "td/utils/port/path.h" #include "td/utils/port/user.h" #include "td/utils/filesystem.h" +#include "td/utils/overloaded.h" #include "common/checksum.h" #include "common/errorcode.h" #include "tl-utils/tl-utils.hpp" #include "auto/tl/ton_api_json.h" #include "adnl-proxy-types.h" +#include "adnl-received-mask.h" #include +#include "git.h" #if TD_DARWIN || TD_LINUX #include @@ -50,12 +53,19 @@ namespace ton { namespace adnl { +namespace { +td::int32 start_time() { + static td::int32 t = static_cast(td::Clocks::system()); + return t; +} +} // namespace + class Receiver : public td::actor::Actor { public: void start_up() override; - void receive_common(td::BufferSlice data); - void receive_from_client(td::BufferSlice data); - void receive_to_client(td::BufferSlice data); + void receive_common(td::IPAddress addr, td::BufferSlice data); + void receive_from_client(td::IPAddress addr, td::BufferSlice data); + void receive_to_client(td::IPAddress addr, td::BufferSlice data); Receiver(td::uint16 in_port, td::uint16 out_port, std::shared_ptr proxy, td::IPAddress client_addr) : in_port_(in_port), out_port_(out_port), proxy_(std::move(proxy)), addr_(client_addr) { @@ -68,6 +78,10 @@ class Receiver : public td::actor::Actor { td::IPAddress addr_; td::actor::ActorOwn out_udp_server_; td::actor::ActorOwn in_udp_server_; + + td::int32 client_start_time_{0}; + td::uint64 out_seqno_{0}; + AdnlReceivedMaskVersion received_; }; void Receiver::start_up() { @@ -81,15 +95,18 @@ void Receiver::start_up() { const td::uint32 mode_; void on_udp_message(td::UdpMessage udp_message) override { if (udp_message.error.is_error()) { - LOG(DEBUG) << udp_message.error; + LOG(INFO) << "receifed udp message with error: " << udp_message.error; return; } if (mode_ == 0) { - td::actor::send_closure_later(manager_, &Receiver::receive_common, std::move(udp_message.data)); + td::actor::send_closure_later(manager_, &Receiver::receive_common, udp_message.address, + std::move(udp_message.data)); } else if (mode_ == 1) { - td::actor::send_closure_later(manager_, &Receiver::receive_from_client, std::move(udp_message.data)); + td::actor::send_closure_later(manager_, &Receiver::receive_from_client, udp_message.address, + std::move(udp_message.data)); } else { - td::actor::send_closure_later(manager_, &Receiver::receive_to_client, std::move(udp_message.data)); + td::actor::send_closure_later(manager_, &Receiver::receive_to_client, udp_message.address, + std::move(udp_message.data)); } } }; @@ -108,46 +125,151 @@ void Receiver::start_up() { } } -void Receiver::receive_common(td::BufferSlice data) { +void Receiver::receive_common(td::IPAddress addr, td::BufferSlice data) { if (data.size() <= 32) { + LOG(INFO) << "dropping too short packet: size=" << data.size(); return; } - td::Bits256 id; - id.as_slice().copy_from(data.as_slice().truncate(32)); - - if (id.is_zero()) { - receive_from_client(std::move(data)); + if (proxy_->id().as_slice() == data.as_slice().truncate(32)) { + receive_from_client(addr, std::move(data)); } else { - receive_to_client(std::move(data)); + receive_to_client(addr, std::move(data)); } } -void Receiver::receive_from_client(td::BufferSlice data) { +void Receiver::receive_from_client(td::IPAddress addr, td::BufferSlice data) { auto F = proxy_->decrypt(std::move(data)); if (F.is_error()) { + LOG(INFO) << "proxy: failed to decrypt message from client: " << F.move_as_error(); return; } auto f = F.move_as_ok(); + if (f.flags & (1 << 16)) { + LOG(INFO) << "proxy: dropping message from client: flag 16 is set"; + return; + } + + if (f.date) { + if (f.date + 60.0 < td::Clocks::system() || f.date - 60.0 > td::Clocks::system()) { + LOG(INFO) << "proxy: dropping message from client: date mismatch"; + return; + } + } + + if ((f.flags & 6) == 6) { + if (received_.packet_is_delivered(f.adnl_start_time, f.seqno)) { + LOG(INFO) << "proxy: dropping message from client: duplicate packet (or old seqno/start_time)"; + return; + } + received_.deliver_packet(f.adnl_start_time, f.seqno); + } + + if (f.flags & (1 << 17)) { + auto F = fetch_tl_object(std::move(f.data), true); + if (F.is_error()) { + LOG(INFO) << this << ": dropping proxy packet: bad control packet: " << F.move_as_error(); + return; + } + ton_api::downcast_call(*F.move_as_ok().get(), + td::overloaded( + [&](const ton_api::adnl_proxyControlPacketPing &f) { + auto data = create_serialize_tl_object(f.id_); + AdnlProxy::Packet p; + p.flags = 6 | (1 << 16) | (1 << 17); + if (addr.is_valid() && addr.is_ipv4()) { + p.flags |= 1; + p.ip = addr.get_ipv4(); + p.port = static_cast(addr.get_port()); + } else { + p.ip = 0; + p.port = 0; + } + p.data = std::move(data); + p.adnl_start_time = start_time(); + p.seqno = out_seqno_; + + auto enc = proxy_->encrypt(std::move(p)); + + td::UdpMessage M; + M.address = addr; + M.data = std::move(enc); + + td::actor::send_closure( + in_udp_server_.empty() ? out_udp_server_.get() : in_udp_server_.get(), + &td::UdpServer::send, std::move(M)); + }, + [&](const ton_api::adnl_proxyControlPacketPong &f) {}, + [&](const ton_api::adnl_proxyControlPacketRegister &f) { + if (f.ip_ == 0 && f.port_ == 0) { + if (addr.is_valid() && addr.is_ipv4()) { + addr_ = addr; + } + } else { + td::IPAddress a; + auto S = a.init_host_port(td::IPAddress::ipv4_to_str(f.ip_), f.port_); + if (S.is_ok()) { + addr_ = a; + } else { + LOG(INFO) << "failed to init remote addr: " << S.move_as_error(); + } + } + })); + return; + } + + if (!(f.flags & 1)) { + LOG(INFO) << this << ": dropping proxy packet: no destination"; + return; + } td::IPAddress a; if (a.init_ipv4_port(td::IPAddress::ipv4_to_str(f.ip), f.port).is_error()) { + LOG(INFO) << this << ": dropping proxy packet: invalid destination"; + return; + } + if (!a.is_valid()) { + LOG(INFO) << this << ": dropping proxy packet: invalid destination"; return; } td::UdpMessage M; M.address = a; M.data = std::move(f.data); + LOG(DEBUG) << this << ": proxying DOWN packet of length " << M.data.size() << " to " << a; td::actor::send_closure(out_udp_server_.empty() ? in_udp_server_.get() : out_udp_server_.get(), &td::UdpServer::send, std::move(M)); } -void Receiver::receive_to_client(td::BufferSlice data) { +void Receiver::receive_to_client(td::IPAddress addr, td::BufferSlice data) { LOG(DEBUG) << "proxying to " << addr_; + if (!addr_.is_valid() || !addr_.is_ipv4() || !addr_.get_ipv4()) { + LOG(INFO) << this << ": dropping external packet: client not inited"; + return; + } + + AdnlProxy::Packet p; + p.flags = (1 << 16); + if (addr.is_valid() && addr.is_ipv4()) { + p.flags |= 1; + p.ip = addr.get_ipv4(); + p.port = static_cast(addr.get_port()); + } else { + p.ip = 0; + p.port = 0; + } + p.flags |= 2; + p.adnl_start_time = start_time(); + p.flags |= 4; + p.seqno = ++out_seqno_; + p.data = std::move(data); + + LOG(DEBUG) << this << ": proxying UP packet of length " << p.data.size() << " to " << addr_; + td::UdpMessage M; M.address = addr_; - M.data = std::move(data); + M.data = proxy_->encrypt(std::move(p)); td::actor::send_closure(in_udp_server_.empty() ? out_udp_server_.get() : in_udp_server_.get(), &td::UdpServer::send, std::move(M)); @@ -175,12 +297,15 @@ int main(int argc, char *argv[]) { std::string config = "/var/ton-work/etc/adnl-proxy.conf.json"; - td::OptionsParser p; + td::OptionParser p; p.set_description("validator or full node for TON network"); p.add_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) { int v = VERBOSITY_NAME(FATAL) + (td::to_integer(arg)); SET_VERBOSITY_LEVEL(v); - return td::Status::OK(); + }); + p.add_option('V', "version", "shows adnl-proxy build information", [&]() { + std::cout << "adnl-proxy build information: [ Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate() << "]\n"; + std::exit(0); }); p.add_option('h', "help", "prints_help", [&]() { char b[10240]; @@ -188,42 +313,38 @@ int main(int argc, char *argv[]) { sb << p; std::cout << sb.as_cslice().c_str(); std::exit(2); - return td::Status::OK(); - }); - p.add_option('c', "config", "config file", [&](td::Slice arg) { - config = arg.str(); - return td::Status::OK(); }); + p.add_option('c', "config", "config file", [&](td::Slice arg) { config = arg.str(); }); p.add_option('d', "daemonize", "set SIGHUP", [&]() { #if TD_DARWIN || TD_LINUX close(0); setsid(); #endif td::set_signal_handler(td::SignalType::HangUp, force_rotate_logs).ensure(); - return td::Status::OK(); }); - p.add_option('l', "logname", "log to file", [&](td::Slice fname) { + p.add_checked_option('l', "logname", "log to file", [&](td::Slice fname) { auto F = std::make_unique(); - TRY_STATUS(F->init(fname.str(), std::numeric_limits::max(), true)); + TRY_STATUS(F->init(fname.str(), std::numeric_limits::max(), true)); logger_ = std::move(F); td::log_interface = logger_.get(); return td::Status::OK(); }); td::uint32 threads = 7; - p.add_option('t', "threads", PSTRING() << "number of threads (default=" << threads << ")", [&](td::Slice fname) { - td::int32 v; - try { - v = std::stoi(fname.str()); - } catch (...) { - return td::Status::Error(ton::ErrorCode::error, "bad value for --threads: not a number"); - } - if (v < 1 || v > 256) { - return td::Status::Error(ton::ErrorCode::error, "bad value for --threads: should be in range [1..256]"); - } - threads = v; - return td::Status::OK(); - }); - p.add_option('u', "user", "change user", [&](td::Slice user) { return td::change_user(user); }); + p.add_checked_option( + 't', "threads", PSTRING() << "number of threads (default=" << threads << ")", [&](td::Slice fname) { + td::int32 v; + try { + v = std::stoi(fname.str()); + } catch (...) { + return td::Status::Error(ton::ErrorCode::error, "bad value for --threads: not a number"); + } + if (v < 1 || v > 256) { + return td::Status::Error(ton::ErrorCode::error, "bad value for --threads: should be in range [1..256]"); + } + threads = v; + return td::Status::OK(); + }); + p.add_checked_option('u', "user", "change user", [&](td::Slice user) { return td::change_user(user.str()); }); p.run(argc, argv).ensure(); @@ -248,7 +369,9 @@ int main(int argc, char *argv[]) { } TRY_RESULT(proxy, ton::adnl::AdnlProxy::create(*y->proxy_type_.get())); td::IPAddress a; - a.init_ipv4_port(td::IPAddress::ipv4_to_str(y->dst_ip_), static_cast(y->dst_port_)).ensure(); + if (y->dst_ip_ || y->dst_port_) { + a.init_ipv4_port(td::IPAddress::ipv4_to_str(y->dst_ip_), static_cast(y->dst_port_)).ensure(); + } scheduler.run_in_context([&] { x.push_back(td::actor::create_actor("adnl-proxy", in_port, out_port, std::move(proxy), a)); diff --git a/adnl/adnl-query.cpp b/adnl/adnl-query.cpp index d2f0f52d..e098c134 100644 --- a/adnl/adnl-query.cpp +++ b/adnl/adnl-query.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-query.h" #include "common/errorcode.h" @@ -25,13 +25,16 @@ namespace ton { namespace adnl { void AdnlQuery::alarm() { - promise_.set_error(td::Status::Error(ErrorCode::timeout, "adnl query timeout")); - stop(); + set_error(td::Status::Error(ErrorCode::timeout, "adnl query timeout")); } void AdnlQuery::result(td::BufferSlice data) { promise_.set_value(std::move(data)); stop(); } +void AdnlQuery::set_error(td::Status error) { + promise_.set_error(std::move(error)); + stop(); +} AdnlQueryId AdnlQuery::random_query_id() { AdnlQueryId q_id; diff --git a/adnl/adnl-query.h b/adnl/adnl-query.h index 3c0ebf06..3db8c754 100644 --- a/adnl/adnl-query.h +++ b/adnl/adnl-query.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -48,6 +48,7 @@ class AdnlQuery : public td::actor::Actor { } void alarm() override; void result(td::BufferSlice data); + void set_error(td::Status error); void start_up() override { alarm_timestamp() = timeout_; } diff --git a/adnl/adnl-received-mask.h b/adnl/adnl-received-mask.h new file mode 100644 index 00000000..0822ae3e --- /dev/null +++ b/adnl/adnl-received-mask.h @@ -0,0 +1,102 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ +#pragma once + +#include "td/utils/int_types.h" +#include "td/utils/logging.h" + +namespace ton { + +namespace adnl { + +class AdnlReceivedMask { + public: + void reset() { + seqno_ = 0; + mask_ = 0; + } + bool packet_is_delivered(td::int64 seqno) const { + if (seqno <= 0) { + return false; + } + if (seqno + 64 <= seqno_) { + return true; + } + if (seqno > seqno_) { + return false; + } + return mask_ & (1ull << (seqno_ - seqno)); + } + void deliver_packet(td::int64 seqno) { + CHECK(!packet_is_delivered(seqno)); + + CHECK(seqno > 0); + if (seqno <= seqno_) { + mask_ |= (1ull << (seqno_ - seqno)); + } else { + auto old = seqno_; + seqno_ = seqno; + if (seqno_ - old >= 64) { + mask_ = 1; + } else { + mask_ = mask_ << (seqno_ - old); + mask_ |= 1; + } + } + } + + private: + td::int64 seqno_{0}; + td::uint64 mask_{0}; +}; + +class AdnlReceivedMaskVersion { + public: + bool packet_is_delivered(td::int32 utime, td::uint64 seqno) { + if (utime < utime_) { + return true; + } else if (utime == utime_) { + return mask_.packet_is_delivered(seqno); + } else { + return false; + } + } + void deliver_packet(td::int32 utime, td::uint64 seqno) { + CHECK(utime >= utime_); + if (utime == utime_) { + mask_.deliver_packet(seqno); + } else { + utime_ = utime; + mask_.reset(); + mask_.deliver_packet(seqno); + } + } + void reset() { + mask_.reset(); + utime_ = 0; + } + + private: + AdnlReceivedMask mask_; + td::int32 utime_{0}; +}; + +} // namespace adnl + +} // namespace ton diff --git a/adnl/adnl-static-nodes.cpp b/adnl/adnl-static-nodes.cpp index edb7efb3..251715be 100644 --- a/adnl/adnl-static-nodes.cpp +++ b/adnl/adnl-static-nodes.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-static-nodes.h" #include "adnl-static-nodes.hpp" diff --git a/adnl/adnl-static-nodes.h b/adnl/adnl-static-nodes.h index 3ca06f15..91e3bed0 100644 --- a/adnl/adnl-static-nodes.h +++ b/adnl/adnl-static-nodes.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-static-nodes.hpp b/adnl/adnl-static-nodes.hpp index 51ecbb8e..166be5aa 100644 --- a/adnl/adnl-static-nodes.hpp +++ b/adnl/adnl-static-nodes.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-test-loopback-implementation.cpp b/adnl/adnl-test-loopback-implementation.cpp index 1ba0e920..c5ef7530 100644 --- a/adnl/adnl-test-loopback-implementation.cpp +++ b/adnl/adnl-test-loopback-implementation.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-test-loopback-implementation.h" diff --git a/adnl/adnl-test-loopback-implementation.h b/adnl/adnl-test-loopback-implementation.h index 40701d52..4773f053 100644 --- a/adnl/adnl-test-loopback-implementation.h +++ b/adnl/adnl-test-loopback-implementation.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -34,9 +34,10 @@ class TestLoopbackNetworkManager : public ton::adnl::AdnlNetworkManager { callback_ = std::move(callback); } - void add_self_addr(td::IPAddress addr, td::uint32 priority) override { + void add_self_addr(td::IPAddress addr, AdnlCategoryMask cat_mask, td::uint32 priority) override { } - void add_proxy_addr(td::IPAddress addr, std::shared_ptr proxy, td::uint32 priority) override { + void add_proxy_addr(td::IPAddress addr, td::uint16 local_port, std::shared_ptr proxy, + AdnlCategoryMask cat_mask, td::uint32 priority) override { } void send_udp_packet(ton::adnl::AdnlNodeIdShort src_id, ton::adnl::AdnlNodeIdShort dst_id, td::IPAddress dst_addr, td::uint32 priority, td::BufferSlice data) override { @@ -48,7 +49,9 @@ class TestLoopbackNetworkManager : public ton::adnl::AdnlNetworkManager { return; } CHECK(callback_); - callback_->receive_packet(dst_addr, std::move(data)); + AdnlCategoryMask m; + m[0] = true; + callback_->receive_packet(dst_addr, std::move(m), std::move(data)); } void add_node_id(AdnlNodeIdShort id, bool allow_send, bool allow_receive) { @@ -68,6 +71,8 @@ class TestLoopbackNetworkManager : public ton::adnl::AdnlNetworkManager { CHECK(p >= 0 && p <= 1); loss_probability_ = p; } + void set_local_id_category(AdnlNodeIdShort id, td::uint8 cat) override { + } TestLoopbackNetworkManager() { } diff --git a/adnl/adnl-tunnel.cpp b/adnl/adnl-tunnel.cpp new file mode 100644 index 00000000..360196f4 --- /dev/null +++ b/adnl/adnl-tunnel.cpp @@ -0,0 +1,115 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ +#include "adnl-tunnel.h" +#include "adnl-peer-table.h" + +namespace ton { + +namespace adnl { + +void AdnlInboundTunnelEndpoint::receive_packet(AdnlNodeIdShort src, td::IPAddress src_addr, td::BufferSlice datagram) { + receive_packet_cont(src, src_addr, std::move(datagram), 0); +} + +void AdnlInboundTunnelEndpoint::receive_packet_cont(AdnlNodeIdShort src, td::IPAddress src_addr, + td::BufferSlice datagram, size_t idx) { + if (datagram.size() <= 32) { + VLOG(ADNL_INFO) << "dropping too short datagram"; + return; + } + if (datagram.as_slice().truncate(32) != decrypt_via_[idx].as_slice()) { + VLOG(ADNL_INFO) << "invalid tunnel midpoint"; + return; + } + datagram.confirm_read(32); + + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), src, src_addr, idx](td::Result R) { + if (R.is_error()) { + VLOG(ADNL_INFO) << "dropping tunnel packet: failed to decrypt: " << R.move_as_error(); + return; + } else { + td::actor::send_closure(SelfId, &AdnlInboundTunnelEndpoint::decrypted_packet, src, src_addr, R.move_as_ok(), idx); + } + }); + td::actor::send_closure(keyring_, &keyring::Keyring::decrypt_message, decrypt_via_[idx], std::move(datagram), + std::move(P)); +} + +void AdnlInboundTunnelEndpoint::decrypted_packet(AdnlNodeIdShort src, td::IPAddress src_addr, td::BufferSlice data, + size_t idx) { + if (idx == decrypt_via_.size() - 1) { + td::actor::send_closure(adnl_, &AdnlPeerTable::receive_packet, src_addr, std::move(data)); + return; + } + auto F = fetch_tl_object(std::move(data), true); + if (F.is_error()) { + VLOG(ADNL_INFO) << "dropping tunnel packet: failed to fetch: " << F.move_as_error(); + return; + } + auto packet = F.move_as_ok(); + + td::IPAddress addr; + if (packet->flags_ & 1) { + addr.init_host_port(td::IPAddress::ipv4_to_str(packet->from_ip_), packet->from_port_).ignore(); + } + + if (packet->flags_ & 2) { + receive_packet_cont(src, addr, std::move(packet->message_), idx + 1); + } +} + +void AdnlInboundTunnelMidpoint::start_up() { + encrypt_key_hash_ = encrypt_via_.compute_short_id(); + auto R = encrypt_via_.create_encryptor(); + if (R.is_error()) { + return; + } + encryptor_ = R.move_as_ok(); +} + +void AdnlInboundTunnelMidpoint::receive_packet(AdnlNodeIdShort src, td::IPAddress src_addr, td::BufferSlice datagram) { + if (!encryptor_) { + return; + } + auto obj = create_tl_object(); + obj->flags_ = 2; + obj->message_ = std::move(datagram); + if (src_addr.is_valid() && src_addr.is_ipv4()) { + obj->flags_ |= 1; + obj->from_ip_ = src_addr.get_ipv4(); + obj->from_port_ = src_addr.get_port(); + } + auto packet = serialize_tl_object(std::move(obj), true); + auto dataR = encryptor_->encrypt(packet.as_slice()); + if (dataR.is_error()) { + return; + } + auto data = dataR.move_as_ok(); + td::BufferSlice enc{data.size() + 32}; + auto S = enc.as_slice(); + S.copy_from(encrypt_key_hash_.as_slice()); + S.remove_prefix(32); + S.copy_from(data.as_slice()); + + td::actor::send_closure(adnl_, &Adnl::send_message_ex, proxy_as_, proxy_to_, std::move(enc), + Adnl::SendFlags::direct_only); +} + +} // namespace adnl +} // namespace ton diff --git a/adnl/adnl-tunnel.h b/adnl/adnl-tunnel.h new file mode 100644 index 00000000..d7f6a66f --- /dev/null +++ b/adnl/adnl-tunnel.h @@ -0,0 +1,91 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ +#pragma once + +#include "adnl.h" +#include "adnl-peer-table.h" +#include "keys/encryptor.h" + +#include + +namespace ton { + +namespace adnl { + +class AdnlInboundTunnelPoint : public AdnlTunnel { + public: + virtual ~AdnlInboundTunnelPoint() = default; + virtual void receive_packet(AdnlNodeIdShort src, td::IPAddress src_addr, td::BufferSlice datagram) = 0; +}; + +class AdnlInboundTunnelEndpoint : public AdnlInboundTunnelPoint { + public: + AdnlInboundTunnelEndpoint(PublicKeyHash pubkey_hash, std::vector decrypt_via, AdnlNodeIdShort proxy_to, + td::actor::ActorId keyring, td::actor::ActorId adnl) + : pubkey_hash_(std::move(pubkey_hash)) + , decrypt_via_(std::move(decrypt_via)) + , proxy_to_(std::move(proxy_to)) + , keyring_(std::move(keyring)) + , adnl_(std::move(adnl)) { + } + + void receive_packet(AdnlNodeIdShort src, td::IPAddress src_addr, td::BufferSlice datagram) override; + void receive_packet_cont(AdnlNodeIdShort src, td::IPAddress src_addr, td::BufferSlice datagram, size_t idx); + void decrypted_packet(AdnlNodeIdShort src, td::IPAddress src_addr, td::BufferSlice data, size_t idx); + + private: + PublicKeyHash pubkey_hash_; + std::vector decrypt_via_; + AdnlNodeIdShort proxy_to_; + td::actor::ActorId keyring_; + td::actor::ActorId adnl_; +}; + +class AdnlInboundTunnelMidpoint : public AdnlInboundTunnelPoint { + public: + AdnlInboundTunnelMidpoint(ton::PublicKey encrypt_via, AdnlNodeIdShort proxy_to, AdnlNodeIdShort proxy_as, + td::actor::ActorId keyring, td::actor::ActorId adnl) + : encrypt_via_(std::move(encrypt_via)), proxy_to_(proxy_to), proxy_as_(proxy_as), keyring_(keyring), adnl_(adnl) { + } + void start_up() override; + void receive_packet(AdnlNodeIdShort src, td::IPAddress src_addr, td::BufferSlice datagram) override; + + private: + ton::PublicKeyHash encrypt_key_hash_; + ton::PublicKey encrypt_via_; + std::unique_ptr encryptor_; + AdnlNodeIdShort proxy_to_; + AdnlNodeIdShort proxy_as_; + td::actor::ActorId keyring_; + td::actor::ActorId adnl_; +}; + +class AdnlProxyNode : public td::actor::Actor { + public: + void receive_message(AdnlNodeIdShort src, AdnlNodeIdShort dst, td::BufferSlice data); + void receive_query(AdnlNodeIdShort src, AdnlNodeIdShort dst, td::BufferSlice data, + td::Promise promise); + + private: + std::map> mid_; +}; + +} // namespace adnl + +} // namespace ton diff --git a/adnl/adnl.h b/adnl/adnl.h index 30790aae..a1c39d5e 100644 --- a/adnl/adnl.h +++ b/adnl/adnl.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -34,6 +34,8 @@ class Dht; namespace adnl { +enum class AdnlLocalIdMode : td::uint32 { direct_only = 1, drop_from_net = 2 }; + class AdnlNetworkManager; class AdnlExtServer : public td::actor::Actor { @@ -54,8 +56,11 @@ class AdnlSenderInterface : public td::actor::Actor { virtual void send_query_ex(AdnlNodeIdShort src, AdnlNodeIdShort dst, std::string name, td::Promise promise, td::Timestamp timeout, td::BufferSlice data, td::uint64 max_answer_size) = 0; + virtual void get_conn_ip_str(AdnlNodeIdShort l_id, AdnlNodeIdShort p_id, td::Promise promise) = 0; }; +class AdnlTunnel : public td::actor::Actor {}; + class Adnl : public AdnlSenderInterface { public: class Callback { @@ -73,6 +78,11 @@ class Adnl : public AdnlSenderInterface { return 1024 * 8; } + struct SendFlags { + enum Flags : td::uint32 { direct_only = 1 }; + }; + virtual void send_message_ex(AdnlNodeIdShort src, AdnlNodeIdShort dst, td::BufferSlice data, td::uint32 flags) = 0; + // adds node to peer table // used mostly from DHT to avoid loops virtual void add_peer(AdnlNodeIdShort local_id, AdnlNodeIdFull id, AdnlAddressList addr_list) = 0; @@ -81,9 +91,14 @@ class Adnl : public AdnlSenderInterface { virtual void add_static_nodes_from_config(AdnlNodesList nodes) = 0; // adds local id. After that you can send/receive messages from/to this id - virtual void add_id(AdnlNodeIdFull id, AdnlAddressList addr_list) = 0; + void add_id(AdnlNodeIdFull id, AdnlAddressList addr_list, td::uint8 cat) { + add_id_ex(std::move(id), std::move(addr_list), cat, 0); + } + virtual void add_id_ex(AdnlNodeIdFull id, AdnlAddressList addr_list, td::uint8 cat, td::uint32 mode) = 0; virtual void del_id(AdnlNodeIdShort id, td::Promise promise) = 0; + virtual void check_id_exists(AdnlNodeIdShort id, td::Promise promise) = 0; + // subscribe to (some) messages(+queries) to this local id virtual void subscribe(AdnlNodeIdShort dst, std::string prefix, std::unique_ptr callback) = 0; virtual void unsubscribe(AdnlNodeIdShort dst, std::string prefix) = 0; @@ -103,6 +118,8 @@ class Adnl : public AdnlSenderInterface { virtual void create_ext_server(std::vector ids, std::vector ports, td::Promise> promise) = 0; + virtual void create_tunnel(AdnlNodeIdShort dst, td::uint32 size, + td::Promise, AdnlAddress>> promise) = 0; static td::actor::ActorOwn create(std::string db, td::actor::ActorId keyring); diff --git a/adnl/test/adnl-test-ping.cpp b/adnl/test/adnl-test-ping.cpp index 593f1226..289aa043 100644 --- a/adnl/test/adnl-test-ping.cpp +++ b/adnl/test/adnl-test-ping.cpp @@ -23,7 +23,7 @@ exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl/adnl-network-manager.h" #include "adnl/adnl-peer-table.h" @@ -31,7 +31,7 @@ #include "keys/encryptor.h" #include "td/utils/Time.h" #include "td/utils/format.h" -#include "td/utils/OptionsParser.h" +#include "td/utils/OptionParser.h" #include #include @@ -168,7 +168,7 @@ td::Result get_uint256(std::string str) { int main(int argc, char *argv[]) { td::actor::ActorOwn x; - td::OptionsParser p; + td::OptionParser p; p.set_description("test basic adnl functionality"); p.add_option('h', "help", "prints_help", [&]() { char b[10240]; diff --git a/adnl/utils.cpp b/adnl/utils.cpp index 9fe93bb1..cb05f396 100644 --- a/adnl/utils.cpp +++ b/adnl/utils.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "utils.hpp" #include "tl/tl_object_store.h" diff --git a/adnl/utils.hpp b/adnl/utils.hpp index e0008260..50aec2ef 100644 --- a/adnl/utils.hpp +++ b/adnl/utils.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/assembly/android/build-android-tonlib.sh b/assembly/android/build-android-tonlib.sh new file mode 100644 index 00000000..e470f602 --- /dev/null +++ b/assembly/android/build-android-tonlib.sh @@ -0,0 +1,55 @@ +with_artifacts=false + +while getopts 'a' flag; do + case "${flag}" in + a) with_artifacts=true ;; + *) break + ;; + esac +done + +if [ ! -d android-ndk-r25b ]; then + rm android-ndk-r25b-linux.zip + wget -q https://dl.google.com/android/repository/android-ndk-r25b-linux.zip + unzip -q android-ndk-r25b-linux.zip + test $? -eq 0 || { echo "Can't unzip android-ndk-r25b-linux.zip"; exit 1; } + echo Android NDK extracted +else + echo Using extracted Android NDK +fi + +export JAVA_AWT_LIBRARY=NotNeeded +export JAVA_JVM_LIBRARY=NotNeeded +export JAVA_INCLUDE_PATH=${JAVA_HOME}/include +export JAVA_AWT_INCLUDE_PATH=${JAVA_HOME}/include +export JAVA_INCLUDE_PATH2=${JAVA_HOME}/include/linux + +export ANDROID_NDK_ROOT=$(pwd)/android-ndk-r25b +export NDK_PLATFORM="android-21" +export ANDROID_PLATFORM="android-21" +export OPENSSL_DIR=$(pwd)/example/android/third_party/crypto + +rm -rf example/android/src/drinkless/org/ton/TonApi.java +cd example/android/ + +rm CMakeCache.txt .ninja_* +cmake -GNinja -DTON_ONLY_TONLIB=ON . + +test $? -eq 0 || { echo "Can't configure TON"; exit 1; } + +ninja prepare_cross_compiling + +test $? -eq 0 || { echo "Can't compile prepare_cross_compiling"; exit 1; } + +rm CMakeCache.txt .ninja_* + +. ./build-all.sh + +find . -name "*.debug" -type f -delete + +if [ "$with_artifacts" = true ]; then + cd ../.. + mkdir -p artifacts/tonlib-android-jni + cp example/android/src/drinkless/org/ton/TonApi.java artifacts/tonlib-android-jni/ + cp -R example/android/libs/* artifacts/tonlib-android-jni/ +fi diff --git a/assembly/cicd/jenkins/test-builds.groovy b/assembly/cicd/jenkins/test-builds.groovy new file mode 100644 index 00000000..a959d75a --- /dev/null +++ b/assembly/cicd/jenkins/test-builds.groovy @@ -0,0 +1,236 @@ +pipeline { + agent none + stages { + stage('Run Builds') { + parallel { + stage('Ubuntu 20.04 x86-64 (shared)') { + agent { + label 'Ubuntu_x86-64' + } + steps { + timeout(time: 180, unit: 'MINUTES') { + sh ''' + cp assembly/native/build-ubuntu-shared.sh . + chmod +x build-ubuntu-shared.sh + ./build-ubuntu-shared.sh -t -a + ''' + sh ''' + cd artifacts + zip -9r ton-x86_64-linux-shared ./* + ''' + archiveArtifacts artifacts: 'artifacts/ton-x86_64-linux-shared.zip' + } + } + } + stage('Ubuntu 20.04 x86-64 (portable)') { + agent { + label 'Ubuntu_x86-64' + } + steps { + timeout(time: 180, unit: 'MINUTES') { + sh ''' + cp assembly/nix/build-linux-x86-64-nix.sh . + chmod +x build-linux-x86-64-nix.sh + ./build-linux-x86-64-nix.sh -t + ''' + sh ''' + cd artifacts + zip -9r ton-x86-64-linux-portable ./* + ''' + archiveArtifacts artifacts: 'artifacts/ton-x86-64-linux-portable.zip' + } + } + } + stage('Ubuntu 20.04 aarch64 (shared)') { + agent { + label 'Ubuntu_arm64' + } + steps { + timeout(time: 180, unit: 'MINUTES') { + sh ''' + cp assembly/native/build-ubuntu-shared.sh . + chmod +x build-ubuntu-shared.sh + ./build-ubuntu-shared.sh -t -a + ''' + sh ''' + cd artifacts + zip -9r ton-arm64-linux-shared ./* + ''' + archiveArtifacts artifacts: 'artifacts/ton-arm64-linux-shared.zip' + } + } + } + stage('Ubuntu 20.04 aarch64 (portable)') { + agent { + label 'Ubuntu_arm64' + } + steps { + timeout(time: 180, unit: 'MINUTES') { + sh ''' + cp assembly/nix/build-linux-arm64-nix.sh . + chmod +x build-linux-arm64-nix.sh + ./build-linux-arm64-nix.sh -t + ''' + sh ''' + cd artifacts + zip -9r ton-arm64-linux-portable ./* + ''' + archiveArtifacts artifacts: 'artifacts/ton-arm64-linux-portable.zip' + } + } + } + stage('macOS 12.7 x86-64 (shared)') { + agent { + label 'macOS_12.7_x86-64' + } + steps { + timeout(time: 180, unit: 'MINUTES') { + sh ''' + cp assembly/native/build-macos-shared.sh . + chmod +x build-macos-shared.sh + ./build-macos-shared.sh -t -a + ''' + sh ''' + cd artifacts + zip -9r ton-x86-64-macos-shared ./* + ''' + archiveArtifacts artifacts: 'artifacts/ton-x86-64-macos-shared.zip' + } + } + } + stage('macOS 12.7 x86-64 (portable)') { + agent { + label 'macOS_12.7_x86-64' + } + steps { + timeout(time: 180, unit: 'MINUTES') { + sh ''' + cp assembly/nix/build-macos-nix.sh . + chmod +x build-macos-nix.sh + ./build-macos-nix.sh -t + ''' + sh ''' + cd artifacts + zip -9r ton-x86-64-macos-portable ./* + ''' + archiveArtifacts artifacts: 'artifacts/ton-x86-64-macos-portable.zip' + } + } + } + stage('macOS 12.6 aarch64 (shared)') { + agent { + label 'macOS_12.6-arm64-m1' + } + steps { + timeout(time: 180, unit: 'MINUTES') { + sh ''' + cp assembly/native/build-macos-shared.sh . + chmod +x build-macos-shared.sh + ./build-macos-shared.sh -t -a + ''' + sh ''' + cd artifacts + zip -9r ton-arm64-macos-m1-shared ./* + ''' + archiveArtifacts artifacts: 'artifacts/ton-arm64-macos-m1-shared.zip' + } + } + } + stage('macOS 12.6 aarch64 (portable)') { + agent { + label 'macOS_12.6-arm64-m1' + } + steps { + timeout(time: 180, unit: 'MINUTES') { + sh ''' + cp assembly/nix/build-macos-nix.sh . + chmod +x build-macos-nix.sh + ./build-macos-nix.sh -t + ''' + sh ''' + cd artifacts + zip -9r ton-arm64-macos-portable ./* + ''' + archiveArtifacts artifacts: 'artifacts/ton-arm64-macos-portable.zip' + } + } + } + stage('macOS 13.2 aarch64 (shared)') { + agent { + label 'macOS_13.2-arm64-m2' + } + steps { + timeout(time: 180, unit: 'MINUTES') { + sh ''' + cp assembly/native/build-macos-shared.sh . + chmod +x build-macos-shared.sh + ./build-macos-shared.sh -t -a + ''' + sh ''' + cd artifacts + zip -9r ton-arm64-macos-m2-shared ./* + ''' + archiveArtifacts artifacts: 'artifacts/ton-arm64-macos-m2-shared.zip' + } + } + } + stage('Windows Server 2022 x86-64') { + agent { + label 'Windows_x86-64' + } + steps { + timeout(time: 180, unit: 'MINUTES') { + bat ''' + copy assembly\\native\\build-windows.bat . + build-windows.bat -t + ''' + bat ''' + cd artifacts + zip -9r ton-x86-64-windows ./* + ''' + archiveArtifacts artifacts: 'artifacts/ton-x86-64-windows.zip' + } + } + } + stage('Android Tonlib') { + agent { + label 'Ubuntu_x86-64' + } + steps { + timeout(time: 180, unit: 'MINUTES') { + sh ''' + cp assembly/android/build-android-tonlib.sh . + chmod +x build-android-tonlib.sh + ./build-android-tonlib.sh -a + ''' + sh ''' + cd artifacts/tonlib-android-jni + zip -9r ton-android-tonlib ./* + ''' + archiveArtifacts artifacts: 'artifacts/tonlib-android-jni/ton-android-tonlib.zip' + } + } + } + stage('WASM fift func emulator') { + agent { + label 'Ubuntu_x86-64' + } + steps { + timeout(time: 180, unit: 'MINUTES') { + sh ''' + cd assembly/wasm + chmod +x fift-func-wasm-build-ubuntu.sh + ./fift-func-wasm-build-ubuntu.sh -a + ''' + sh ''' + cd artifacts + zip -9r ton-wasm-binaries ./* + ''' + archiveArtifacts artifacts: 'artifacts/ton-wasm-binaries.zip' + } + } + } + } + } + } +} diff --git a/assembly/native/build-macos-portable.sh b/assembly/native/build-macos-portable.sh new file mode 100644 index 00000000..32a09e45 --- /dev/null +++ b/assembly/native/build-macos-portable.sh @@ -0,0 +1,225 @@ +#/bin/bash + +with_tests=false +with_artifacts=false +OSX_TARGET=10.15 + + +while getopts 'tao:' flag; do + case "${flag}" in + t) with_tests=true ;; + a) with_artifacts=true ;; + o) OSX_TARGET=${OPTARG} ;; + *) break + ;; + esac +done + +if [ ! -d "build" ]; then + mkdir build + cd build +else + cd build + rm -rf .ninja* CMakeCache.txt +fi + +export NONINTERACTIVE=1 +brew install ninja pkg-config automake libtool autoconf +brew install llvm@16 + + +if [ -f /opt/homebrew/opt/llvm@16/bin/clang ]; then + export CC=/opt/homebrew/opt/llvm@16/bin/clang + export CXX=/opt/homebrew/opt/llvm@16/bin/clang++ +else + export CC=/usr/local/opt/llvm@16/bin/clang + export CXX=/usr/local/opt/llvm@16/bin/clang++ +fi +export CCACHE_DISABLE=1 + +if [ ! -d "lz4" ]; then +git clone https://github.com/lz4/lz4.git +cd lz4 +lz4Path=`pwd` +git checkout v1.9.4 +make -j12 +test $? -eq 0 || { echo "Can't compile lz4"; exit 1; } +cd .. +# ./lib/liblz4.a +# ./lib +else + lz4Path=$(pwd)/lz4 + echo "Using compiled lz4" +fi + +if [ ! -d "secp256k1" ]; then +git clone https://github.com/bitcoin-core/secp256k1.git +cd secp256k1 +secp256k1Path=`pwd` +git checkout v0.3.2 +./autogen.sh +./configure --enable-module-recovery --enable-static --disable-tests --disable-benchmark --with-pic +make -j12 +test $? -eq 0 || { echo "Can't compile secp256k1"; exit 1; } +cd .. +else + secp256k1Path=$(pwd)/secp256k1 + echo "Using compiled secp256k1" +fi + +if [ ! -d "libsodium" ]; then + export LIBSODIUM_FULL_BUILD=1 + git clone https://github.com/jedisct1/libsodium.git + cd libsodium + sodiumPath=`pwd` + git checkout 1.0.18 + ./autogen.sh + ./configure --with-pic --enable-static + make -j12 + test $? -eq 0 || { echo "Can't compile libsodium"; exit 1; } + cd .. +else + sodiumPath=$(pwd)/libsodium + echo "Using compiled libsodium" +fi + +if [ ! -d "openssl_3" ]; then + git clone https://github.com/openssl/openssl openssl_3 + cd openssl_3 + opensslPath=`pwd` + git checkout openssl-3.1.4 + ./config -static + make build_libs -j12 + test $? -eq 0 || { echo "Can't compile openssl_3"; exit 1; } + cd .. +else + opensslPath=$(pwd)/openssl_3 + echo "Using compiled openssl_3" +fi + +if [ ! -d "zlib" ]; then + git clone https://github.com/madler/zlib.git + cd zlib + zlibPath=`pwd` + ./configure --static + make -j12 + test $? -eq 0 || { echo "Can't compile zlib"; exit 1; } + cd .. +else + zlibPath=$(pwd)/zlib + echo "Using compiled zlib" +fi + +if [ ! -d "libmicrohttpd" ]; then + git clone https://git.gnunet.org/libmicrohttpd.git + cd libmicrohttpd + libmicrohttpdPath=`pwd` + ./autogen.sh + ./configure --enable-static --disable-tests --disable-benchmark --disable-shared --disable-https --with-pic + make -j12 + test $? -eq 0 || { echo "Can't compile libmicrohttpd"; exit 1; } + cd .. +else + libmicrohttpdPath=$(pwd)/libmicrohttpd + echo "Using compiled libmicrohttpd" +fi + +cmake -GNinja .. \ +-DPORTABLE=1 \ +-DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=$OSX_TARGET \ +-DCMAKE_CXX_FLAGS="-stdlib=libc++" \ +-DCMAKE_BUILD_TYPE=Release \ +-DOPENSSL_FOUND=1 \ +-DOPENSSL_INCLUDE_DIR=$opensslPath/include \ +-DOPENSSL_CRYPTO_LIBRARY=$opensslPath/libcrypto.a \ +-DZLIB_FOUND=1 \ +-DZLIB_INCLUDE_DIR=$zlibPath \ +-DZLIB_LIBRARIES=$zlibPath/libz.a \ +-DSECP256K1_FOUND=1 \ +-DSECP256K1_INCLUDE_DIR=$secp256k1Path/include \ +-DSECP256K1_LIBRARY=$secp256k1Path/.libs/libsecp256k1.a \ +-DSODIUM_FOUND=1 \ +-DSODIUM_INCLUDE_DIR=$sodiumPath/src/libsodium/include \ +-DSODIUM_LIBRARY_RELEASE=$sodiumPath/src/libsodium/.libs/libsodium.a \ +-DMHD_FOUND=1 \ +-DMHD_INCLUDE_DIR=$libmicrohttpdPath/src/include \ +-DMHD_LIBRARY=$libmicrohttpdPath/src/microhttpd/.libs/libmicrohttpd.a \ +-DLZ4_FOUND=1 \ +-DLZ4_INCLUDE_DIRS=$lz4Path/lib \ +-DLZ4_LIBRARIES=$lz4Path/lib/liblz4.a + + +test $? -eq 0 || { echo "Can't configure ton"; exit 1; } + +if [ "$with_tests" = true ]; then + ninja storage-daemon storage-daemon-cli blockchain-explorer \ + tonlib tonlibjson tonlib-cli validator-engine func fift \ + lite-client pow-miner validator-engine-console generate-random-id json2tlo dht-server \ + http-proxy rldp-http-proxy adnl-proxy create-state create-hardfork tlbc emulator \ + test-ed25519 test-ed25519-crypto test-bigint test-vm test-fift test-cells test-smartcont \ + test-net test-tdactor test-tdutils test-tonlib-offline test-adnl test-dht test-rldp \ + test-rldp2 test-catchain test-fec test-tddb test-db test-validator-session-state + test $? -eq 0 || { echo "Can't compile ton"; exit 1; } +else + ninja storage-daemon storage-daemon-cli blockchain-explorer \ + tonlib tonlibjson tonlib-cli validator-engine func fift \ + lite-client pow-miner validator-engine-console generate-random-id json2tlo dht-server \ + http-proxy rldp-http-proxy adnl-proxy create-state create-hardfork tlbc emulator + test $? -eq 0 || { echo "Can't compile ton"; exit 1; } +fi + +strip storage/storage-daemon/storage-daemon +strip storage/storage-daemon/storage-daemon-cli +strip blockchain-explorer/blockchain-explorer +strip crypto/fift +strip crypto/func +strip crypto/create-state +strip crypto/tlbc +strip validator-engine-console/validator-engine-console +strip tonlib/tonlib-cli +strip http/http-proxy +strip rldp-http-proxy/rldp-http-proxy +strip dht-server/dht-server +strip lite-client/lite-client +strip validator-engine/validator-engine +strip utils/generate-random-id +strip utils/json2tlo +strip adnl/adnl-proxy + +cd .. + +if [ "$with_artifacts" = true ]; then + echo Creating artifacts... + rm -rf artifacts + mkdir artifacts + cp crypto/fift/lib artifacts/ + cp -R crypto/smartcont/ artifacts/ + cp build/storage/storage-daemon/storage-daemon artifacts/ + cp build/storage/storage-daemon/storage-daemon-cli artifacts/ + cp build/blockchain-explorer/blockchain-explorer artifacts/ + cp build/crypto/fift artifacts/ + cp build/crypto/func artifacts/ + cp build/crypto/create-state artifacts/ + cp build/crypto/tlbc artifacts/ + cp build/validator-engine-console/validator-engine-console artifacts/ + cp build/tonlib/tonlib-cli artifacts/ + cp build/tonlib/libtonlibjson.0.5.dylib artifacts/libtonlibjson.dylib + cp build/http/http-proxy artifacts/ + cp build/rldp-http-proxy/rldp-http-proxy artifacts/ + cp build/dht-server/dht-server artifacts/ + cp build/lite-client/lite-client artifacts/ + cp build/validator-engine/validator-engine artifacts/ + cp build/utils/generate-random-id artifacts/ + cp build/utils/json2tlo artifacts/ + cp build/adnl/adnl-proxy artifacts/ + cp build/emulator/libemulator.dylib artifacts/ + chmod +x artifacts/* + rsync -r crypto/smartcont artifacts/ + rsync -r crypto/fift/lib artifacts/ +fi + +if [ "$with_tests" = true ]; then + cd build +# ctest --output-on-failure -E "test-catchain|test-actors" + ctest --output-on-failure +fi diff --git a/assembly/native/build-macos-shared.sh b/assembly/native/build-macos-shared.sh new file mode 100644 index 00000000..0f16eeda --- /dev/null +++ b/assembly/native/build-macos-shared.sh @@ -0,0 +1,152 @@ +#/bin/bash + +with_tests=false +with_artifacts=false +OSX_TARGET=10.15 + + +while getopts 'tao:' flag; do + case "${flag}" in + t) with_tests=true ;; + a) with_artifacts=true ;; + o) OSX_TARGET=${OPTARG} ;; + *) break + ;; + esac +done + +if [ ! -d "build" ]; then + mkdir build + cd build +else + cd build + rm -rf .ninja* CMakeCache.txt +fi + +export NONINTERACTIVE=1 +brew install ninja libsodium libmicrohttpd pkg-config automake libtool autoconf gnutls +brew install llvm@16 + +if [ -f /opt/homebrew/opt/llvm@16/bin/clang ]; then + export CC=/opt/homebrew/opt/llvm@16/bin/clang + export CXX=/opt/homebrew/opt/llvm@16/bin/clang++ +else + export CC=/usr/local/opt/llvm@16/bin/clang + export CXX=/usr/local/opt/llvm@16/bin/clang++ +fi +export CCACHE_DISABLE=1 + +if [ ! -d "secp256k1" ]; then + git clone https://github.com/bitcoin-core/secp256k1.git + cd secp256k1 + secp256k1Path=`pwd` + git checkout v0.3.2 + ./autogen.sh + ./configure --enable-module-recovery --enable-static --disable-tests --disable-benchmark + make -j12 + test $? -eq 0 || { echo "Can't compile secp256k1"; exit 1; } + cd .. +else + secp256k1Path=$(pwd)/secp256k1 + echo "Using compiled secp256k1" +fi + +if [ ! -d "lz4" ]; then + git clone https://github.com/lz4/lz4 + cd lz4 + lz4Path=`pwd` + git checkout v1.9.4 + make -j12 + test $? -eq 0 || { echo "Can't compile lz4"; exit 1; } + cd .. +else + lz4Path=$(pwd)/lz4 + echo "Using compiled lz4" +fi + +brew unlink openssl@1.1 +brew install openssl@3 +brew unlink openssl@3 && brew link --overwrite openssl@3 + +cmake -GNinja -DCMAKE_BUILD_TYPE=Release .. \ +-DCMAKE_CXX_FLAGS="-stdlib=libc++" \ +-DSECP256K1_FOUND=1 \ +-DSECP256K1_INCLUDE_DIR=$secp256k1Path/include \ +-DSECP256K1_LIBRARY=$secp256k1Path/.libs/libsecp256k1.a \ +-DLZ4_FOUND=1 \ +-DLZ4_LIBRARIES=$lz4Path/lib/liblz4.a \ +-DLZ4_INCLUDE_DIRS=$lz4Path/lib + +test $? -eq 0 || { echo "Can't configure ton"; exit 1; } + +if [ "$with_tests" = true ]; then + ninja storage-daemon storage-daemon-cli blockchain-explorer \ + tonlib tonlibjson tonlib-cli validator-engine func fift \ + lite-client pow-miner validator-engine-console generate-random-id json2tlo dht-server \ + http-proxy rldp-http-proxy adnl-proxy create-state create-hardfork tlbc emulator \ + test-ed25519 test-ed25519-crypto test-bigint test-vm test-fift test-cells test-smartcont \ + test-net test-tdactor test-tdutils test-tonlib-offline test-adnl test-dht test-rldp \ + test-rldp2 test-catchain test-fec test-tddb test-db test-validator-session-state + test $? -eq 0 || { echo "Can't compile ton"; exit 1; } +else + ninja storage-daemon storage-daemon-cli blockchain-explorer \ + tonlib tonlibjson tonlib-cli validator-engine func fift \ + lite-client pow-miner validator-engine-console generate-random-id json2tlo dht-server \ + http-proxy rldp-http-proxy adnl-proxy create-state create-hardfork tlbc emulator + test $? -eq 0 || { echo "Can't compile ton"; exit 1; } +fi + + +strip storage/storage-daemon/storage-daemon +strip storage/storage-daemon/storage-daemon-cli +strip blockchain-explorer/blockchain-explorer +strip crypto/fift +strip crypto/func +strip crypto/create-state +strip crypto/tlbc +strip validator-engine-console/validator-engine-console +strip tonlib/tonlib-cli +strip http/http-proxy +strip rldp-http-proxy/rldp-http-proxy +strip dht-server/dht-server +strip lite-client/lite-client +strip validator-engine/validator-engine +strip utils/generate-random-id +strip utils/json2tlo +strip adnl/adnl-proxy + +cd .. + +if [ "$with_artifacts" = true ]; then + echo Creating artifacts... + rm -rf artifacts + mkdir artifacts + cp build/storage/storage-daemon/storage-daemon artifacts/ + cp build/storage/storage-daemon/storage-daemon-cli artifacts/ + cp build/blockchain-explorer/blockchain-explorer artifacts/ + cp build/crypto/fift artifacts/ + cp build/crypto/func artifacts/ + cp build/crypto/create-state artifacts/ + cp build/crypto/tlbc artifacts/ + cp build/validator-engine-console/validator-engine-console artifacts/ + cp build/tonlib/tonlib-cli artifacts/ + cp build/tonlib/libtonlibjson.0.5.dylib artifacts/libtonlibjson.dylib + cp build/http/http-proxy artifacts/ + cp build/rldp-http-proxy/rldp-http-proxy artifacts/ + cp build/dht-server/dht-server artifacts/ + cp build/lite-client/lite-client artifacts/ + cp build/validator-engine/validator-engine artifacts/ + cp build/utils/generate-random-id artifacts/ + cp build/utils/json2tlo artifacts/ + cp build/adnl/adnl-proxy artifacts/ + cp build/emulator/libemulator.dylib artifacts/ + chmod +x artifacts/* + rsync -r crypto/smartcont artifacts/ + rsync -r crypto/fift/lib artifacts/ +fi + +if [ "$with_tests" = true ]; then + cd build +# ctest --output-on-failure -E "test-catchain|test-actors" + ctest --output-on-failure --timeout 1800 +fi diff --git a/assembly/native/build-ubuntu-portable.sh b/assembly/native/build-ubuntu-portable.sh new file mode 100644 index 00000000..a3a11f1b --- /dev/null +++ b/assembly/native/build-ubuntu-portable.sh @@ -0,0 +1,216 @@ +#/bin/bash + +#sudo apt-get update +#sudo apt-get install -y build-essential git cmake ninja-build automake libtool texinfo autoconf + +with_tests=false +with_artifacts=false + + +while getopts 'ta' flag; do + case "${flag}" in + t) with_tests=true ;; + a) with_artifacts=true ;; + *) break + ;; + esac +done + +if [ ! -d "build" ]; then + mkdir build + cd build +else + cd build + rm -rf .ninja* CMakeCache.txt +fi + +export CC=$(which clang-16) +export CXX=$(which clang++-16) +export CCACHE_DISABLE=1 + +if [ ! -d "lz4" ]; then +git clone https://github.com/lz4/lz4.git +cd lz4 +lz4Path=`pwd` +git checkout v1.9.4 +make -j12 +test $? -eq 0 || { echo "Can't compile lz4"; exit 1; } +cd .. +# ./lib/liblz4.a +# ./lib +else + lz4Path=$(pwd)/lz4 + echo "Using compiled lz4" +fi + +if [ ! -d "secp256k1" ]; then +git clone https://github.com/bitcoin-core/secp256k1.git +cd secp256k1 +secp256k1Path=`pwd` +git checkout v0.3.2 +./autogen.sh +./configure --enable-module-recovery --enable-static --disable-tests --disable-benchmark --with-pic +make -j12 +test $? -eq 0 || { echo "Can't compile secp256k1"; exit 1; } +cd .. +# ./.libs/libsecp256k1.a +# ./include +else + secp256k1Path=$(pwd)/secp256k1 + echo "Using compiled secp256k1" +fi + +if [ ! -d "libsodium" ]; then + export LIBSODIUM_FULL_BUILD=1 + git clone https://github.com/jedisct1/libsodium.git + cd libsodium + sodiumPath=`pwd` + git checkout 1.0.18 + ./autogen.sh + ./configure --with-pic --enable-static + make -j12 + test $? -eq 0 || { echo "Can't compile libsodium"; exit 1; } + cd .. +else + sodiumPath=$(pwd)/libsodium + echo "Using compiled libsodium" +fi + +if [ ! -d "openssl_3" ]; then + git clone https://github.com/openssl/openssl openssl_3 + cd openssl_3 + opensslPath=`pwd` + git checkout openssl-3.1.4 + ./config -static + make build_libs -j12 + test $? -eq 0 || { echo "Can't compile openssl_3"; exit 1; } + cd .. +else + opensslPath=$(pwd)/openssl_3 + echo "Using compiled openssl_3" +fi + +if [ ! -d "zlib" ]; then + git clone https://github.com/madler/zlib.git + cd zlib + zlibPath=`pwd` + ./configure --static + make -j12 + test $? -eq 0 || { echo "Can't compile zlib"; exit 1; } + cd .. +else + zlibPath=$(pwd)/zlib + echo "Using compiled zlib" +fi + +if [ ! -d "libmicrohttpd" ]; then + git clone https://git.gnunet.org/libmicrohttpd.git + cd libmicrohttpd + libmicrohttpdPath=`pwd` + ./autogen.sh + ./configure --enable-static --disable-tests --disable-benchmark --disable-shared --disable-https --with-pic + make -j12 + test $? -eq 0 || { echo "Can't compile libmicrohttpd"; exit 1; } + cd .. +else + libmicrohttpdPath=$(pwd)/libmicrohttpd + echo "Using compiled libmicrohttpd" +fi + +cmake -GNinja .. \ +-DPORTABLE=1 \ +-DCMAKE_BUILD_TYPE=Release \ +-DOPENSSL_FOUND=1 \ +-DOPENSSL_INCLUDE_DIR=$opensslPath/include \ +-DOPENSSL_CRYPTO_LIBRARY=$opensslPath/libcrypto.a \ +-DZLIB_FOUND=1 \ +-DZLIB_INCLUDE_DIR=$zlibPath \ +-DZLIB_LIBRARIES=$zlibPath/libz.a \ +-DSECP256K1_FOUND=1 \ +-DSECP256K1_INCLUDE_DIR=$secp256k1Path/include \ +-DSECP256K1_LIBRARY=$secp256k1Path/.libs/libsecp256k1.a \ +-DSODIUM_FOUND=1 \ +-DSODIUM_INCLUDE_DIR=$sodiumPath/src/libsodium/include \ +-DSODIUM_LIBRARY_RELEASE=$sodiumPath/src/libsodium/.libs/libsodium.a \ +-DMHD_FOUND=1 \ +-DMHD_INCLUDE_DIR=$libmicrohttpdPath/src/include \ +-DMHD_LIBRARY=$libmicrohttpdPath/src/microhttpd/.libs/libmicrohttpd.a \ +-DLZ4_FOUND=1 \ +-DLZ4_INCLUDE_DIRS=$lz4Path/lib \ +-DLZ4_LIBRARIES=$lz4Path/lib/liblz4.a + + + +test $? -eq 0 || { echo "Can't configure ton"; exit 1; } + +if [ "$with_tests" = true ]; then +ninja storage-daemon storage-daemon-cli fift func tonlib tonlibjson tonlib-cli \ + validator-engine lite-client pow-miner validator-engine-console blockchain-explorer \ + generate-random-id json2tlo dht-server http-proxy rldp-http-proxy \ + adnl-proxy create-state emulator test-ed25519 test-ed25519-crypto test-bigint \ + test-vm test-fift test-cells test-smartcont test-net test-tdactor test-tdutils \ + test-tonlib-offline test-adnl test-dht test-rldp test-rldp2 test-catchain \ + test-fec test-tddb test-db test-validator-session-state + test $? -eq 0 || { echo "Can't compile ton"; exit 1; } +else +ninja storage-daemon storage-daemon-cli fift func tonlib tonlibjson tonlib-cli \ + validator-engine lite-client pow-miner validator-engine-console blockchain-explorer \ + generate-random-id json2tlo dht-server http-proxy rldp-http-proxy \ + adnl-proxy create-state emulator + test $? -eq 0 || { echo "Can't compile ton"; exit 1; } +fi + +strip -g storage/storage-daemon/storage-daemon \ + storage/storage-daemon/storage-daemon-cli \ + blockchain-explorer/blockchain-explorer \ + crypto/fift \ + crypto/tlbc \ + crypto/func \ + crypto/create-state \ + validator-engine-console/validator-engine-console \ + tonlib/tonlib-cli \ + tonlib/libtonlibjson.so.0.5 \ + http/http-proxy \ + rldp-http-proxy/rldp-http-proxy \ + dht-server/dht-server \ + lite-client/lite-client \ + validator-engine/validator-engine \ + utils/generate-random-id \ + utils/json2tlo \ + adnl/adnl-proxy \ + emulator/libemulator.* + +test $? -eq 0 || { echo "Can't strip final binaries"; exit 1; } + +# simple binaries' test +./storage/storage-daemon/storage-daemon -V || exit 1 +./validator-engine/validator-engine -V || exit 1 +./lite-client/lite-client -V || exit 1 +./crypto/fift -V || exit 1 + +cd .. + +if [ "$with_artifacts" = true ]; then + rm -rf artifacts + mkdir artifacts + cp crypto/fift/lib artifacts/ + cp -R crypto/smartcont/ artifacts/ + mv build/tonlib/libtonlibjson.so.0.5 build/tonlib/libtonlibjson.so + cp build/storage/storage-daemon/storage-daemon build/storage/storage-daemon/storage-daemon-cli \ + build/crypto/fift build/crypto/tlbc build/crypto/func build/crypto/create-state build/blockchain-explorer/blockchain-explorer \ + build/validator-engine-console/validator-engine-console build/tonlib/tonlib-cli \ + build/tonlib/libtonlibjson.so build/http/http-proxy build/rldp-http-proxy/rldp-http-proxy \ + build/dht-server/dht-server build/lite-client/lite-client build/validator-engine/validator-engine \ + build/utils/generate-random-id build/utils/json2tlo build/adnl/adnl-proxy build/emulator/libemulator.so \ + artifacts + test $? -eq 0 || { echo "Can't copy final binaries"; exit 1; } + chmod +x artifacts/* + cp -R crypto/smartcont artifacts + cp -R crypto/fift/lib artifacts +fi + +if [ "$with_tests" = true ]; then + cd build +# ctest --output-on-failure -E "test-catchain|test-actors|test-smartcont|test-adnl|test-validator-session-state|test-dht|test-rldp" + ctest --output-on-failure -E "test-adnl" +fi diff --git a/assembly/native/build-ubuntu-shared.sh b/assembly/native/build-ubuntu-shared.sh new file mode 100644 index 00000000..7d5326a4 --- /dev/null +++ b/assembly/native/build-ubuntu-shared.sh @@ -0,0 +1,122 @@ +#/bin/bash + +#sudo apt-get update +#sudo apt-get install -y build-essential git cmake ninja-build zlib1g-dev libsecp256k1-dev libmicrohttpd-dev libsodium-dev liblz4-dev + +with_tests=false +with_artifacts=false + + +while getopts 'ta' flag; do + case "${flag}" in + t) with_tests=true ;; + a) with_artifacts=true ;; + *) break + ;; + esac +done + +if [ ! -d "build" ]; then + mkdir build + cd build +else + cd build + rm -rf .ninja* CMakeCache.txt +fi + +export CC=$(which clang-16) +export CXX=$(which clang++-16) +export CCACHE_DISABLE=1 + +if [ ! -d "openssl_3" ]; then + git clone https://github.com/openssl/openssl openssl_3 + cd openssl_3 + opensslPath=`pwd` + git checkout openssl-3.1.4 + ./config + make build_libs -j12 + test $? -eq 0 || { echo "Can't compile openssl_3"; exit 1; } + cd .. +else + opensslPath=$(pwd)/openssl_3 + echo "Using compiled openssl_3" +fi + +cmake -GNinja .. \ +-DCMAKE_BUILD_TYPE=Release \ +-DOPENSSL_ROOT_DIR=$opensslPath \ +-DOPENSSL_INCLUDE_DIR=$opensslPath/include \ +-DOPENSSL_CRYPTO_LIBRARY=$opensslPath/libcrypto.so + + +test $? -eq 0 || { echo "Can't configure ton"; exit 1; } + +if [ "$with_tests" = true ]; then +ninja storage-daemon storage-daemon-cli fift func tonlib tonlibjson tonlib-cli \ + validator-engine lite-client pow-miner validator-engine-console blockchain-explorer \ + generate-random-id json2tlo dht-server http-proxy rldp-http-proxy \ + adnl-proxy create-state emulator test-ed25519 test-ed25519-crypto test-bigint \ + test-vm test-fift test-cells test-smartcont test-net test-tdactor test-tdutils \ + test-tonlib-offline test-adnl test-dht test-rldp test-rldp2 test-catchain \ + test-fec test-tddb test-db test-validator-session-state + test $? -eq 0 || { echo "Can't compile ton"; exit 1; } +else +ninja storage-daemon storage-daemon-cli fift func tonlib tonlibjson tonlib-cli \ + validator-engine lite-client pow-miner validator-engine-console blockchain-explorer \ + generate-random-id json2tlo dht-server http-proxy rldp-http-proxy \ + adnl-proxy create-state emulator + test $? -eq 0 || { echo "Can't compile ton"; exit 1; } +fi + +strip -g storage/storage-daemon/storage-daemon \ + storage/storage-daemon/storage-daemon-cli \ + blockchain-explorer/blockchain-explorer \ + crypto/fift \ + crypto/tlbc \ + crypto/func \ + crypto/create-state \ + validator-engine-console/validator-engine-console \ + tonlib/tonlib-cli \ + tonlib/libtonlibjson.so.0.5 \ + http/http-proxy \ + rldp-http-proxy/rldp-http-proxy \ + dht-server/dht-server \ + lite-client/lite-client \ + validator-engine/validator-engine \ + utils/generate-random-id \ + utils/json2tlo \ + adnl/adnl-proxy \ + emulator/libemulator.* + +test $? -eq 0 || { echo "Can't strip final binaries"; exit 1; } + +# simple binaries' test +./storage/storage-daemon/storage-daemon -V || exit 1 +./validator-engine/validator-engine -V || exit 1 +./lite-client/lite-client -V || exit 1 +./crypto/fift -V || exit 1 + +cd .. + +if [ "$with_artifacts" = true ]; then + rm -rf artifacts + mkdir artifacts + mv build/tonlib/libtonlibjson.so.0.5 build/tonlib/libtonlibjson.so + cp build/storage/storage-daemon/storage-daemon build/storage/storage-daemon/storage-daemon-cli \ + build/crypto/fift build/crypto/tlbc build/crypto/func build/crypto/create-state build/blockchain-explorer/blockchain-explorer \ + build/validator-engine-console/validator-engine-console build/tonlib/tonlib-cli \ + build/tonlib/libtonlibjson.so build/http/http-proxy build/rldp-http-proxy/rldp-http-proxy \ + build/dht-server/dht-server build/lite-client/lite-client build/validator-engine/validator-engine \ + build/utils/generate-random-id build/utils/json2tlo build/adnl/adnl-proxy build/emulator/libemulator.so \ + artifacts + test $? -eq 0 || { echo "Can't copy final binaries"; exit 1; } + chmod +x artifacts/* + cp -R crypto/smartcont artifacts + cp -R crypto/fift/lib artifacts +fi + +if [ "$with_tests" = true ]; then + cd build +# ctest --output-on-failure -E "test-catchain|test-actors|test-smartcont|test-adnl|test-validator-session-state|test-dht|test-rldp" + ctest --output-on-failure --timeout 1800 +fi diff --git a/assembly/native/build-windows-github.bat b/assembly/native/build-windows-github.bat new file mode 100644 index 00000000..7cad8c7e --- /dev/null +++ b/assembly/native/build-windows-github.bat @@ -0,0 +1,2 @@ +call "C:\Program Files\Microsoft Visual Studio\2022\%1\VC\Auxiliary\Build\vcvars64.bat" +call build-windows.bat -t \ No newline at end of file diff --git a/assembly/native/build-windows.bat b/assembly/native/build-windows.bat new file mode 100644 index 00000000..9b7322e1 --- /dev/null +++ b/assembly/native/build-windows.bat @@ -0,0 +1,222 @@ +REM execute this script inside elevated (Run as Administrator) console "x64 Native Tools Command Prompt for VS 2022" + +echo off + +echo Installing chocolatey windows package manager... +@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" +choco -? +IF %errorlevel% NEQ 0 ( + echo Can't install chocolatey + exit /b %errorlevel% +) + +choco feature enable -n allowEmptyChecksums + +echo Installing pkgconfiglite... +choco install -y pkgconfiglite +IF %errorlevel% NEQ 0 ( + echo Can't install pkgconfiglite + exit /b %errorlevel% +) + +echo Installing ninja... +choco install -y ninja +IF %errorlevel% NEQ 0 ( + echo Can't install ninja + exit /b %errorlevel% +) + +if not exist "zlib" ( +git clone https://github.com/madler/zlib.git +cd zlib +git checkout v1.3.1 +cd contrib\vstudio\vc14 +msbuild zlibstat.vcxproj /p:Configuration=ReleaseWithoutAsm /p:platform=x64 -p:PlatformToolset=v143 + +IF %errorlevel% NEQ 0 ( + echo Can't install zlib + exit /b %errorlevel% +) +cd ..\..\..\.. +) else ( +echo Using zlib... +) + +if not exist "lz4" ( +git clone https://github.com/lz4/lz4.git +cd lz4 +git checkout v1.9.4 +cd build\VS2017\liblz4 +msbuild liblz4.vcxproj /p:Configuration=Release /p:platform=x64 -p:PlatformToolset=v143 +dir /s +IF %errorlevel% NEQ 0 ( + echo Can't install lz4 + exit /b %errorlevel% +) +cd ..\..\..\.. +) else ( +echo Using lz4... +) + +if not exist "secp256k1" ( +git clone https://github.com/bitcoin-core/secp256k1.git +cd secp256k1 +git checkout v0.3.2 +cmake -G "Visual Studio 17 2022" -A x64 -S . -B build -DSECP256K1_ENABLE_MODULE_RECOVERY=ON -DBUILD_SHARED_LIBS=OFF +IF %errorlevel% NEQ 0 ( + echo Can't configure secp256k1 + exit /b %errorlevel% +) +cmake --build build --config Release +IF %errorlevel% NEQ 0 ( + echo Can't install secp256k1 + exit /b %errorlevel% +) +cd .. +) else ( +echo Using secp256k1... +) + + +if not exist "libsodium" ( +curl -Lo libsodium-1.0.18-stable-msvc.zip https://download.libsodium.org/libsodium/releases/libsodium-1.0.18-stable-msvc.zip +IF %errorlevel% NEQ 0 ( + echo Can't download libsodium + exit /b %errorlevel% +) +unzip libsodium-1.0.18-stable-msvc.zip +) else ( +echo Using libsodium... +) + +if not exist "openssl-3.1.4" ( +curl -Lo openssl-3.1.4.zip https://github.com/neodiX42/precompiled-openssl-win64/raw/main/openssl-3.1.4.zip +IF %errorlevel% NEQ 0 ( + echo Can't download OpenSSL + exit /b %errorlevel% +) +unzip -q openssl-3.1.4.zip +) else ( +echo Using openssl... +) + +if not exist "libmicrohttpd-0.9.77-w32-bin" ( +curl -Lo libmicrohttpd-0.9.77-w32-bin.zip https://github.com/neodiX42/precompiled-openssl-win64/raw/main/libmicrohttpd-0.9.77-w32-bin.zip +IF %errorlevel% NEQ 0 ( + echo Can't download libmicrohttpd + exit /b %errorlevel% +) +unzip -q libmicrohttpd-0.9.77-w32-bin.zip +) else ( +echo Using libmicrohttpd... +) + +if not exist "readline-5.0-1-lib" ( +curl -Lo readline-5.0-1-lib.zip https://github.com/neodiX42/precompiled-openssl-win64/raw/main/readline-5.0-1-lib.zip +IF %errorlevel% NEQ 0 ( + echo Can't download readline + exit /b %errorlevel% +) +unzip -q -d readline-5.0-1-lib readline-5.0-1-lib.zip +) else ( +echo Using readline... +) + + +set root=%cd% +echo %root% +set SODIUM_DIR=%root%\libsodium + +mkdir build +cd build +cmake -GNinja -DCMAKE_BUILD_TYPE=Release ^ +-DPORTABLE=1 ^ +-DSODIUM_USE_STATIC_LIBS=1 ^ +-DSECP256K1_FOUND=1 ^ +-DSECP256K1_INCLUDE_DIR=%root%\secp256k1\include ^ +-DSECP256K1_LIBRARY=%root%\secp256k1\build\src\Release\libsecp256k1.lib ^ +-DLZ4_FOUND=1 ^ +-DLZ4_INCLUDE_DIRS=%root%\lz4\lib ^ +-DLZ4_LIBRARIES=%root%\lz4\build\VS2017\liblz4\bin\x64_Release\liblz4_static.lib ^ +-DMHD_FOUND=1 ^ +-DMHD_LIBRARY=%root%\libmicrohttpd-0.9.77-w32-bin\x86_64\VS2019\Release-static\libmicrohttpd.lib ^ +-DMHD_INCLUDE_DIR=%root%\libmicrohttpd-0.9.77-w32-bin\x86_64\VS2019\Release-static ^ +-DZLIB_FOUND=1 ^ +-DZLIB_INCLUDE_DIR=%root%\zlib ^ +-DZLIB_LIBRARIES=%root%\zlib\contrib\vstudio\vc14\x64\ZlibStatReleaseWithoutAsm\zlibstat.lib ^ +-DOPENSSL_FOUND=1 ^ +-DOPENSSL_INCLUDE_DIR=%root%\openssl-3.1.4\x64\include ^ +-DOPENSSL_CRYPTO_LIBRARY=%root%\openssl-3.1.4\x64\lib\libcrypto_static.lib ^ +-DREADLINE_INCLUDE_DIR=%root%\readline-5.0-1-lib\include ^ +-DREADLINE_LIBRARY=%root%\readline-5.0-1-lib\lib\readline.lib ^ +-DCMAKE_CXX_FLAGS="/DTD_WINDOWS=1 /EHsc /bigobj" .. +IF %errorlevel% NEQ 0 ( + echo Can't configure TON + exit /b %errorlevel% +) + +IF "%1"=="-t" ( +ninja storage-daemon storage-daemon-cli blockchain-explorer fift func tonlib tonlibjson ^ +tonlib-cli validator-engine lite-client pow-miner validator-engine-console generate-random-id ^ +json2tlo dht-server http-proxy rldp-http-proxy adnl-proxy create-state create-hardfork emulator ^ +test-ed25519 test-ed25519-crypto test-bigint test-vm test-fift test-cells test-smartcont test-net ^ +test-tdactor test-tdutils test-tonlib-offline test-adnl test-dht test-rldp test-rldp2 test-catchain ^ +test-fec test-tddb test-db test-validator-session-state +IF %errorlevel% NEQ 0 ( + echo Can't compile TON + exit /b %errorlevel% +) +) else ( +ninja storage-daemon storage-daemon-cli blockchain-explorer fift func tonlib tonlibjson ^ +tonlib-cli validator-engine lite-client pow-miner validator-engine-console generate-random-id ^ +json2tlo dht-server http-proxy rldp-http-proxy adnl-proxy create-state create-hardfork emulator +IF %errorlevel% NEQ 0 ( + echo Can't compile TON + exit /b %errorlevel% +) +) + +copy validator-engine\validator-engine.exe test +IF %errorlevel% NEQ 0 ( + echo validator-engine.exe does not exist + exit /b %errorlevel% +) + +IF "%1"=="-t" ( + echo Running tests... +REM ctest -C Release --output-on-failure -E "test-catchain|test-actors|test-validator-session-state" + ctest -C Release --output-on-failure -E "test-bigint" --timeout 1800 + IF %errorlevel% NEQ 0 ( + echo Some tests failed + exit /b %errorlevel% + ) +) + + +echo Creating artifacts... +cd .. +mkdir artifacts +mkdir artifacts\smartcont +mkdir artifacts\lib + +for %%I in (build\storage\storage-daemon\storage-daemon.exe ^ +build\storage\storage-daemon\storage-daemon-cli.exe ^ +build\blockchain-explorer\blockchain-explorer.exe ^ +build\crypto\fift.exe ^ +build\crypto\tlbc.exe ^ +build\crypto\func.exe ^ +build\crypto\create-state.exe ^ +build\validator-engine-console\validator-engine-console.exe ^ +build\tonlib\tonlib-cli.exe ^ +build\tonlib\tonlibjson.dll ^ +build\http\http-proxy.exe ^ +build\rldp-http-proxy\rldp-http-proxy.exe ^ +build\dht-server\dht-server.exe ^ +build\lite-client\lite-client.exe ^ +build\validator-engine\validator-engine.exe ^ +build\utils\generate-random-id.exe ^ +build\utils\json2tlo.exe ^ +build\adnl\adnl-proxy.exe ^ +build\emulator\emulator.dll) do (strip -g %%I & copy %%I artifacts\) +xcopy /e /k /h /i crypto\smartcont artifacts\smartcont +xcopy /e /k /h /i crypto\fift\lib artifacts\lib diff --git a/assembly/nix/build-linux-arm64-nix.sh b/assembly/nix/build-linux-arm64-nix.sh new file mode 100644 index 00000000..8e5c367c --- /dev/null +++ b/assembly/nix/build-linux-arm64-nix.sh @@ -0,0 +1,37 @@ +#/bin/bash + +nix-build --version +test $? -eq 0 || { echo "Nix is not installed!"; exit 1; } + +with_tests=false + + +while getopts 't' flag; do + case "${flag}" in + t) with_tests=true ;; + *) break + ;; + esac +done + +cp assembly/nix/linux-arm64* . +cp assembly/nix/microhttpd.nix . +cp assembly/nix/openssl.nix . +export NIX_PATH=nixpkgs=https://github.com/nixOS/nixpkgs/archive/23.05.tar.gz + +if [ "$with_tests" = true ]; then + nix-build linux-arm64-static.nix --arg testing true +else + nix-build linux-arm64-static.nix +fi + +mkdir -p artifacts/lib +cp ./result/bin/* artifacts/ +test $? -eq 0 || { echo "No artifacts have been built..."; exit 1; } +chmod +x artifacts/* +rm -rf result +nix-build linux-arm64-tonlib.nix +cp ./result/lib/libtonlibjson.so.0.5 artifacts/libtonlibjson.so +cp ./result/lib/libemulator.so artifacts/ +cp ./result/lib/fift/* artifacts/lib/ +cp -r ./result/share/ton/smartcont artifacts/ diff --git a/assembly/nix/build-linux-x86-64-nix.sh b/assembly/nix/build-linux-x86-64-nix.sh new file mode 100644 index 00000000..38431ca4 --- /dev/null +++ b/assembly/nix/build-linux-x86-64-nix.sh @@ -0,0 +1,37 @@ +#/bin/bash + +nix-build --version +test $? -eq 0 || { echo "Nix is not installed!"; exit 1; } + +with_tests=false + + +while getopts 't' flag; do + case "${flag}" in + t) with_tests=true ;; + *) break + ;; + esac +done + +cp assembly/nix/linux-x86-64* . +cp assembly/nix/microhttpd.nix . +cp assembly/nix/openssl.nix . +export NIX_PATH=nixpkgs=https://github.com/nixOS/nixpkgs/archive/23.05.tar.gz + +if [ "$with_tests" = true ]; then + nix-build linux-x86-64-static.nix --arg testing true +else + nix-build linux-x86-64-static.nix +fi + +mkdir -p artifacts/lib +cp ./result/bin/* artifacts/ +test $? -eq 0 || { echo "No artifacts have been built..."; exit 1; } +chmod +x artifacts/* +rm -rf result +nix-build linux-x86-64-tonlib.nix +cp ./result/lib/libtonlibjson.so.0.5 artifacts/libtonlibjson.so +cp ./result/lib/libemulator.so artifacts/ +cp ./result/lib/fift/* artifacts/lib/ +cp -r ./result/share/ton/smartcont artifacts/ diff --git a/assembly/nix/build-macos-nix.sh b/assembly/nix/build-macos-nix.sh new file mode 100644 index 00000000..12977745 --- /dev/null +++ b/assembly/nix/build-macos-nix.sh @@ -0,0 +1,35 @@ +#/bin/bash + +nix-build --version +test $? -eq 0 || { echo "Nix is not installed!"; exit 1; } + +with_tests=false + + +while getopts 't' flag; do + case "${flag}" in + t) with_tests=true ;; + *) break + ;; + esac +done + +cp assembly/nix/macos-* . +export NIX_PATH=nixpkgs=https://github.com/nixOS/nixpkgs/archive/23.05.tar.gz + +if [ "$with_tests" = true ]; then + nix-build macos-static.nix --arg testing true +else + nix-build macos-static.nix +fi + +mkdir -p artifacts/lib +cp ./result-bin/bin/* artifacts/ +test $? -eq 0 || { echo "No artifacts have been built..."; exit 1; } +chmod +x artifacts/* +rm -rf result-bin +nix-build macos-tonlib.nix +cp ./result/lib/libtonlibjson.dylib artifacts/ +cp ./result/lib/libemulator.dylib artifacts/ +cp ./result/lib/fift/* artifacts/lib/ +cp -r ./result/share/ton/smartcont artifacts/ diff --git a/assembly/nix/flakes/flake.lock b/assembly/nix/flakes/flake.lock new file mode 100644 index 00000000..d22f15d3 --- /dev/null +++ b/assembly/nix/flakes/flake.lock @@ -0,0 +1,94 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1698846319, + "narHash": "sha256-4jyW/dqFBVpWFnhl0nvP6EN4lP7/ZqPxYRjl6var0Oc=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "50fc86b75d2744e1ab3837ef74b53f103a9b55a0", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-trunk": { + "locked": { + "lastModified": 1683098912, + "narHash": "sha256-bFHOixPoHZ5y44FvFgpEuZV0UYTQPNDZK/XqeUi1Lbs=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "abc97d3572dec126eba9fec358eb2f359a944683", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs-stable": "nixpkgs-stable", + "nixpkgs-trunk": "nixpkgs-trunk" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} \ No newline at end of file diff --git a/assembly/nix/flakes/flake.nix b/assembly/nix/flakes/flake.nix new file mode 100644 index 00000000..4e993ac5 --- /dev/null +++ b/assembly/nix/flakes/flake.nix @@ -0,0 +1,158 @@ +{ + inputs = { + nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-23.05"; + nixpkgs-trunk.url = "github:nixos/nixpkgs"; + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs-stable, nixpkgs-trunk, flake-compat, flake-utils }: + let + ton = { host, pkgs ? host, stdenv ? pkgs.stdenv, staticGlibc ? false + , staticMusl ? false, staticExternalDeps ? staticGlibc }: + with host.lib; + stdenv.mkDerivation { + pname = "ton"; + version = "dev"; + + src = ./.; + + nativeBuildInputs = with host; + [ cmake ninja pkg-config git ] ++ + optionals stdenv.isLinux [ dpkg rpm createrepo_c pacman ]; + buildInputs = with pkgs; + # at some point nixpkgs' pkgsStatic will build with static glibc + # then we can skip these manual overrides + # and switch between pkgsStatic and pkgsStatic.pkgsMusl for static glibc and musl builds + if !staticExternalDeps then [ + openssl + zlib + libmicrohttpd + libsodium + secp256k1 + ] else + [ + (openssl.override { static = true; }).dev + (zlib.override { shared = false; }).dev + ] + ++ optionals (!stdenv.isDarwin) [ pkgsStatic.libmicrohttpd.dev pkgsStatic.libsodium.dev secp256k1 ] + ++ optionals stdenv.isDarwin [ (libiconv.override { enableStatic = true; enableShared = false; }) ] + ++ optionals stdenv.isDarwin (forEach [ libmicrohttpd.dev libsodium.dev secp256k1 gmp.dev nettle.dev (gnutls.override { withP11-kit = false; }).dev libtasn1.dev libidn2.dev libunistring.dev gettext ] (x: x.overrideAttrs(oldAttrs: rec { configureFlags = (oldAttrs.configureFlags or []) ++ [ "--enable-static" "--disable-shared" ]; dontDisableStatic = true; }))) + ++ optionals staticGlibc [ glibc.static ]; + + dontAddStaticConfigureFlags = stdenv.isDarwin; + + cmakeFlags = [ "-DTON_USE_ABSEIL=OFF" "-DNIX=ON" ] ++ optionals staticMusl [ + "-DCMAKE_CROSSCOMPILING=OFF" # pkgsStatic sets cross + ] ++ optionals (staticGlibc || staticMusl) [ + "-DCMAKE_LINK_SEARCH_START_STATIC=ON" + "-DCMAKE_LINK_SEARCH_END_STATIC=ON" + ] ++ optionals (stdenv.isDarwin) [ + "-DCMAKE_CXX_FLAGS=-stdlib=libc++" "-DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=11.7" + ]; + + LDFLAGS = optional staticExternalDeps (concatStringsSep " " [ + (optionalString stdenv.cc.isGNU "-static-libgcc") + (optionalString stdenv.isDarwin "-framework CoreFoundation") + "-static-libstdc++" + ]); + + GIT_REVISION = if self ? rev then self.rev else "dirty"; + GIT_REVISION_DATE = (builtins.concatStringsSep "-" (builtins.match "(.{4})(.{2})(.{2}).*" self.lastModifiedDate)) + " " + (builtins.concatStringsSep ":" (builtins.match "^........(.{2})(.{2})(.{2}).*" self.lastModifiedDate)); + + postInstall = '' + moveToOutput bin "$bin" + ''; + + preFixup = optionalString stdenv.isDarwin '' + for fn in "$bin"/bin/* "$out"/lib/*.dylib; do + echo Fixing libc++ in "$fn" + install_name_tool -change "$(otool -L "$fn" | grep libc++.1 | cut -d' ' -f1 | xargs)" libc++.1.dylib "$fn" + install_name_tool -change "$(otool -L "$fn" | grep libc++abi.1 | cut -d' ' -f1 | xargs)" libc++abi.dylib "$fn" + done + ''; + + outputs = [ "bin" "out" ]; + }; + hostPkgs = system: + import nixpkgs-stable { + inherit system; + overlays = [ + (self: super: { + zchunk = nixpkgs-trunk.legacyPackages.${system}.zchunk; + }) + ]; + }; + in with flake-utils.lib; + (nixpkgs-stable.lib.recursiveUpdate + (eachSystem (with system; [ x86_64-linux aarch64-linux ]) (system: + let + host = hostPkgs system; + # look out for https://github.com/NixOS/nixpkgs/issues/129595 for progress on better infra for this + # + # nixos 19.09 ships with glibc 2.27 + # we could also just override glibc source to a particular release + # but then we'd need to port patches as well + nixos1909 = (import (builtins.fetchTarball { + url = "https://channels.nixos.org/nixos-19.09/nixexprs.tar.xz"; + sha256 = "1vp1h2gkkrckp8dzkqnpcc6xx5lph5d2z46sg2cwzccpr8ay58zy"; + }) { inherit system; }); + glibc227 = nixos1909.glibc // { pname = "glibc"; }; + stdenv227 = let + cc = host.wrapCCWith { + cc = nixos1909.buildPackages.gcc-unwrapped; + libc = glibc227; + bintools = host.binutils.override { libc = glibc227; }; + }; + in (host.overrideCC host.stdenv cc); + in rec { + packages = rec { + ton-normal = ton { inherit host; }; + ton-static = ton { + inherit host; + stdenv = host.makeStatic host.stdenv; + staticGlibc = true; + }; + ton-musl = + let pkgs = nixpkgs-stable.legacyPackages.${system}.pkgsStatic; + in ton { + inherit host; + inherit pkgs; + stdenv = + pkgs.gcc12Stdenv; # doesn't build on aarch64-linux w/default GCC 9 + staticMusl = true; + }; + ton-oldglibc = (ton { + inherit host; + stdenv = stdenv227; + staticExternalDeps = true; + }); + ton-oldglibc_staticbinaries = host.symlinkJoin { + name = "ton"; + paths = [ ton-musl.bin ton-oldglibc.out ]; + }; + }; + devShells.default = + host.mkShell { inputsFrom = [ packages.ton-normal ]; }; + })) (eachSystem (with system; [ x86_64-darwin aarch64-darwin ]) (system: + let host = hostPkgs system; + in rec { + packages = rec { + ton-normal = ton { inherit host; }; + ton-static = ton { + inherit host; + stdenv = host.makeStatic host.stdenv; + staticExternalDeps = true; + }; + ton-staticbin-dylib = host.symlinkJoin { + name = "ton"; + paths = [ ton-static.bin ton-static.out ]; + }; + }; + devShells.default = + host.mkShell { inputsFrom = [ packages.ton-normal ]; }; + }))); +} \ No newline at end of file diff --git a/assembly/nix/flakes/shell.nix b/assembly/nix/flakes/shell.nix new file mode 100644 index 00000000..6234bb4d --- /dev/null +++ b/assembly/nix/flakes/shell.nix @@ -0,0 +1,10 @@ +(import + ( + let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + { src = ./.; } +).shellNix diff --git a/assembly/nix/linux-arm64-static.nix b/assembly/nix/linux-arm64-static.nix new file mode 100644 index 00000000..8c2749b0 --- /dev/null +++ b/assembly/nix/linux-arm64-static.nix @@ -0,0 +1,46 @@ +# export NIX_PATH=nixpkgs=https://github.com/nixOS/nixpkgs/archive/23.05.tar.gz + +{ pkgs ? import { system = builtins.currentSystem; } +, lib ? pkgs.lib +, stdenv ? pkgs.stdenv +, testing ? false +}: +let + microhttpdmy = (import ./microhttpd.nix) {}; +in +with import microhttpdmy; +stdenv.mkDerivation { + pname = "ton"; + version = "dev-bin"; + + src = ./.; + + nativeBuildInputs = with pkgs; + [ + cmake ninja git pkg-config + ]; + + buildInputs = with pkgs; + [ + pkgsStatic.openssl microhttpdmy pkgsStatic.zlib pkgsStatic.libsodium.dev pkgsStatic.secp256k1 glibc.static pkgsStatic.lz4 + ]; + + makeStatic = true; + doCheck = testing; + + cmakeFlags = [ + "-DTON_USE_ABSEIL=OFF" + "-DNIX=ON" + "-DBUILD_SHARED_LIBS=OFF" + "-DCMAKE_LINK_SEARCH_START_STATIC=ON" + "-DCMAKE_LINK_SEARCH_END_STATIC=ON" + "-DMHD_FOUND=1" + "-DMHD_INCLUDE_DIR=${microhttpdmy}/usr/local/include" + "-DMHD_LIBRARY=${microhttpdmy}/usr/local/lib/libmicrohttpd.a" + "-DCMAKE_CTEST_ARGUMENTS=--timeout;1800" + ]; + + LDFLAGS = [ + "-static-libgcc" "-static-libstdc++" "-static" + ]; +} diff --git a/assembly/nix/linux-arm64-tonlib.nix b/assembly/nix/linux-arm64-tonlib.nix new file mode 100644 index 00000000..ae62ca26 --- /dev/null +++ b/assembly/nix/linux-arm64-tonlib.nix @@ -0,0 +1,44 @@ +# export NIX_PATH=nixpkgs=https://github.com/nixOS/nixpkgs/archive/23.05.tar.gz +{ + pkgs ? import { system = builtins.currentSystem; } +, lib ? pkgs.lib +, stdenv ? pkgs.stdenv +}: +let + microhttpdmy = (import ./microhttpd.nix) {}; +in +with import microhttpdmy; +pkgs.llvmPackages_16.stdenv.mkDerivation { + pname = "ton"; + version = "dev-lib"; + + src = ./.; + + nativeBuildInputs = with pkgs; + [ + cmake ninja git pkg-config + ]; + + buildInputs = with pkgs; + [ + pkgsStatic.openssl microhttpdmy pkgsStatic.zlib pkgsStatic.libsodium.dev pkgsStatic.secp256k1 pkgsStatic.lz4 + ]; + + dontAddStaticConfigureFlags = false; + + cmakeFlags = [ + "-DTON_USE_ABSEIL=OFF" + "-DNIX=ON" + "-DMHD_FOUND=1" + "-DMHD_INCLUDE_DIR=${microhttpdmy}/usr/local/include" + "-DMHD_LIBRARY=${microhttpdmy}/usr/local/lib/libmicrohttpd.a" + ]; + + LDFLAGS = [ + "-static-libgcc" "-static-libstdc++" "-fPIC" "-fcommon" + ]; + + ninjaFlags = [ + "tonlibjson" "emulator" + ]; +} diff --git a/assembly/nix/linux-x86-64-static.nix b/assembly/nix/linux-x86-64-static.nix new file mode 100644 index 00000000..8c2749b0 --- /dev/null +++ b/assembly/nix/linux-x86-64-static.nix @@ -0,0 +1,46 @@ +# export NIX_PATH=nixpkgs=https://github.com/nixOS/nixpkgs/archive/23.05.tar.gz + +{ pkgs ? import { system = builtins.currentSystem; } +, lib ? pkgs.lib +, stdenv ? pkgs.stdenv +, testing ? false +}: +let + microhttpdmy = (import ./microhttpd.nix) {}; +in +with import microhttpdmy; +stdenv.mkDerivation { + pname = "ton"; + version = "dev-bin"; + + src = ./.; + + nativeBuildInputs = with pkgs; + [ + cmake ninja git pkg-config + ]; + + buildInputs = with pkgs; + [ + pkgsStatic.openssl microhttpdmy pkgsStatic.zlib pkgsStatic.libsodium.dev pkgsStatic.secp256k1 glibc.static pkgsStatic.lz4 + ]; + + makeStatic = true; + doCheck = testing; + + cmakeFlags = [ + "-DTON_USE_ABSEIL=OFF" + "-DNIX=ON" + "-DBUILD_SHARED_LIBS=OFF" + "-DCMAKE_LINK_SEARCH_START_STATIC=ON" + "-DCMAKE_LINK_SEARCH_END_STATIC=ON" + "-DMHD_FOUND=1" + "-DMHD_INCLUDE_DIR=${microhttpdmy}/usr/local/include" + "-DMHD_LIBRARY=${microhttpdmy}/usr/local/lib/libmicrohttpd.a" + "-DCMAKE_CTEST_ARGUMENTS=--timeout;1800" + ]; + + LDFLAGS = [ + "-static-libgcc" "-static-libstdc++" "-static" + ]; +} diff --git a/assembly/nix/linux-x86-64-tonlib.nix b/assembly/nix/linux-x86-64-tonlib.nix new file mode 100644 index 00000000..5a6e43e8 --- /dev/null +++ b/assembly/nix/linux-x86-64-tonlib.nix @@ -0,0 +1,54 @@ +# export NIX_PATH=nixpkgs=https://github.com/nixOS/nixpkgs/archive/23.11.tar.gz +# copy linux-x86-64-tonlib.nix to git root directory and execute: +# nix-build linux-x86-64-tonlib.nix +{ + pkgs ? import { system = builtins.currentSystem; } +, lib ? pkgs.lib +, stdenv ? pkgs.stdenv +}: +let + system = builtins.currentSystem; + + nixos1909 = (import (builtins.fetchTarball { + url = "https://channels.nixos.org/nixos-19.09/nixexprs.tar.xz"; + sha256 = "1vp1h2gkkrckp8dzkqnpcc6xx5lph5d2z46sg2cwzccpr8ay58zy"; + }) { inherit system; }); + glibc227 = nixos1909.glibc // { pname = "glibc"; }; + stdenv227 = let + cc = pkgs.wrapCCWith { + cc = nixos1909.buildPackages.gcc-unwrapped; + libc = glibc227; + bintools = pkgs.binutils.override { libc = glibc227; }; + }; + in (pkgs.overrideCC pkgs.stdenv cc); + +in +stdenv227.mkDerivation { + pname = "ton"; + version = "dev-lib"; + + src = ./.; + + nativeBuildInputs = with pkgs; + [ cmake ninja git pkg-config ]; + + buildInputs = with pkgs; + [ + pkgsStatic.openssl pkgsStatic.zlib pkgsStatic.libmicrohttpd.dev pkgsStatic.libsodium.dev pkgsStatic.secp256k1 pkgsStatic.lz4 + ]; + + dontAddStaticConfigureFlags = false; + + cmakeFlags = [ + "-DTON_USE_ABSEIL=OFF" + "-DNIX=ON" + ]; + + LDFLAGS = [ + "-static-libgcc" "-static-libstdc++" "-fPIC" + ]; + + ninjaFlags = [ + "tonlibjson" "emulator" + ]; +} diff --git a/assembly/nix/macos-static.nix b/assembly/nix/macos-static.nix new file mode 100644 index 00000000..be15579c --- /dev/null +++ b/assembly/nix/macos-static.nix @@ -0,0 +1,67 @@ +# export NIX_PATH=nixpkgs=https://github.com/nixOS/nixpkgs/archive/23.05.tar.gz + +{ pkgs ? import { system = builtins.currentSystem; } +, lib ? pkgs.lib +, stdenv ? pkgs.stdenv +, testing ? false +}: + +pkgs.llvmPackages_14.stdenv.mkDerivation { + pname = "ton"; + version = "dev-bin"; + + src = ./.; + + nativeBuildInputs = with pkgs; + [ cmake ninja git pkg-config ]; + + buildInputs = with pkgs; + lib.forEach [ + secp256k1 libsodium.dev libmicrohttpd.dev gmp.dev nettle.dev libtasn1.dev libidn2.dev libunistring.dev gettext (gnutls.override { withP11-kit = false; }).dev + ] + (x: x.overrideAttrs(oldAttrs: rec { configureFlags = (oldAttrs.configureFlags or []) ++ [ "--enable-static" "--disable-shared" "--disable-tests" ]; dontDisableStatic = true; })) + ++ [ + darwin.apple_sdk.frameworks.CoreFoundation + (openssl.override { static = true; }).dev + (zlib.override { shared = false; }).dev + (libiconv.override { enableStatic = true; enableShared = false; }) + (lz4.override { enableStatic = true; enableShared = false; }).dev + ]; + + + dontAddStaticConfigureFlags = true; + makeStatic = true; + doCheck = testing; + + configureFlags = []; + + cmakeFlags = [ + "-DTON_USE_ABSEIL=OFF" + "-DNIX=ON" + "-DCMAKE_CROSSCOMPILING=OFF" + "-DCMAKE_LINK_SEARCH_START_STATIC=ON" + "-DCMAKE_LINK_SEARCH_END_STATIC=ON" + "-DBUILD_SHARED_LIBS=OFF" + "-DCMAKE_CXX_FLAGS=-stdlib=libc++" + "-DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=11.3" + "-DCMAKE_CTEST_ARGUMENTS=--timeout;1800" + ]; + + LDFLAGS = [ + "-static-libstdc++" + "-framework CoreFoundation" + ]; + + postInstall = '' + moveToOutput bin "$bin" + ''; + + preFixup = '' + for fn in "$bin"/bin/* "$out"/lib/*.dylib; do + echo Fixing libc++ in "$fn" + install_name_tool -change "$(otool -L "$fn" | grep libc++.1 | cut -d' ' -f1 | xargs)" libc++.1.dylib "$fn" + install_name_tool -change "$(otool -L "$fn" | grep libc++abi.1 | cut -d' ' -f1 | xargs)" libc++abi.dylib "$fn" + done + ''; + outputs = [ "bin" "out" ]; +} diff --git a/assembly/nix/macos-tonlib.nix b/assembly/nix/macos-tonlib.nix new file mode 100644 index 00000000..4f7b7620 --- /dev/null +++ b/assembly/nix/macos-tonlib.nix @@ -0,0 +1,56 @@ +# export NIX_PATH=nixpkgs=https://github.com/nixOS/nixpkgs/archive/23.05.tar.gz + +{ pkgs ? import { system = builtins.currentSystem; } +, lib ? pkgs.lib +, stdenv ? pkgs.stdenv +}: + +pkgs.llvmPackages_14.stdenv.mkDerivation { + pname = "ton"; + version = "dev-lib"; + + src = ./.; + + nativeBuildInputs = with pkgs; + [ cmake ninja git pkg-config ]; + + buildInputs = with pkgs; + lib.forEach [ + secp256k1 libsodium.dev libmicrohttpd.dev gmp.dev nettle.dev libtasn1.dev libidn2.dev libunistring.dev gettext (gnutls.override { withP11-kit = false; }).dev + ] (x: x.overrideAttrs(oldAttrs: rec { configureFlags = (oldAttrs.configureFlags or []) ++ [ "--enable-static" "--disable-shared" "--disable-tests" ]; dontDisableStatic = true; })) + ++ [ + darwin.apple_sdk.frameworks.CoreFoundation + (openssl.override { static = true; }).dev + (zlib.override { shared = false; }).dev + (libiconv.override { enableStatic = true; enableShared = false; }) + (lz4.override { enableStatic = true; enableShared = false; }).dev + ]; + + dontAddStaticConfigureFlags = true; + + configureFlags = []; + + cmakeFlags = [ + "-DTON_USE_ABSEIL=OFF" + "-DNIX=ON" + "-DCMAKE_CXX_FLAGS=-stdlib=libc++" + "-DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=11.3" + ]; + + LDFLAGS = [ + "-static-libstdc++" + "-framework CoreFoundation" + ]; + + ninjaFlags = [ + "tonlibjson" "emulator" + ]; + + preFixup = '' + for fn in $out/bin/* $out/lib/*.dylib; do + echo Fixing libc++ in "$fn" + install_name_tool -change "$(otool -L "$fn" | grep libc++.1 | cut -d' ' -f1 | xargs)" libc++.1.dylib "$fn" + install_name_tool -change "$(otool -L "$fn" | grep libc++abi.1 | cut -d' ' -f1 | xargs)" libc++abi.dylib "$fn" + done + ''; +} diff --git a/assembly/nix/microhttpd.nix b/assembly/nix/microhttpd.nix new file mode 100644 index 00000000..4f871425 --- /dev/null +++ b/assembly/nix/microhttpd.nix @@ -0,0 +1,28 @@ +{ pkgs ? import { system = builtins.currentSystem; } +, stdenv ? pkgs.stdenv +, fetchgit ? pkgs.fetchgit +}: + +stdenv.mkDerivation rec { + name = "microhttpdmy"; + + + src = fetchgit { + url = "https://git.gnunet.org/libmicrohttpd.git"; + rev = "refs/tags/v0.9.77"; + sha256 = "sha256-x+nfB07PbZwBlFc6kZZFYiRpk0a3QN/ByHB+hC8na/o="; + }; + + nativeBuildInputs = with pkgs; [ automake libtool autoconf texinfo ]; + + buildInputs = with pkgs; [ ]; + + configurePhase = '' + ./autogen.sh + ./configure --enable-static --disable-tests --disable-benchmark --disable-shared --disable-https --with-pic + ''; + + installPhase = '' + make install DESTDIR=$out + ''; +} diff --git a/assembly/nix/openssl.nix b/assembly/nix/openssl.nix new file mode 100644 index 00000000..8d30aa50 --- /dev/null +++ b/assembly/nix/openssl.nix @@ -0,0 +1,30 @@ +{ pkgs ? import { system = builtins.currentSystem; } +, stdenv ? pkgs.stdenv +, fetchFromGitHub ? pkgs.fetchFromGitHub +}: + +stdenv.mkDerivation rec { + name = "opensslmy"; + + src = fetchFromGitHub { + owner = "openssl"; + repo = "openssl"; + rev = "refs/tags/openssl-3.1.4"; + sha256 = "sha256-Vvf1wiNb4ikg1lIS9U137aodZ2JzM711tSWMJFYWtWI="; + }; + + nativeBuildInputs = with pkgs; [ perl ]; + + buildInputs = with pkgs; [ ]; + + postPatch = '' + patchShebangs Configure + ''; + + configurePhase = '' + ./Configure no-shared + ''; + installPhase = '' + make install DESTDIR=$out + ''; +} diff --git a/assembly/wasm/fift-func-wasm-build-ubuntu.sh b/assembly/wasm/fift-func-wasm-build-ubuntu.sh new file mode 100644 index 00000000..6daf2d4c --- /dev/null +++ b/assembly/wasm/fift-func-wasm-build-ubuntu.sh @@ -0,0 +1,170 @@ +# The script builds funcfift compiler to WASM + +# Execute these prerequisites first +# sudo apt update +# sudo apt install -y build-essential git make cmake ninja-build clang libgflags-dev zlib1g-dev libssl-dev \ +# libreadline-dev libmicrohttpd-dev pkg-config libgsl-dev python3 python3-dev python3-pip \ +# nodejs libsecp256k1-dev libsodium-dev automake libtool + +# wget https://apt.llvm.org/llvm.sh +# chmod +x llvm.sh +# sudo ./llvm.sh 16 all + +with_artifacts=false + +while getopts 'a' flag; do + case "${flag}" in + a) with_artifacts=true ;; + *) break + ;; + esac +done + +export CC=$(which clang-16) +export CXX=$(which clang++-16) +export CCACHE_DISABLE=1 + +cd ../.. +rm -rf openssl zlib emsdk secp256k1 libsodium build +echo `pwd` + +git clone https://github.com/openssl/openssl.git +cd openssl +git checkout checkout openssl-3.1.4 +./config +make -j16 +OPENSSL_DIR=`pwd` +cd .. + +git clone https://github.com/madler/zlib.git +cd zlib +ZLIB_DIR=`pwd` +cd .. + +git clone https://github.com/lz4/lz4.git +cd lz4 +LZ4_DIR=`pwd` +cd .. + +git clone https://github.com/bitcoin-core/secp256k1.git +cd secp256k1 +./autogen.sh +SECP256K1_DIR=`pwd` +cd .. + +git clone https://github.com/jedisct1/libsodium --branch stable +cd libsodium +SODIUM_DIR=`pwd` +cd .. + +mkdir build +cd build +cmake -GNinja -DCMAKE_BUILD_TYPE=Release \ +-DCMAKE_CXX_STANDARD=17 \ +-DOPENSSL_FOUND=1 \ +-DOPENSSL_ROOT_DIR=$OPENSSL_DIR \ +-DOPENSSL_INCLUDE_DIR=$OPENSSL_DIR/include \ +-DOPENSSL_CRYPTO_LIBRARY=$OPENSSL_DIR/libcrypto.so \ +-DOPENSSL_SSL_LIBRARY=$OPENSSL_DIR/libssl.so \ +-DTON_USE_ABSEIL=OFF .. + +test $? -eq 0 || { echo "Can't configure TON build"; exit 1; } + +ninja fift smc-envelope + +test $? -eq 0 || { echo "Can't compile fift "; exit 1; } + +rm -rf * + +cd .. + +git clone https://github.com/emscripten-core/emsdk.git +cd emsdk +./emsdk install 3.1.19 +./emsdk activate 3.1.19 +EMSDK_DIR=`pwd` +ls $EMSDK_DIR + +. $EMSDK_DIR/emsdk_env.sh +export CC=$(which emcc) +export CXX=$(which em++) +export CCACHE_DISABLE=1 + +cd ../openssl + +make clean +emconfigure ./Configure linux-generic32 no-shared no-dso no-engine no-unit-test +sed -i 's/CROSS_COMPILE=.*/CROSS_COMPILE=/g' Makefile +sed -i 's/-ldl//g' Makefile +sed -i 's/-O3/-Os/g' Makefile +emmake make depend +emmake make -j16 +test $? -eq 0 || { echo "Can't compile OpenSSL with emmake "; exit 1; } + +cd ../zlib + +emconfigure ./configure --static +emmake make -j16 +test $? -eq 0 || { echo "Can't compile zlib with emmake "; exit 1; } + +cd ../lz4 +emmake make -j16 +test $? -eq 0 || { echo "Can't compile lz4 with emmake "; exit 1; } + +cd ../secp256k1 + +emconfigure ./configure --enable-module-recovery +emmake make -j16 +test $? -eq 0 || { echo "Can't compile secp256k1 with emmake "; exit 1; } + +cd ../libsodium + +emconfigure ./configure --disable-ssp +emmake make -j16 +test $? -eq 0 || { echo "Can't compile libsodium with emmake "; exit 1; } + +cd ../build + +emcmake cmake -DUSE_EMSCRIPTEN=ON -DCMAKE_BUILD_TYPE=Release \ +-DZLIB_FOUND=1 \ +-DZLIB_LIBRARIES=$ZLIB_DIR/libz.a \ +-DZLIB_INCLUDE_DIR=$ZLIB_DIR \ +-DLZ4_FOUND=1 \ +-DLZ4_LIBRARIES=$LZ4_DIR/lib/liblz4.a \ +-DLZ4_INCLUDE_DIRS=$LZ4_DIR/lib \ +-DOPENSSL_FOUND=1 \ +-DOPENSSL_ROOT_DIR=$OPENSSL_DIR \ +-DOPENSSL_INCLUDE_DIR=$OPENSSL_DIR/include \ +-DOPENSSL_CRYPTO_LIBRARY=$OPENSSL_DIR/libcrypto.a \ +-DOPENSSL_SSL_LIBRARY=$OPENSSL_DIR/libssl.a \ +-DCMAKE_TOOLCHAIN_FILE=$EMSDK_DIR/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \ +-DCMAKE_CXX_FLAGS="-sUSE_ZLIB=1" \ +-DSECP256K1_FOUND=1 \ +-DSECP256K1_INCLUDE_DIR=$SECP256K1_DIR/include \ +-DSECP256K1_LIBRARY=$SECP256K1_DIR/.libs/libsecp256k1.a \ +-DSODIUM_INCLUDE_DIR=$SODIUM_DIR/src/libsodium/include \ +-DSODIUM_LIBRARY_RELEASE=$SODIUM_DIR/src/libsodium/.libs/libsodium.a \ +-DSODIUM_USE_STATIC_LIBS=ON .. + +test $? -eq 0 || { echo "Can't configure TON with emmake "; exit 1; } +cp -R ../crypto/smartcont ../crypto/fift/lib crypto + +emmake make -j16 funcfiftlib func fift tlbc emulator-emscripten + +test $? -eq 0 || { echo "Can't compile TON with emmake "; exit 1; } + +if [ "$with_artifacts" = true ]; then + echo "Creating artifacts..." + cd .. + rm -rf artifacts + mkdir artifacts + ls build/crypto + cp build/crypto/fift* artifacts + cp build/crypto/func* artifacts + cp build/crypto/tlbc* artifacts + cp build/emulator/emulator-emscripten* artifacts + cp -R crypto/smartcont artifacts + cp -R crypto/fift/lib artifacts +fi + + diff --git a/blockchain-explorer/CMakeLists.txt b/blockchain-explorer/CMakeLists.txt index 0d02f01f..8aae8805 100644 --- a/blockchain-explorer/CMakeLists.txt +++ b/blockchain-explorer/CMakeLists.txt @@ -1,22 +1,41 @@ -cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) +option(NIX "Use \"ON\" for a static build." OFF) -find_package(MHD) - -if (MHD_FOUND) - - set(BLOCHAIN_EXPLORER_SOURCE +set(BLOCHAIN_EXPLORER_SOURCE blockchain-explorer.cpp blockchain-explorer.hpp blockchain-explorer-http.cpp blockchain-explorer-http.hpp blockchain-explorer-query.cpp blockchain-explorer-query.hpp - ) +) - add_executable(blockchain-explorer ${BLOCHAIN_EXPLORER_SOURCE}) - target_include_directories(blockchain-explorer PUBLIC ${MHD_INCLUDE_DIRS}) - target_link_libraries(blockchain-explorer tdutils tdactor adnllite tl_lite_api tl-lite-utils - ton_crypto ton_block ${MHD_LIBRARY}) +add_executable(blockchain-explorer ${BLOCHAIN_EXPLORER_SOURCE}) +if (NIX) + if (MHD_FOUND) + target_include_directories(blockchain-explorer PUBLIC ${MHD_INCLUDE_DIR}) + target_link_libraries(blockchain-explorer tdutils tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block ${MHD_LIBRARY}) + else() + find_package(PkgConfig REQUIRED) + pkg_check_modules(MHD libmicrohttpd) + target_include_directories(blockchain-explorer PUBLIC ${MHD_INCLUDE_DIR} ${MHD_STATIC_INCLUDE_DIRS}) + target_link_libraries(blockchain-explorer tdutils tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block ${MHD_LIBRARIES} ${MHD_STATIC_LIBRARIES}) + endif() +else() + if (MHD_FOUND) + target_include_directories(blockchain-explorer PUBLIC ${MHD_INCLUDE_DIR}) + target_link_libraries(blockchain-explorer tdutils tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block ${MHD_LIBRARY}) + else() + find_package(MHD) + target_include_directories(blockchain-explorer PUBLIC ${MHD_INCLUDE_DIR}) + target_link_libraries(blockchain-explorer tdutils tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block ${MHD_LIBRARY}) + endif() endif() + +target_include_directories(blockchain-explorer PUBLIC ${MHD_INCLUDE_DIR}) +target_link_libraries(blockchain-explorer tdutils tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block ${MHD_LIBRARY}) + +install(TARGETS blockchain-explorer RUNTIME DESTINATION bin) + diff --git a/blockchain-explorer/blockchain-explorer-http.cpp b/blockchain-explorer/blockchain-explorer-http.cpp index d5d41a15..e2322ff7 100644 --- a/blockchain-explorer/blockchain-explorer-http.cpp +++ b/blockchain-explorer/blockchain-explorer-http.cpp @@ -23,7 +23,7 @@ exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "blockchain-explorer-http.hpp" #include "block/block-db.h" @@ -35,6 +35,37 @@ #include "vm/cells/MerkleProof.h" #include "block/mc-config.h" #include "ton/ton-shard.h" +#include "td/utils/date.h" + +bool local_scripts{false}; + +static std::string time_to_human(unsigned ts) { + td::StringBuilder sb; + sb << date::format("%F %T", + std::chrono::time_point{std::chrono::seconds(ts)}) + << ", "; + auto now = (unsigned)td::Clocks::system(); + bool past = now >= ts; + unsigned x = past ? now - ts : ts - now; + if (!past) { + sb << "in "; + } + if (x < 60) { + sb << x << "s"; + } else if (x < 3600) { + sb << x / 60 << "m " << x % 60 << "s"; + } else if (x < 3600 * 24) { + x /= 60; + sb << x / 60 << "h " << x % 60 << "m"; + } else { + x /= 3600; + sb << x / 24 << "d " << x % 24 << "h"; + } + if (past) { + sb << " ago"; + } + return sb.as_cslice().str(); +} HttpAnswer& HttpAnswer::operator<<(AddressCell addr_c) { ton::WorkchainId wc; @@ -82,7 +113,7 @@ HttpAnswer& HttpAnswer::operator<<(MessageCell msg) { << "source" << AddressCell{info.src} << "\n" << "destinationNONE\n" << "lt" << info.created_lt << "\n" - << "time" << info.created_at << "\n"; + << "time" << info.created_at << " (" << time_to_human(info.created_at) << ")\n"; break; } case block::gen::CommonMsgInfo::int_msg_info: { @@ -101,7 +132,7 @@ HttpAnswer& HttpAnswer::operator<<(MessageCell msg) { << "source" << AddressCell{info.src} << "\n" << "destination" << AddressCell{info.dest} << "\n" << "lt" << info.created_lt << "\n" - << "time" << info.created_at << "\n" + << "time" << info.created_at << " (" << time_to_human(info.created_at) << ")\n" << "value" << value << "\n"; break; } @@ -275,7 +306,7 @@ HttpAnswer& HttpAnswer::operator<<(TransactionCell trans_c) { << "account" << trans_c.addr.rserialize(true) << "" << "hash" << trans_c.root->get_hash().to_hex() << "\n" << "lt" << trans.lt << "\n" - << "time" << trans.now << "\n" + << "time" << trans.now << " (" << time_to_human(trans.now) << ")\n" << "out messages"; vm::Dictionary dict{trans.r1.out_msgs, 15}; for (td::int32 i = 0; i < trans.outmsg_cnt; i++) { @@ -386,7 +417,7 @@ HttpAnswer& HttpAnswer::operator<<(AccountCell acc_c) { << "" << "\n" << "
" - << "
" + << "" << "" << "" << "" @@ -425,7 +456,7 @@ HttpAnswer& HttpAnswer::operator<<(AccountCell acc_c) { HttpAnswer& HttpAnswer::operator<<(BlockHeaderCell head_c) { *this << "
"; - vm::CellSlice cs{vm::NoVm{}, head_c.root}; + vm::CellSlice cs{vm::NoVm(), head_c.root}; auto block_id = head_c.block_id; try { auto virt_root = vm::MerkleProof::virtualize(head_c.root, 1); @@ -454,7 +485,7 @@ HttpAnswer& HttpAnswer::operator<<(BlockHeaderCell head_c) { << "block" << block_id.id.to_str() << "\n" << "roothash" << block_id.root_hash.to_hex() << "\n" << "filehash" << block_id.file_hash.to_hex() << "\n" - << "time" << info.gen_utime << "\n" + << "time" << info.gen_utime << " (" << time_to_human(info.gen_utime) << ")\n" << "lt" << info.start_lt << " .. " << info.end_lt << "\n" << "global_id" << blk.global_id << "\n" << "version" << info.version << "\n" @@ -541,7 +572,8 @@ HttpAnswer& HttpAnswer::operator<<(BlockShardsCell shards_c) { ton::ShardIdFull shard{id.workchain, id.shard}; if (ref.not_null()) { *this << "" << shard.to_str() << "top_block_id()} - << "\">" << ref->top_block_id().id.seqno << "" << ref->created_at() << "" + << "\">" << ref->top_block_id().id.seqno << "created_at()) << "\">" << ref->created_at() << "" << "" << ref->want_split_ << "" << "" << ref->want_merge_ << "" << "" << ref->before_split_ << "" @@ -676,13 +708,17 @@ std::string HttpAnswer::header() { "maximum-scale=1.0, user-scalable=no\" />\n" << "\n" << "\n" - << "\n" + << "\n" << "" - << "\n" + << "\n" << "\n" - << "\n" + << "\n" << "\n" - << "\n" + << "\n" << "\n" << "
\n" << "
"; return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -1270,8 +1279,9 @@ void HttpQuerySend::finish_query() { } return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -1434,8 +1444,9 @@ void HttpQueryRunMethod::finish_query() { return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -1494,7 +1505,7 @@ void HttpQueryStatus::finish_query() { td::uint32 j = 0; for (auto &X : results_.results) { if (!X->values_[i].is_valid()) { - A << "FAIL"; + A << "FAIL"; } else { if (m[j].count(X->values_[i].id.seqno) == 0) { m[j].insert(X->values_[i].id.seqno); @@ -1511,8 +1522,9 @@ void HttpQueryStatus::finish_query() { A << ""; return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } diff --git a/blockchain-explorer/blockchain-explorer-query.hpp b/blockchain-explorer/blockchain-explorer-query.hpp index ce7a713f..0ce52af6 100644 --- a/blockchain-explorer/blockchain-explorer-query.hpp +++ b/blockchain-explorer/blockchain-explorer-query.hpp @@ -23,7 +23,7 @@ exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/blockchain-explorer/blockchain-explorer.cpp b/blockchain-explorer/blockchain-explorer.cpp index b3776e3e..3b5346b7 100644 --- a/blockchain-explorer/blockchain-explorer.cpp +++ b/blockchain-explorer/blockchain-explorer.cpp @@ -23,12 +23,12 @@ from all source files in the program, then also delete it here. along with TON Blockchain. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl/adnl-ext-client.h" #include "adnl/utils.hpp" #include "auto/tl/ton_api_json.h" -#include "td/utils/OptionsParser.h" +#include "td/utils/OptionParser.h" #include "td/utils/Time.h" #include "td/utils/filesystem.h" #include "td/utils/format.h" @@ -52,8 +52,7 @@ #include "vm/boc.h" #include "vm/cellops.h" #include "vm/cells/MerkleProof.h" -#include "vm/continuation.h" -#include "vm/cp0.h" +#include "vm/vm.h" #include "auto/tl/lite_api.h" #include "ton/lite-tl.hpp" @@ -104,23 +103,24 @@ class HttpQueryRunner { Self->finish(nullptr); } }); - mutex_.lock(); scheduler_ptr->run_in_context_external([&]() { func(std::move(P)); }); } void finish(MHD_Response* response) { + std::unique_lock lock(mutex_); response_ = response; - mutex_.unlock(); + cond.notify_all(); } MHD_Response* wait() { - mutex_.lock(); - mutex_.unlock(); + std::unique_lock lock(mutex_); + cond.wait(lock, [&]() { return response_ != nullptr; }); return response_; } private: std::function)> func_; - MHD_Response* response_; + MHD_Response* response_ = nullptr; std::mutex mutex_; + std::condition_variable cond; }; class CoreActor : public CoreActorInterface { @@ -261,7 +261,7 @@ class CoreActor : public CoreActorInterface { CoreActor() { } - static int get_arg_iterate(void* cls, enum MHD_ValueKind kind, const char* key, const char* value) { + static MHD_RESULT get_arg_iterate(void* cls, enum MHD_ValueKind kind, const char* key, const char* value) { auto X = static_cast*>(cls); if (key && value && std::strlen(key) > 0 && std::strlen(value) > 0) { X->emplace(key, urldecode(td::Slice{value}, false)); @@ -278,7 +278,7 @@ class CoreActor : public CoreActorInterface { ~HttpRequestExtra() { MHD_destroy_post_processor(postprocessor); } - static int iterate_post(void* coninfo_cls, enum MHD_ValueKind kind, const char* key, const char* filename, + static MHD_RESULT iterate_post(void* coninfo_cls, enum MHD_ValueKind kind, const char* key, const char* filename, const char* content_type, const char* transfer_encoding, const char* data, uint64_t off, size_t size) { auto ptr = static_cast(coninfo_cls); @@ -306,10 +306,10 @@ class CoreActor : public CoreActorInterface { } } - static int process_http_request(void* cls, struct MHD_Connection* connection, const char* url, const char* method, + static MHD_RESULT process_http_request(void* cls, struct MHD_Connection* connection, const char* url, const char* method, const char* version, const char* upload_data, size_t* upload_data_size, void** ptr) { struct MHD_Response* response = nullptr; - int ret; + MHD_RESULT ret; bool is_post = false; if (std::strcmp(method, "GET") == 0) { @@ -592,9 +592,9 @@ int main(int argc, char* argv[]) { td::actor::ActorOwn x; - td::OptionsParser p; + td::OptionParser p; p.set_description("TON Blockchain explorer"); - p.add_option('h', "help", "prints_help", [&]() { + p.add_checked_option('h', "help", "prints_help", [&]() { char b[10240]; td::StringBuilder sb(td::MutableSlice{b, 10000}); sb << p; @@ -602,31 +602,31 @@ int main(int argc, char* argv[]) { std::exit(2); return td::Status::OK(); }); - p.add_option('I', "hide-ips", "hides ips from status", [&]() { + p.add_checked_option('I', "hide-ips", "hides ips from status", [&]() { td::actor::send_closure(x, &CoreActor::set_hide_ips, true); return td::Status::OK(); }); - p.add_option('u', "user", "change user", [&](td::Slice user) { return td::change_user(user); }); - p.add_option('C', "global-config", "file to read global config", [&](td::Slice fname) { + p.add_checked_option('u', "user", "change user", [&](td::Slice user) { return td::change_user(user.str()); }); + p.add_checked_option('C', "global-config", "file to read global config", [&](td::Slice fname) { td::actor::send_closure(x, &CoreActor::set_global_config, fname.str()); return td::Status::OK(); }); - p.add_option('a', "addr", "connect to ip:port", [&](td::Slice arg) { + p.add_checked_option('a', "addr", "connect to ip:port", [&](td::Slice arg) { td::IPAddress addr; TRY_STATUS(addr.init_host_port(arg.str())); td::actor::send_closure(x, &CoreActor::set_remote_addr, addr); return td::Status::OK(); }); - p.add_option('p', "pub", "remote public key", [&](td::Slice arg) { + p.add_checked_option('p', "pub", "remote public key", [&](td::Slice arg) { td::actor::send_closure(x, &CoreActor::set_remote_public_key, td::BufferSlice{arg}); return td::Status::OK(); }); - p.add_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) { + p.add_checked_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) { verbosity = td::to_integer(arg); SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + verbosity); return (verbosity >= 0 && verbosity <= 9) ? td::Status::OK() : td::Status::Error("verbosity must be 0..9"); }); - p.add_option('d', "daemonize", "set SIGHUP", [&]() { + p.add_checked_option('d', "daemonize", "set SIGHUP", [&]() { td::set_signal_handler(td::SignalType::HangUp, [](int sig) { #if TD_DARWIN || TD_LINUX close(0); @@ -635,12 +635,16 @@ int main(int argc, char* argv[]) { }).ensure(); return td::Status::OK(); }); - p.add_option('H', "http-port", "listen on http port", [&](td::Slice arg) { + p.add_checked_option('H', "http-port", "listen on http port", [&](td::Slice arg) { td::actor::send_closure(x, &CoreActor::set_http_port, td::to_integer(arg)); return td::Status::OK(); }); + p.add_checked_option('L', "local-scripts", "use local copy of ajax/bootstrap/... JS", [&]() { + local_scripts = true; + return td::Status::OK(); + }); #if TD_DARWIN || TD_LINUX - p.add_option('l', "logname", "log to file", [&](td::Slice fname) { + p.add_checked_option('l', "logname", "log to file", [&](td::Slice fname) { auto FileLog = td::FileFd::open(td::CSlice(fname.str().c_str()), td::FileFd::Flags::Create | td::FileFd::Flags::Append | td::FileFd::Flags::Write) .move_as_ok(); @@ -651,7 +655,7 @@ int main(int argc, char* argv[]) { }); #endif - vm::init_op_cp0(); + vm::init_vm().ensure(); td::actor::Scheduler scheduler({2}); scheduler_ptr = &scheduler; diff --git a/blockchain-explorer/blockchain-explorer.hpp b/blockchain-explorer/blockchain-explorer.hpp index 59e42cce..1bae362d 100644 --- a/blockchain-explorer/blockchain-explorer.hpp +++ b/blockchain-explorer/blockchain-explorer.hpp @@ -23,7 +23,7 @@ exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -31,9 +31,23 @@ #include "td/utils/buffer.h" #include "ton/ton-types.h" #include "td/utils/port/IPAddress.h" +#include #define MAX_POST_SIZE (64 << 10) +// Beginning with v0.9.71, libmicrohttpd changed the return type of most +// functions from int to enum MHD_Result +// https://git.gnunet.org/gnunet.git/tree/src/include/gnunet_mhd_compat.h +// proposes to define a constant for the return type so it works well +// with all versions of libmicrohttpd +#if MHD_VERSION >= 0x00097002 +#define MHD_RESULT enum MHD_Result +#else +#define MHD_RESULT int +#endif + +extern bool local_scripts_; + class CoreActorInterface : public td::actor::Actor { public: struct RemoteNodeStatus { diff --git a/catchain/CMakeLists.txt b/catchain/CMakeLists.txt index a57d3788..8ab9525d 100644 --- a/catchain/CMakeLists.txt +++ b/catchain/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) if (NOT OPENSSL_FOUND) find_package(OpenSSL REQUIRED) diff --git a/catchain/catchain-block.cpp b/catchain/catchain-block.cpp index 10c04eb7..e12c89f9 100644 --- a/catchain/catchain-block.cpp +++ b/catchain/catchain-block.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "catchain-block.hpp" @@ -22,8 +22,8 @@ namespace ton { namespace catchain { -std::unique_ptr CatChainBlock::create(td::uint32 src, td::uint32 fork, PublicKeyHash src_hash, - CatChainBlockHeight height, CatChainBlockHash hash, +std::unique_ptr CatChainBlock::create(td::uint32 src, td::uint32 fork, const PublicKeyHash &src_hash, + CatChainBlockHeight height, const CatChainBlockHash &hash, td::SharedSlice payload, CatChainBlock *prev, std::vector deps, std::vector vt) { @@ -31,10 +31,10 @@ std::unique_ptr CatChainBlock::create(td::uint32 src, td::uint32 std::move(deps), std::move(vt)); } -CatChainBlockImpl::CatChainBlockImpl(td::uint32 src, td::uint32 fork, PublicKeyHash src_hash, - CatChainBlockHeight height, CatChainBlockHash hash, td::SharedSlice payload, - CatChainBlock *prev, std::vector deps, - std::vector vt) +CatChainBlockImpl::CatChainBlockImpl(td::uint32 src, td::uint32 fork, const PublicKeyHash &src_hash, + CatChainBlockHeight height, const CatChainBlockHash &hash, + td::SharedSlice payload, CatChainBlock *prev, + std::vector deps, std::vector vt) : src_(src) , fork_(fork) , src_hash_(src_hash) @@ -47,7 +47,7 @@ CatChainBlockImpl::CatChainBlockImpl(td::uint32 src, td::uint32 fork, PublicKeyH } bool CatChainBlockImpl::is_descendant_of(CatChainBlock *block) { - auto fork = block->fork(); + td::uint32 fork = block->fork(); if (fork >= vt_.size()) { return false; } diff --git a/catchain/catchain-block.hpp b/catchain/catchain-block.hpp index c6e10348..d37b4dc5 100644 --- a/catchain/catchain-block.hpp +++ b/catchain/catchain-block.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -103,8 +103,8 @@ class CatChainBlockImpl : public CatChainBlock { bool is_descendant_of(CatChainBlock *block) override; - CatChainBlockImpl(td::uint32 src, td::uint32 fork, PublicKeyHash src_hash, CatChainBlockHeight height, - CatChainBlockHash hash, td::SharedSlice payload, CatChainBlock *prev, + CatChainBlockImpl(td::uint32 src, td::uint32 fork, const PublicKeyHash &src_hash, CatChainBlockHeight height, + const CatChainBlockHash &hash, td::SharedSlice payload, CatChainBlock *prev, std::vector deps, std::vector vt); }; diff --git a/catchain/catchain-received-block.cpp b/catchain/catchain-received-block.cpp index 711ee29c..5996e967 100644 --- a/catchain/catchain-received-block.cpp +++ b/catchain/catchain-received-block.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include @@ -33,7 +33,7 @@ void CatChainReceivedBlockImpl::initialize(tl_object_ptr 0); + CHECK(!payload_.empty()); prev_ = dynamic_cast(chain_->create_block(std::move(block->data_->prev_))); CHECK(prev_ != nullptr); @@ -51,7 +51,7 @@ void CatChainReceivedBlockImpl::initialize(tl_object_ptris_ill()) { set_ill(); return; @@ -63,17 +63,17 @@ void CatChainReceivedBlockImpl::initialize(tl_object_ptrdelivered()) { pending_deps++; } else { - update_deps(prev_); + update_vt(prev_); } if (!prev_->delivered()) { prev_->add_rev_dep(this); } } - for (auto &X : block_deps_) { + for (CatChainReceivedBlockImpl *X : block_deps_) { if (!X->delivered()) { pending_deps++; } else { - update_deps(X); + update_vt(X); } if (!X->delivered()) { X->add_rev_dep(this); @@ -121,11 +121,11 @@ void CatChainReceivedBlockImpl::initialize_fork() { } } - if (deps_.size() < fork_id_ + 1) { - deps_.resize(fork_id_ + 1, 0); + if (vt_.size() < fork_id_ + 1) { + vt_.resize(fork_id_ + 1, 0); } - CHECK(deps_[fork_id_] < height_); - deps_[fork_id_] = height_; + CHECK(vt_[fork_id_] < height_); + vt_[fork_id_] = height_; } void CatChainReceivedBlockImpl::pre_deliver(ton_api::catchain_block_data_fork &b) { @@ -153,7 +153,7 @@ void CatChainReceivedBlockImpl::pre_deliver(ton_api::catchain_block_data_fork &b return; } - auto S = chain_->get_source(b.left_->src_); + CatChainReceiverSource *S = chain_->get_source(b.left_->src_); S->on_found_fork_proof( create_serialize_tl_object(std::move(b.left_), std::move(b.right_))); S->blame(fork_id_, height_); @@ -173,15 +173,15 @@ void CatChainReceivedBlockImpl::pre_deliver() { CHECK(pending_deps_ == 0); CHECK(in_db_); - auto M = chain_->get_source(source_id_); + CatChainReceiverSource *M = chain_->get_source(source_id_); - auto d = prev_ ? &prev_->deps_ : nullptr; + std::vector *d = prev_ ? &prev_->vt_ : nullptr; - for (auto &X : block_deps_) { - auto S = chain_->get_source(X->get_source_id()); - auto &f = S->get_forks(); + for (CatChainReceivedBlockImpl *X : block_deps_) { + CatChainReceiverSource *S = chain_->get_source(X->get_source_id()); + const std::vector &f = S->get_forks(); if (d) { - auto &dd = *d; + const std::vector &dd = *d; if (X->get_fork_id() < dd.size() && X->get_height() <= dd[X->get_fork_id()]) { VLOG(CATCHAIN_WARNING) << this << ": has direct dep from source " << X->get_source_id() << " and prev block " << " has newer indirect dep"; @@ -190,8 +190,8 @@ void CatChainReceivedBlockImpl::pre_deliver() { } } if (S->blamed() && d) { - auto &dd = *d; - for (auto x : f) { + const std::vector &dd = *d; + for (td::uint32 x : f) { if (x != X->get_fork_id() && dd.size() > x && dd[x] > 0) { VLOG(CATCHAIN_WARNING) << this << ": has direct dep from source " << X->get_source_id() << " and prev block " << " has indirect dep to another fork of this source " << x << " " << X->get_fork_id() @@ -201,7 +201,7 @@ void CatChainReceivedBlockImpl::pre_deliver() { return; } } - auto v = S->get_blamed_heights(); + std::vector v = S->get_blamed_heights(); for (size_t i = 0; i < v.size() && i < dd.size(); i++) { if (v[i] > 0 && dd[i] >= v[i]) { @@ -220,7 +220,7 @@ void CatChainReceivedBlockImpl::pre_deliver() { if (X.is_error()) { is_custom_ = true; } else { - ton_api::downcast_call(*X.move_as_ok().get(), [Self = this](auto &obj) { Self->pre_deliver(obj); }); + ton_api::downcast_call(*X.move_as_ok(), [Self = this](auto &obj) { Self->pre_deliver(obj); }); } } @@ -237,7 +237,7 @@ void CatChainReceivedBlockImpl::deliver() { state_ = bs_delivered; VLOG(CATCHAIN_DEBUG) << this << ": delivered"; - for (auto &B : rev_deps_) { + for (CatChainReceivedBlockImpl *B : rev_deps_) { B->dep_delivered(this); } rev_deps_.clear(); @@ -250,10 +250,10 @@ void CatChainReceivedBlockImpl::set_ill() { return; } VLOG(CATCHAIN_WARNING) << this << ": got ill"; - auto M = chain_->get_source(source_id_); + CatChainReceiverSource *M = chain_->get_source(source_id_); M->blame(); state_ = bs_ill; - for (auto &B : rev_deps_) { + for (CatChainReceivedBlockImpl *B : rev_deps_) { B->dep_ill(this); } } @@ -262,14 +262,14 @@ void CatChainReceivedBlockImpl::dep_ill(CatChainReceivedBlockImpl *block) { set_ill(); } -void CatChainReceivedBlockImpl::update_deps(CatChainReceivedBlockImpl *block) { - auto &d = block->deps_; - if (d.size() > deps_.size()) { - deps_.resize(d.size(), 0); +void CatChainReceivedBlockImpl::update_vt(CatChainReceivedBlockImpl *block) { + const std::vector &d = block->vt_; + if (d.size() > vt_.size()) { + vt_.resize(d.size(), 0); } for (size_t i = 0; i < d.size(); i++) { - if (deps_[i] < d[i]) { - deps_[i] = d[i]; + if (vt_[i] < d[i]) { + vt_[i] = d[i]; } } } @@ -279,7 +279,7 @@ void CatChainReceivedBlockImpl::dep_delivered(CatChainReceivedBlockImpl *block) return; } CHECK(!block->is_ill()); - update_deps(block); + update_vt(block); pending_deps_--; if (pending_deps_ == 0 && in_db_) { schedule(); @@ -332,35 +332,37 @@ void CatChainReceivedBlockImpl::find_pending_deps(std::vector if (prev_) { prev_->find_pending_deps(vec, max_size); } - for (auto &X : block_deps_) { + for (CatChainReceivedBlockImpl *X : block_deps_) { X->find_pending_deps(vec, max_size); } } -tl_object_ptr CatChainReceivedBlock::block_id(CatChainReceiver *chain, - tl_object_ptr &block, - td::Slice payload) { +tl_object_ptr CatChainReceivedBlock::block_id( + const CatChainReceiver *chain, const tl_object_ptr &block, const td::Slice &payload) { + td::Bits256 hash = data_payload_hash(chain, block->data_, payload); return create_tl_object(block->incarnation_, chain->get_source_hash(block->src_).tl(), - block->height_, sha256_bits256(payload)); + block->height_, hash); } tl_object_ptr CatChainReceivedBlock::block_id( - CatChainReceiver *chain, tl_object_ptr &block) { + const CatChainReceiver *chain, const tl_object_ptr &block) { return create_tl_object( chain->get_incarnation(), chain->get_source_hash(block->src_).tl(), block->height_, block->data_hash_); } -CatChainBlockHash CatChainReceivedBlock::block_hash(CatChainReceiver *chain, - tl_object_ptr &block, td::Slice payload) { +CatChainBlockHash CatChainReceivedBlock::block_hash(const CatChainReceiver *chain, + const tl_object_ptr &block, + const td::Slice &payload) { return get_tl_object_sha_bits256(block_id(chain, block, payload)); } -CatChainBlockHash CatChainReceivedBlock::block_hash(CatChainReceiver *chain, - tl_object_ptr &block) { +CatChainBlockHash CatChainReceivedBlock::block_hash(const CatChainReceiver *chain, + const tl_object_ptr &block) { return get_tl_object_sha_bits256(block_id(chain, block)); } -td::Status CatChainReceivedBlock::pre_validate_block(CatChainReceiver *chain, - tl_object_ptr &block, td::Slice payload) { +td::Status CatChainReceivedBlock::pre_validate_block(const CatChainReceiver *chain, + const tl_object_ptr &block, + const td::Slice &payload) { CHECK(block->incarnation_ == chain->get_incarnation()); if (block->height_ <= 0) { return td::Status::Error(ErrorCode::protoviolation, std::string("bad height ") + std::to_string(block->height_)); @@ -397,7 +399,7 @@ td::Status CatChainReceivedBlock::pre_validate_block(CatChainReceiver *chain, std::set used; used.insert(block->src_); - for (auto &X : block->data_->deps_) { + for (const auto &X : block->data_->deps_) { if (used.find(X->src_) != used.end()) { return td::Status::Error(ErrorCode::protoviolation, "two deps from same source"); } @@ -405,19 +407,19 @@ td::Status CatChainReceivedBlock::pre_validate_block(CatChainReceiver *chain, } TRY_STATUS(chain->validate_block_sync(block->data_->prev_)); - for (auto &X : block->data_->deps_) { + for (const auto &X : block->data_->deps_) { TRY_STATUS(chain->validate_block_sync(X)); } - if (payload.size() == 0) { + if (payload.empty()) { return td::Status::Error(ErrorCode::protoviolation, "empty payload"); } return td::Status::OK(); } -td::Status CatChainReceivedBlock::pre_validate_block(CatChainReceiver *chain, - tl_object_ptr &block) { +td::Status CatChainReceivedBlock::pre_validate_block(const CatChainReceiver *chain, + const tl_object_ptr &block) { if (block->height_ < 0) { return td::Status::Error(ErrorCode::protoviolation, std::string("bad height ") + std::to_string(block->height_)); } @@ -430,7 +432,7 @@ td::Status CatChainReceivedBlock::pre_validate_block(CatChainReceiver *chain, return td::Status::Error(ErrorCode::protoviolation, std::string("bad src (first block) ") + std::to_string(block->src_)); } - if (block->data_hash_ != chain->get_incarnation() || block->signature_.size() != 0) { + if (block->data_hash_ != chain->get_incarnation() || !block->signature_.empty()) { return td::Status::Error(ErrorCode::protoviolation, std::string("bad first block")); } } @@ -443,9 +445,10 @@ tl_object_ptr CatChainReceivedBlockImpl::export_tl() co CHECK(height_ > 0); std::vector> deps; - for (auto &B : block_deps_) { + for (CatChainReceivedBlockImpl *B : block_deps_) { deps.push_back(B->export_tl_dep()); } + CHECK(deps.size() <= chain_->opts().max_deps) return create_tl_object( chain_->get_incarnation(), source_id_, height_, @@ -454,35 +457,34 @@ tl_object_ptr CatChainReceivedBlockImpl::export_tl() co } tl_object_ptr CatChainReceivedBlockImpl::export_tl_dep() const { - return create_tl_object(source_id_, height_, data_hash_, + return create_tl_object(source_id_, height_, data_payload_hash_, signature_.clone_as_buffer_slice()); } -CatChainReceivedBlockImpl::CatChainReceivedBlockImpl(td::uint32 source_id, CatChainSessionId hash, +CatChainReceivedBlockImpl::CatChainReceivedBlockImpl(td::uint32 source_id, const CatChainBlockPayloadHash &hash, CatChainReceiver *chain) { chain_ = chain; state_ = bs_delivered; fork_id_ = 0; source_id_ = source_id; - data_ = nullptr; - prev_ = nullptr; height_ = 0; - data_hash_ = hash; - hash_ = get_tl_object_sha_bits256(create_tl_object( - chain->get_incarnation(), chain->get_incarnation(), height_, data_hash_)); + data_payload_hash_ = hash; + block_id_hash_ = get_tl_object_sha_bits256(create_tl_object( + chain->get_incarnation(), chain->get_incarnation(), height_, data_payload_hash_)); } CatChainReceivedBlockImpl::CatChainReceivedBlockImpl(tl_object_ptr block, td::SharedSlice payload, CatChainReceiver *chain) { chain_ = chain; - data_hash_ = sha256_bits256(payload.as_slice()); - hash_ = get_tl_object_sha_bits256(create_tl_object( - block->incarnation_, chain->get_source_hash(block->src_).tl(), block->height_, data_hash_)); + data_payload_hash_ = data_payload_hash(chain, block->data_, payload); + block_id_hash_ = get_tl_object_sha_bits256(create_tl_object( + block->incarnation_, chain->get_source_hash(block->src_).tl(), block->height_, data_payload_hash_)); height_ = block->height_; source_id_ = block->src_; + CHECK(height_ <= get_max_block_height(chain->opts(), chain->get_sources_cnt())); - auto S = chain_->get_source(source_id_); + CatChainReceiverSource *S = chain_->get_source(source_id_); S->on_new_block(this); initialize(std::move(block), std::move(payload)); @@ -491,14 +493,14 @@ CatChainReceivedBlockImpl::CatChainReceivedBlockImpl(tl_object_ptr block, CatChainReceiver *chain) { chain_ = chain; - data_hash_ = block->data_hash_; + data_payload_hash_ = block->data_hash_; source_id_ = block->src_; signature_ = td::SharedSlice{block->signature_.as_slice()}; - hash_ = get_tl_object_sha_bits256(create_tl_object( - chain_->get_incarnation(), chain_->get_source_hash(source_id_).tl(), block->height_, data_hash_)); + block_id_hash_ = get_tl_object_sha_bits256(create_tl_object( + chain_->get_incarnation(), chain_->get_source_hash(source_id_).tl(), block->height_, data_payload_hash_)); height_ = block->height_; - auto S = chain_->get_source(source_id_); + CatChainReceiverSource *S = chain_->get_source(source_id_); S->on_new_block(this); } @@ -513,9 +515,24 @@ std::unique_ptr CatChainReceivedBlock::create(tl_object_p } std::unique_ptr CatChainReceivedBlock::create_root(td::uint32 source_id, - CatChainSessionId data_hash, + CatChainSessionId session_id, CatChainReceiver *chain) { - return std::make_unique(source_id, data_hash, chain); + return std::make_unique(source_id, session_id, chain); +} + +CatChainBlockPayloadHash CatChainReceivedBlock::data_payload_hash( + const CatChainReceiver *chain, const tl_object_ptr &data, const td::Slice &payload) { + td::Bits256 hash = sha256_bits256(payload); + if (chain->opts().block_hash_covers_data) { + td::Bits256 data_hash = get_tl_object_sha_bits256(data); + char buf[32 * 2]; + CHECK(hash.as_array().size() == 32 && data_hash.as_array().size() == 32); + std::copy(hash.as_array().begin(), hash.as_array().end(), buf); + std::copy(data_hash.as_array().begin(), data_hash.as_array().end(), buf + 32); + return sha256_bits256(td::Slice(buf, buf + 64)); + } else { + return hash; + } } } // namespace catchain diff --git a/catchain/catchain-received-block.h b/catchain/catchain-received-block.h index fc1d5522..3c4f5d76 100644 --- a/catchain/catchain-received-block.h +++ b/catchain/catchain-received-block.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -30,7 +30,6 @@ namespace catchain { class CatChainReceiver; class CatChainReceiverSource; -class CatChainReceiverFork; class CatChainReceivedBlock { public: @@ -43,7 +42,7 @@ class CatChainReceivedBlock { virtual CatChainReceivedBlock *get_prev() const = 0; virtual CatChainBlockHash get_prev_hash() const = 0; - virtual const std::vector &get_deps() const = 0; + virtual const std::vector &get_vt() const = 0; virtual std::vector get_dep_hashes() const = 0; virtual CatChainReceiver *get_chain() const = 0; @@ -56,6 +55,8 @@ class CatChainReceivedBlock { virtual void find_pending_deps(std::vector &vec, td::uint32 max_size) const = 0; + virtual bool has_rev_deps() const = 0; + public: // state virtual bool initialized() const = 0; @@ -76,20 +77,28 @@ class CatChainReceivedBlock { td::SharedSlice payload, CatChainReceiver *chain); static std::unique_ptr create(tl_object_ptr block, CatChainReceiver *chain); - static std::unique_ptr create_root(td::uint32 source_id, CatChainBlockPayloadHash data_hash, + static std::unique_ptr create_root(td::uint32 source_id, CatChainSessionId session_id, CatChainReceiver *chain); - static tl_object_ptr block_id(CatChainReceiver *chain, - tl_object_ptr &block, - td::Slice payload); - static tl_object_ptr block_id(CatChainReceiver *chain, - tl_object_ptr &block); - static CatChainBlockHash block_hash(CatChainReceiver *chain, tl_object_ptr &block, - td::Slice payload); - static CatChainBlockHash block_hash(CatChainReceiver *chain, tl_object_ptr &block); - static td::Status pre_validate_block(CatChainReceiver *chain, tl_object_ptr &block, - td::Slice payload); - static td::Status pre_validate_block(CatChainReceiver *chain, tl_object_ptr &block); + static tl_object_ptr block_id(const CatChainReceiver *chain, + const tl_object_ptr &block, + const td::Slice &payload); + static tl_object_ptr block_id(const CatChainReceiver *chain, + const tl_object_ptr &block); + static CatChainBlockHash block_hash(const CatChainReceiver *chain, + const tl_object_ptr &block, + const td::Slice &payload); + static CatChainBlockHash block_hash(const CatChainReceiver *chain, + const tl_object_ptr &block); + static td::Status pre_validate_block(const CatChainReceiver *chain, + const tl_object_ptr &block, + const td::Slice &payload); + static td::Status pre_validate_block(const CatChainReceiver *chain, + const tl_object_ptr &block); + static CatChainBlockPayloadHash data_payload_hash(const CatChainReceiver *chain, + const tl_object_ptr &data, + const td::Slice &payload); + virtual ~CatChainReceivedBlock() = default; }; diff --git a/catchain/catchain-received-block.hpp b/catchain/catchain-received-block.hpp index 914fa91c..f1270ab0 100644 --- a/catchain/catchain-received-block.hpp +++ b/catchain/catchain-received-block.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -26,15 +26,14 @@ namespace catchain { class CatChainReceiver; class CatChainReceiverSource; -class CatChainReceiverFork; -class CatChainReceivedBlockImpl : public CatChainReceivedBlock { +class CatChainReceivedBlockImpl final : public CatChainReceivedBlock { public: const td::SharedSlice &get_payload() const override { return payload_; } CatChainBlockHash get_hash() const override { - return hash_; + return block_id_hash_; } const td::SharedSlice &get_signature() const override { return signature_; @@ -46,8 +45,8 @@ class CatChainReceivedBlockImpl : public CatChainReceivedBlock { CatChainReceivedBlock *get_prev() const override; CatChainBlockHash get_prev_hash() const override; - const std::vector &get_deps() const override { - return deps_; + const std::vector &get_vt() const override { + return vt_; } std::vector get_dep_hashes() const override; @@ -69,6 +68,10 @@ class CatChainReceivedBlockImpl : public CatChainReceivedBlock { void find_pending_deps(std::vector &vec, td::uint32 max_size) const override; + bool has_rev_deps() const override { + return !rev_deps_.empty(); + } + public: bool initialized() const override { return state_ >= bs_initialized; @@ -114,7 +117,7 @@ class CatChainReceivedBlockImpl : public CatChainReceivedBlock { CatChainReceiver *chain); CatChainReceivedBlockImpl(tl_object_ptr block, CatChainReceiver *chain); - CatChainReceivedBlockImpl(td::uint32 source_id, CatChainSessionId hash, CatChainReceiver *chain); + CatChainReceivedBlockImpl(td::uint32 source_id, const CatChainSessionId &hash, CatChainReceiver *chain); private: enum State { @@ -124,31 +127,28 @@ class CatChainReceivedBlockImpl : public CatChainReceivedBlock { bs_delivered, } state_ = bs_none; - void update_deps(CatChainReceivedBlockImpl *block); + void update_vt(CatChainReceivedBlockImpl *block); void add_rev_dep(CatChainReceivedBlockImpl *block); - void add_child_dep(CatChainReceivedBlockImpl *block); void initialize_fork(); - void on_ready_to_deliver(); td::uint32 fork_id_{0}; td::uint32 source_id_; CatChainReceiver *chain_; - tl_object_ptr data_; td::SharedSlice payload_; - CatChainBlockHash hash_; - CatChainBlockPayloadHash data_hash_; + CatChainBlockHash block_id_hash_{}; + CatChainBlockPayloadHash data_payload_hash_{}; - CatChainReceivedBlockImpl *prev_; + CatChainReceivedBlockImpl *prev_ = nullptr; CatChainBlockHeight height_; CatChainReceivedBlockImpl *next_ = nullptr; std::vector block_deps_; - std::vector deps_; + std::vector vt_; td::SharedSlice signature_; diff --git a/catchain/catchain-receiver-interface.h b/catchain/catchain-receiver-interface.h index 9580c3fb..bc02832a 100644 --- a/catchain/catchain-receiver-interface.h +++ b/catchain/catchain-receiver-interface.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -35,34 +35,34 @@ class CatChainReceiverInterface : public td::actor::Actor { CatChainBlockHash prev, std::vector deps, std::vector vt, td::SharedSlice data) = 0; virtual void blame(td::uint32 src_id) = 0; - virtual void on_custom_message(PublicKeyHash src, td::BufferSlice data) = 0; - virtual void on_custom_query(PublicKeyHash src, td::BufferSlice data, td::Promise promise) = 0; - virtual void on_broadcast(PublicKeyHash src, td::BufferSlice data) = 0; + virtual void on_custom_query(const PublicKeyHash &src, td::BufferSlice data, + td::Promise promise) = 0; + virtual void on_broadcast(const PublicKeyHash &src, td::BufferSlice data) = 0; virtual void start() = 0; virtual ~Callback() = default; }; virtual void add_block(td::BufferSlice payload, std::vector deps) = 0; virtual void debug_add_fork(td::BufferSlice payload, CatChainBlockHeight height, std::vector deps) = 0; - virtual void blame_node(td::uint32 idx) = 0; virtual void send_fec_broadcast(td::BufferSlice data) = 0; - virtual void send_custom_query_data(PublicKeyHash dst, std::string name, td::Promise promise, + virtual void send_custom_query_data(const PublicKeyHash &dst, std::string name, td::Promise promise, td::Timestamp timeout, td::BufferSlice query) = 0; - virtual void send_custom_query_data_via(PublicKeyHash dst, std::string name, td::Promise promise, - td::Timestamp timeout, td::BufferSlice query, td::uint64 max_answer_size, + virtual void send_custom_query_data_via(const PublicKeyHash &dst, std::string name, + td::Promise promise, td::Timestamp timeout, + td::BufferSlice query, td::uint64 max_answer_size, td::actor::ActorId via) = 0; - virtual void send_custom_message_data(PublicKeyHash dst, td::BufferSlice query) = 0; + virtual void send_custom_message_data(const PublicKeyHash &dst, td::BufferSlice query) = 0; + virtual void on_blame_processed(td::uint32 source_id) = 0; virtual void destroy() = 0; - static td::actor::ActorOwn create(std::unique_ptr callback, CatChainOptions opts, - td::actor::ActorId keyring, - td::actor::ActorId adnl, - td::actor::ActorId overlay_manager, - std::vector ids, PublicKeyHash local_id, - CatChainSessionId unique_hash, std::string db_root); + static td::actor::ActorOwn create( + std::unique_ptr callback, const CatChainOptions &opts, td::actor::ActorId keyring, + td::actor::ActorId adnl, td::actor::ActorId overlay_manager, + const std::vector &ids, const PublicKeyHash &local_id, const CatChainSessionId &unique_hash, + std::string db_root, std::string db_suffix, bool allow_unsafe_self_blocks_resync); - virtual ~CatChainReceiverInterface() = default; + ~CatChainReceiverInterface() override = default; }; } // namespace catchain diff --git a/catchain/catchain-receiver-source.cpp b/catchain/catchain-receiver-source.cpp index 502843f9..e758b335 100644 --- a/catchain/catchain-receiver-source.cpp +++ b/catchain/catchain-receiver-source.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "catchain-receiver-source.hpp" #include "common/errorlog.h" @@ -24,10 +24,10 @@ namespace ton { namespace catchain { td::uint32 CatChainReceiverSourceImpl::add_fork() { - if (fork_ids_.size() > 0) { + if (!fork_ids_.empty()) { blame(); } - auto F = chain_->add_fork(); + td::uint32 F = chain_->add_fork(); CHECK(F > 0); fork_ids_.push_back(F); @@ -60,21 +60,22 @@ td::Result> CatChainReceiverSource::crea void CatChainReceiverSourceImpl::blame(td::uint32 fork, CatChainBlockHeight height) { blame(); - if (blamed_heights_.size() > 0) { - if (blamed_heights_.size() <= fork) { - blamed_heights_.resize(fork + 1, 0); - } - if (blamed_heights_[fork] == 0 || blamed_heights_[fork] > height) { - VLOG(CATCHAIN_INFO) << this << ": blamed at " << fork << " " << height; - blamed_heights_[fork] = height; - } + // if (!blamed_heights_.empty()) { + if (blamed_heights_.size() <= fork) { + blamed_heights_.resize(fork + 1, 0); } + if (blamed_heights_[fork] == 0 || blamed_heights_[fork] > height) { + VLOG(CATCHAIN_INFO) << this << ": blamed at " << fork << " " << height; + blamed_heights_[fork] = height; + } + // } } void CatChainReceiverSourceImpl::blame() { if (!blamed_) { LOG(ERROR) << this << ": CATCHAIN: blaming source " << id_; blocks_.clear(); + delivered_height_ = 0; chain_->on_blame(id_); } blamed_ = true; @@ -129,19 +130,6 @@ void CatChainReceiverSourceImpl::block_delivered(CatChainBlockHeight height) { } } -td::Status CatChainReceiverSourceImpl::validate_dep_sync(tl_object_ptr &dep) { - auto S = std::move(dep->signature_); - auto str = serialize_tl_object(dep, true); - dep->signature_ = std::move(S); - - auto R = encryptor_sync_->check_signature(str.as_slice(), dep->signature_.as_slice()); - if (R.is_error()) { - return R.move_as_error(); - } - - return td::Status::OK(); -} - void CatChainReceiverSourceImpl::on_new_block(CatChainReceivedBlock *block) { if (fork_is_found()) { return; @@ -156,7 +144,7 @@ void CatChainReceiverSourceImpl::on_new_block(CatChainReceivedBlock *block) { on_found_fork_proof(create_serialize_tl_object(block->export_tl_dep(), it->second->export_tl_dep()) .as_slice()); - chain_->add_prepared_event(fork_proof()); + chain_->on_found_fork_proof(id_, fork_proof()); } blame(); return; @@ -164,7 +152,7 @@ void CatChainReceiverSourceImpl::on_new_block(CatChainReceivedBlock *block) { blocks_[block->get_height()] = block; } -void CatChainReceiverSourceImpl::on_found_fork_proof(td::Slice proof) { +void CatChainReceiverSourceImpl::on_found_fork_proof(const td::Slice &proof) { if (!fork_is_found()) { fetch_tl_object(proof, true).ensure(); fork_proof_ = td::SharedSlice{proof}; @@ -174,6 +162,15 @@ void CatChainReceiverSourceImpl::on_found_fork_proof(td::Slice proof) { } } +bool CatChainReceiverSourceImpl::allow_send_block(CatChainBlockHash hash) { + td::uint32 count = ++block_requests_count_[hash]; + if (count > MAX_BLOCK_REQUESTS) { + VLOG(CATCHAIN_INFO) << this << ": node requested block " << hash << " " << count << " times"; + return false; + } + return true; +} + } // namespace catchain } // namespace ton diff --git a/catchain/catchain-receiver-source.h b/catchain/catchain-receiver-source.h index d11d7c74..55035e77 100644 --- a/catchain/catchain-receiver-source.h +++ b/catchain/catchain-receiver-source.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -53,12 +53,17 @@ class CatChainReceiverSource { virtual void block_received(CatChainBlockHeight height) = 0; virtual void block_delivered(CatChainBlockHeight height) = 0; - virtual td::Status validate_dep_sync(tl_object_ptr &dep) = 0; + virtual bool has_unreceived() const = 0; + virtual bool has_undelivered() const = 0; + virtual void on_new_block(CatChainReceivedBlock *block) = 0; - virtual void on_found_fork_proof(td::Slice fork) = 0; + virtual void on_found_fork_proof(const td::Slice &fork) = 0; virtual td::BufferSlice fork_proof() const = 0; virtual bool fork_is_found() const = 0; + // One block can be sent to one node only a limited number of times to prevent DoS + virtual bool allow_send_block(CatChainBlockHash hash) = 0; + static td::Result> create(CatChainReceiver *chain, PublicKey pub_key, adnl::AdnlNodeIdShort adnl_id, td::uint32 id); diff --git a/catchain/catchain-receiver-source.hpp b/catchain/catchain-receiver-source.hpp index ad6935b9..cf08c421 100644 --- a/catchain/catchain-receiver-source.hpp +++ b/catchain/catchain-receiver-source.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -23,6 +23,7 @@ #include "catchain-receiver-source.h" #include "catchain-receiver.h" #include "catchain-received-block.h" +#include namespace ton { @@ -78,11 +79,23 @@ class CatChainReceiverSourceImpl : public CatChainReceiverSource { CatChainBlockHeight received_height() const override { return received_height_; } + bool has_unreceived() const override { + if (blamed()) { + return true; + } + if (blocks_.empty()) { + return false; + } + CHECK(blocks_.rbegin()->second->get_height() >= received_height_); + return blocks_.rbegin()->second->get_height() > received_height_; + } + bool has_undelivered() const override { + return delivered_height_ < received_height_; + } CatChainReceivedBlock *get_block(CatChainBlockHeight height) const override; - td::Status validate_dep_sync(tl_object_ptr &dep) override; void on_new_block(CatChainReceivedBlock *block) override; - void on_found_fork_proof(td::Slice proof) override; + void on_found_fork_proof(const td::Slice &proof) override; bool fork_is_found() const override { return !fork_proof_.empty(); } @@ -90,7 +103,7 @@ class CatChainReceiverSourceImpl : public CatChainReceiverSource { if (!fork_proof_.empty()) { return fork_proof_.clone_as_buffer_slice(); } else { - return td::BufferSlice(); + return {}; } } @@ -98,6 +111,8 @@ class CatChainReceiverSourceImpl : public CatChainReceiverSource { return chain_; } + bool allow_send_block(CatChainBlockHash hash) override; + CatChainReceiverSourceImpl(CatChainReceiver *chain, PublicKey source, adnl::AdnlNodeIdShort adnl_id, td::uint32 id); private: @@ -117,6 +132,11 @@ class CatChainReceiverSourceImpl : public CatChainReceiverSource { CatChainBlockHeight delivered_height_ = 0; CatChainBlockHeight received_height_ = 0; + + std::map block_requests_count_; + // One block can be sent to one node up to 5 times + + static const td::uint32 MAX_BLOCK_REQUESTS = 5; }; } // namespace catchain diff --git a/catchain/catchain-receiver.cpp b/catchain/catchain-receiver.cpp index 7b8a6415..82779e3b 100644 --- a/catchain/catchain-receiver.cpp +++ b/catchain/catchain-receiver.cpp @@ -14,9 +14,10 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include +#include #include "td/actor/PromiseFuture.h" #include "td/utils/Random.h" #include "td/db/RocksDb.h" @@ -30,6 +31,25 @@ namespace ton { namespace catchain { +static const td::uint32 MAX_NEIGHBOURS = 5; +static const double EXPECTED_UNSAFE_INITIAL_SYNC_DURATION = 300.0; +static const double EXPECTED_INITIAL_SYNC_DURATION = 5.0; +static const td::uint32 OVERLAY_MAX_ALLOWED_PACKET_SIZE = 16 * 1024 * 1024; +static const double NEIGHBOURS_ROTATE_INTERVAL_MIN = 60; +static const double NEIGHBOURS_ROTATE_INTERVAL_MAX = 120; +static const td::uint32 MAX_QUERY_BLOCKS = 100; +static const td::uint32 MAX_QUERY_HEIGHT = 100; +static const td::uint32 GET_DIFFERENCE_MAX_SEND = 100; +static const double GET_DIFFERENCE_TIMEOUT = 5.0; +static const double GET_BLOCK_TIMEOUT = 2.0; +static const td::uint32 MAX_PENDING_DEPS = 16; +static const double EXPECTED_INITIAL_SYNC_DURATION_WITH_UNPROCESSED = 60.0; +static const double SYNC_INTERVAL_MIN = 0.1; +static const double SYNC_INTERVAL_MAX = 0.2; +static const td::uint32 SYNC_ITERATIONS = 3; +static const double DESTROY_DB_DELAY = 1.0; +static const td::uint32 DESTROY_DB_MAX_ATTEMPTS = 10; + PublicKeyHash CatChainReceiverImpl::get_source_hash(td::uint32 source_id) const { CHECK(source_id < sources_.size()); return sources_[source_id]->get_hash(); @@ -45,18 +65,19 @@ void CatChainReceiverImpl::deliver_block(CatChainReceivedBlock *block) { << " custom=" << block->is_custom(); callback_->new_block(block->get_source_id(), block->get_fork_id(), block->get_hash(), block->get_height(), block->get_height() == 1 ? CatChainBlockHash::zero() : block->get_prev_hash(), - block->get_dep_hashes(), block->get_deps(), + block->get_dep_hashes(), block->get_vt(), block->is_custom() ? block->get_payload().clone() : td::SharedSlice()); std::vector v; - for (auto it : neighbours_) { - auto S = get_source(it); + for (td::uint32 it : neighbours_) { + CatChainReceiverSource *S = get_source(it); v.push_back(S->get_adnl_id()); } auto update = create_tl_object(block->export_tl()); - auto D = serialize_tl_object(update, true, block->get_payload().as_slice()); + td::BufferSlice D = serialize_tl_object(update, true, block->get_payload().as_slice()); + CHECK(D.size() <= opts_.max_serialized_block_size); td::actor::send_closure(overlay_manager_, &overlay::Overlays::send_multiple_messages, std::move(v), get_source(local_idx_)->get_adnl_id(), overlay_id_, std::move(D)); @@ -64,8 +85,8 @@ void CatChainReceiverImpl::deliver_block(CatChainReceivedBlock *block) { void CatChainReceiverImpl::receive_block(adnl::AdnlNodeIdShort src, tl_object_ptr block, td::BufferSlice payload) { - auto id = CatChainReceivedBlock::block_hash(this, block, payload); - auto B = get_block(id); + CatChainBlockHash id = CatChainReceivedBlock::block_hash(this, block, payload); + CatChainReceivedBlock *B = get_block(id); if (B && B->initialized()) { return; } @@ -76,14 +97,44 @@ void CatChainReceiverImpl::receive_block(adnl::AdnlNodeIdShort src, tl_object_pt return; } - auto S = validate_block_sync(block, payload.as_slice()); + td::uint64 max_block_height = get_max_block_height(opts_, sources_.size()); + if ((td::uint32)block->height_ > max_block_height) { + VLOG(CATCHAIN_WARNING) << this << ": received too many blocks from " << src + << " (limit=" << max_block_height << ")"; + return; + } + + td::uint32 src_id = block->src_; + if (src_id >= get_sources_cnt()) { + VLOG(CATCHAIN_WARNING) << this << ": received broken block from " << src << ": bad src " << block->src_; + return; + } + CatChainReceiverSource *source = get_source(src_id); + if (source->fork_is_found()) { + if (B == nullptr || !B->has_rev_deps()) { + VLOG(CATCHAIN_WARNING) << this << ": dropping block from source " << src_id << ": source has a fork"; + return; + } + } + + td::Status S = validate_block_sync(block, payload.as_slice()); if (S.is_error()) { VLOG(CATCHAIN_WARNING) << this << ": received broken block from " << src << ": " << S.move_as_error(); return; } - auto raw_data = serialize_tl_object(block, true, payload.as_slice()); + if (block->src_ == static_cast(local_idx_)) { + if (!allow_unsafe_self_blocks_resync_ || started_) { + LOG(FATAL) << this << ": received unknown SELF block from " << src + << " (unsafe=" << allow_unsafe_self_blocks_resync_ << ")"; + } else { + LOG(ERROR) << this << ": received unknown SELF block from " << src << ". UPDATING LOCAL DATABASE. UNSAFE"; + initial_sync_complete_at_ = td::Timestamp::in(EXPECTED_UNSAFE_INITIAL_SYNC_DURATION); + } + } + + td::BufferSlice raw_data = serialize_tl_object(block, true, payload.as_slice()); create_block(std::move(block), td::SharedSlice{payload.as_slice()}); if (!opts_.debug_disable_db) { @@ -94,6 +145,11 @@ void CatChainReceiverImpl::receive_block(adnl::AdnlNodeIdShort src, tl_object_pt } void CatChainReceiverImpl::receive_block_answer(adnl::AdnlNodeIdShort src, td::BufferSlice data) { + if (data.size() > opts_.max_serialized_block_size) { + VLOG(CATCHAIN_INFO) << this << ": received bad block result " << src << ": too big (size=" + << data.size() << ", limit=" << opts_.max_serialized_block_size << ")"; + return; + } auto F = fetch_tl_prefix(data, true); if (F.is_error()) { VLOG(CATCHAIN_INFO) << this << ": received bad block result: " << F.move_as_error(); @@ -101,7 +157,7 @@ void CatChainReceiverImpl::receive_block_answer(adnl::AdnlNodeIdShort src, td::B } auto f = F.move_as_ok(); ton_api::downcast_call( - *f.get(), + *f, td::overloaded( [&](ton_api::catchain_blockNotFound &r) { VLOG(CATCHAIN_INFO) << this << ": catchain block not found"; }, [&](ton_api::catchain_blockResult &r) { receive_block(src, std::move(r.block_), std::move(data)); })); @@ -112,13 +168,11 @@ void CatChainReceiverImpl::receive_message_from_overlay(adnl::AdnlNodeIdShort sr return; } - /*auto S = get_source_by_hash(src); - CHECK(S != nullptr); - - if (S->blamed()) { - VLOG(CATCHAIN_INFO) << this << ": dropping block update from blamed source " << src; + if (data.size() > opts_.max_serialized_block_size) { + VLOG(CATCHAIN_WARNING) << this << ": dropping broken block from " << src << ": too big (size=" + << data.size() << ", limit=" << opts_.max_serialized_block_size << ")"; return; - }*/ + } auto R = fetch_tl_prefix(data, true); if (R.is_error()) { VLOG(CATCHAIN_WARNING) << this << ": dropping broken block from " << src << ": " << R.move_as_error(); @@ -129,32 +183,19 @@ void CatChainReceiverImpl::receive_message_from_overlay(adnl::AdnlNodeIdShort sr receive_block(src, std::move(U->block_), std::move(data)); } -void CatChainReceiverImpl::receive_broadcast_from_overlay(PublicKeyHash src, td::BufferSlice data) { +void CatChainReceiverImpl::receive_broadcast_from_overlay(const PublicKeyHash &src, td::BufferSlice data) { if (!read_db_) { return; } callback_->on_broadcast(src, std::move(data)); } -/*void CatChainReceiverImpl::send_block(PublicKeyHash src, tl_object_ptr block, - td::BufferSlice payload) { - CHECK(read_db_); - CHECK(src == local_id_); - - validate_block_sync(block, payload.as_slice()).ensure(); - auto B = create_block(std::move(block), td::SharedSlice{payload.as_slice()}); - CHECK(B != nullptr); - - run_scheduler(); - CHECK(B->delivered()); -}*/ - CatChainReceivedBlock *CatChainReceiverImpl::create_block(tl_object_ptr block, td::SharedSlice payload) { if (block->height_ == 0) { return root_block_; } - auto hash = CatChainReceivedBlock::block_hash(this, block, payload.as_slice()); + CatChainBlockHash hash = CatChainReceivedBlock::block_hash(this, block, payload.as_slice()); auto it = blocks_.find(hash); if (it != blocks_.end()) { @@ -163,9 +204,8 @@ CatChainReceivedBlock *CatChainReceiverImpl::create_block(tl_object_ptrsecond.get(); } else { - blocks_.emplace(hash, CatChainReceivedBlock::create(std::move(block), std::move(payload), this)); - it = blocks_.find(hash); - return it->second.get(); + auto r = blocks_.emplace(hash, CatChainReceivedBlock::create(std::move(block), std::move(payload), this)); + return r.first->second.get(); } } @@ -173,7 +213,7 @@ CatChainReceivedBlock *CatChainReceiverImpl::create_block(tl_object_ptrheight_ == 0) { return root_block_; } - auto hash = CatChainReceivedBlock::block_hash(this, block); + CatChainBlockHash hash = CatChainReceivedBlock::block_hash(this, block); auto it = blocks_.find(hash); if (it != blocks_.end()) { return it->second.get(); @@ -184,20 +224,20 @@ CatChainReceivedBlock *CatChainReceiverImpl::create_block(tl_object_ptr &dep) { +td::Status CatChainReceiverImpl::validate_block_sync(const tl_object_ptr &dep) const { TRY_STATUS_PREFIX(CatChainReceivedBlock::pre_validate_block(this, dep), "failed to validate block: "); if (dep->height_ > 0) { auto id = CatChainReceivedBlock::block_id(this, dep); - auto B = serialize_tl_object(id, true); - auto block = get_block(get_tl_object_sha_bits256(id)); + td::BufferSlice B = serialize_tl_object(id, true); + CatChainReceivedBlock *block = get_block(get_tl_object_sha_bits256(id)); if (block) { return td::Status::OK(); } - auto S = get_source_by_hash(PublicKeyHash{id->src_}); + CatChainReceiverSource *S = get_source_by_hash(PublicKeyHash{id->src_}); CHECK(S != nullptr); - auto E = S->get_encryptor_sync(); + Encryptor *E = S->get_encryptor_sync(); CHECK(E != nullptr); return E->check_signature(B.as_slice(), dep->signature_.as_slice()); } else { @@ -205,27 +245,23 @@ td::Status CatChainReceiverImpl::validate_block_sync(tl_object_ptr &block, td::Slice payload) { - //LOG(INFO) << ton_api::to_string(block); +td::Status CatChainReceiverImpl::validate_block_sync(const tl_object_ptr &block, + const td::Slice &payload) const { TRY_STATUS_PREFIX(CatChainReceivedBlock::pre_validate_block(this, block, payload), "failed to validate block: "); + // After pre_validate_block, block->height_ > 0 + auto id = CatChainReceivedBlock::block_id(this, block, payload); + td::BufferSlice B = serialize_tl_object(id, true); - if (block->height_ > 0) { - auto id = CatChainReceivedBlock::block_id(this, block, payload); - auto B = serialize_tl_object(id, true); - - auto S = get_source_by_hash(PublicKeyHash{id->src_}); - CHECK(S != nullptr); - auto E = S->get_encryptor_sync(); - CHECK(E != nullptr); - return E->check_signature(B.as_slice(), block->signature_.as_slice()); - } else { - return td::Status::OK(); - } + CatChainReceiverSource *S = get_source_by_hash(PublicKeyHash{id->src_}); + CHECK(S != nullptr); + Encryptor *E = S->get_encryptor_sync(); + CHECK(E != nullptr); + return E->check_signature(B.as_slice(), block->signature_.as_slice()); } void CatChainReceiverImpl::run_scheduler() { while (!to_run_.empty()) { - auto B = to_run_.front(); + CatChainReceivedBlock *B = to_run_.front(); to_run_.pop_front(); B->run(); @@ -251,11 +287,13 @@ void CatChainReceiverImpl::add_block_cont_3(tl_object_ptrdelivered()); + LOG_CHECK(last_sent_block_->delivered()) + << "source=" << last_sent_block_->get_source_id() << " ill=" << last_sent_block_->is_ill() + << " height=" << last_sent_block_->get_height(); } active_send_ = false; - if (pending_blocks_.size() > 0) { + if (!pending_blocks_.empty()) { auto B = std::move(pending_blocks_.front()); pending_blocks_.pop_front(); add_block(std::move(B->payload_), std::move(B->deps_)); @@ -268,9 +306,9 @@ void CatChainReceiverImpl::add_block_cont_2(tl_object_ptr add_block_cont_2(std::move(block), std::move(payload)); return; } - auto id = CatChainReceivedBlock::block_hash(this, block, payload.as_slice()); + CatChainBlockHash id = CatChainReceivedBlock::block_hash(this, block, payload.as_slice()); - auto raw_data = serialize_tl_object(block, true, payload.as_slice()); + td::BufferSlice raw_data = serialize_tl_object(block, true, payload.as_slice()); auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), block = std::move(block), payload = std::move(payload)](td::Result R) mutable { @@ -309,7 +347,7 @@ void CatChainReceiverImpl::add_block(td::BufferSlice payload, std::vectorget_id() == local_idx_); if (!intentional_fork_) { @@ -321,7 +359,7 @@ void CatChainReceiverImpl::add_block(td::BufferSlice payload, std::vector> deps_arr; deps_arr.resize(deps.size()); for (size_t i = 0; i < deps.size(); i++) { - auto B = get_block(deps[i]); + CatChainReceivedBlock *B = get_block(deps[i]); LOG_CHECK(B != nullptr) << this << ": cannot find block with hash " << deps[i]; if (!intentional_fork_) { CHECK(B->get_source_id() != local_idx_); @@ -329,13 +367,13 @@ void CatChainReceiverImpl::add_block(td::BufferSlice payload, std::vectorexport_tl_dep(); } - auto height = prev->height_ + 1; + int height = prev->height_ + 1; auto block_data = create_tl_object(std::move(prev), std::move(deps_arr)); auto block = create_tl_object(incarnation_, local_idx_, height, std::move(block_data), td::BufferSlice()); auto id = CatChainReceivedBlock::block_id(this, block, payload); - auto id_s = serialize_tl_object(id, true); + td::BufferSlice id_s = serialize_tl_object(id, true); auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), print_id = print_id(), block = std::move(block), payload = std::move(payload)](td::Result R) mutable { @@ -352,24 +390,24 @@ void CatChainReceiverImpl::add_block(td::BufferSlice payload, std::vector block, td::BufferSlice payload) { validate_block_sync(block, payload.as_slice()).ensure(); - auto B = create_block(std::move(block), td::SharedSlice{payload.as_slice()}); + CatChainReceivedBlock *B = create_block(std::move(block), td::SharedSlice{payload.as_slice()}); B->written(); run_scheduler(); CHECK(B->delivered()); active_send_ = false; - if (pending_blocks_.size() > 0) { - auto B = std::move(pending_blocks_.front()); + if (!pending_blocks_.empty()) { + auto pending_block = std::move(pending_blocks_.front()); pending_blocks_.pop_front(); - add_block(std::move(B->payload_), std::move(B->deps_)); + add_block(std::move(pending_block->payload_), std::move(pending_block->deps_)); } } void CatChainReceiverImpl::debug_add_fork(td::BufferSlice payload, CatChainBlockHeight height, std::vector deps) { intentional_fork_ = true; - auto S = get_source_by_hash(local_id_); + CatChainReceiverSource *S = get_source_by_hash(local_id_); CHECK(S != nullptr); CHECK(S->get_id() == local_idx_); @@ -389,7 +427,7 @@ void CatChainReceiverImpl::debug_add_fork(td::BufferSlice payload, CatChainBlock std::vector> deps_arr; deps_arr.resize(deps.size()); for (size_t i = 0; i < deps.size(); i++) { - auto B = get_block(deps[i]); + CatChainReceivedBlock *B = get_block(deps[i]); LOG_CHECK(B != nullptr) << this << ": cannot find block with hash " << deps[i]; CHECK(B->get_source_id() != local_idx_); deps_arr[i] = B->export_tl_dep(); @@ -400,7 +438,7 @@ void CatChainReceiverImpl::debug_add_fork(td::BufferSlice payload, CatChainBlock td::BufferSlice()); auto id = CatChainReceivedBlock::block_id(this, block, payload); - auto id_s = serialize_tl_object(id, true); + td::BufferSlice id_s = serialize_tl_object(id, true); auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), print_id = print_id(), block = std::move(block), payload = std::move(payload)](td::Result R) mutable { @@ -415,26 +453,33 @@ void CatChainReceiverImpl::debug_add_fork(td::BufferSlice payload, CatChainBlock td::actor::send_closure_later(keyring_, &keyring::Keyring::sign_message, local_id_, std::move(id_s), std::move(P)); } -CatChainReceiverImpl::CatChainReceiverImpl(std::unique_ptr callback, CatChainOptions opts, +CatChainReceiverImpl::CatChainReceiverImpl(std::unique_ptr callback, + const CatChainOptions &opts, td::actor::ActorId keyring, td::actor::ActorId adnl, td::actor::ActorId overlay_manager, - std::vector ids, PublicKeyHash local_id, - CatChainSessionId unique_hash, std::string db_root) + const std::vector &ids, + const PublicKeyHash &local_id, + const CatChainSessionId &unique_hash, + std::string db_root, + std::string db_suffix, + bool allow_unsafe_self_blocks_resync) : callback_(std::move(callback)) - , opts_(std::move(opts)) - , keyring_(keyring) - , adnl_(adnl) - , overlay_manager_(overlay_manager) + , opts_(opts) + , keyring_(std::move(keyring)) + , adnl_(std::move(adnl)) + , overlay_manager_(std::move(overlay_manager)) , local_id_(local_id) - , db_root_(db_root) { + , db_root_(std::move(db_root)) + , db_suffix_(std::move(db_suffix)) + , allow_unsafe_self_blocks_resync_(allow_unsafe_self_blocks_resync) { std::vector short_ids; local_idx_ = static_cast(ids.size()); - for (auto &id : ids) { - td::uint32 seq = static_cast(sources_.size()); + for (const CatChainNode &id : ids) { + auto seq = static_cast(sources_.size()); auto R = CatChainReceiverSource::create(this, id.pub_key, id.adnl_id, seq); auto S = R.move_as_ok(); - auto h = id.pub_key.compute_short_id(); + PublicKeyHash h = id.pub_key.compute_short_id(); short_ids.push_back(h.bits256_value()); sources_hashes_[h] = seq; sources_adnl_addrs_[id.adnl_id] = seq; @@ -447,7 +492,6 @@ CatChainReceiverImpl::CatChainReceiverImpl(std::unique_ptr callback, C } CHECK(local_idx_ != static_cast(ids.size())); - //std::sort(short_ids.begin(), short_ids.end()); auto F = create_tl_object(unique_hash, std::move(short_ids)); overlay_full_id_ = overlay::OverlayIdFull{serialize_tl_object(F, true)}; @@ -459,38 +503,43 @@ CatChainReceiverImpl::CatChainReceiverImpl(std::unique_ptr callback, C blocks_[root_block_->get_hash()] = std::move(R); last_sent_block_ = root_block_; + blame_processed_.resize(sources_.size(), false); + choose_neighbours(); } void CatChainReceiverImpl::start_up() { std::vector ids; + ids.reserve(get_sources_cnt()); for (td::uint32 i = 0; i < get_sources_cnt(); i++) { ids.push_back(get_source(i)->get_adnl_id()); } std::map root_keys; for (td::uint32 i = 0; i < get_sources_cnt(); i++) { - root_keys.emplace(get_source(i)->get_hash(), 16 << 20); + root_keys.emplace(get_source(i)->get_hash(), OVERLAY_MAX_ALLOWED_PACKET_SIZE); } td::actor::send_closure(overlay_manager_, &overlay::Overlays::create_private_overlay, get_source(local_idx_)->get_adnl_id(), overlay_full_id_.clone(), std::move(ids), - make_callback(), overlay::OverlayPrivacyRules{0, std::move(root_keys)}); + make_callback(), overlay::OverlayPrivacyRules{0, 0, std::move(root_keys)}, + R"({ "type": "catchain" })"); CHECK(root_block_); if (!opts_.debug_disable_db) { std::shared_ptr kv = std::make_shared( - td::RocksDb::open(db_root_ + "/catchainreceiver-" + td::base64url_encode(as_slice(incarnation_))).move_as_ok()); + td::RocksDb::open(db_root_ + "/catchainreceiver" + db_suffix_ + td::base64url_encode(as_slice(incarnation_))) + .move_as_ok()); db_ = DbType{std::move(kv)}; auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { R.ensure(); - auto g = R.move_as_ok(); + DbType::GetResult g = R.move_as_ok(); if (g.status == td::KeyValue::GetStatus::NotFound) { td::actor::send_closure(SelfId, &CatChainReceiverImpl::read_db); } else { - auto B = std::move(g.value); - CHECK(B.size() == 32); + td::BufferSlice B = std::move(g.value); CatChainBlockHash x; + CHECK(B.size() == x.as_array().size()); as_slice(x).copy_from(B.as_slice()); td::actor::send_closure(SelfId, &CatChainReceiverImpl::read_db_from, x); } @@ -513,7 +562,7 @@ void CatChainReceiverImpl::read_db_from(CatChainBlockHash id) { auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), id](td::Result R) { R.ensure(); - auto g = R.move_as_ok(); + DbType::GetResult g = R.move_as_ok(); CHECK(g.status == td::KeyValue::GetStatus::Ok); td::actor::send_closure(SelfId, &CatChainReceiverImpl::read_block_from_db, id, std::move(g.value)); @@ -529,12 +578,12 @@ void CatChainReceiverImpl::read_block_from_db(CatChainBlockHash id, td::BufferSl F.ensure(); auto block = F.move_as_ok(); - auto payload = std::move(data); + td::BufferSlice payload = std::move(data); - auto block_id = CatChainReceivedBlock::block_hash(this, block, payload); + CatChainBlockHash block_id = CatChainReceivedBlock::block_hash(this, block, payload); CHECK(block_id == id); - auto B = get_block(id); + CatChainReceivedBlock *B = get_block(id); if (B && B->initialized()) { CHECK(B->in_db()); if (!pending_in_db_) { @@ -543,7 +592,7 @@ void CatChainReceiverImpl::read_block_from_db(CatChainBlockHash id, td::BufferSl return; } - auto source = get_source(block->src_); + CatChainReceiverSource *source = get_source(block->src_); CHECK(source != nullptr); CHECK(block->incarnation_ == incarnation_); @@ -551,18 +600,18 @@ void CatChainReceiverImpl::read_block_from_db(CatChainBlockHash id, td::BufferSl validate_block_sync(block, payload).ensure(); B = create_block(std::move(block), td::SharedSlice{payload.as_slice()}); - B->written(); CHECK(B); + B->written(); - auto deps = B->get_dep_hashes(); + std::vector deps = B->get_dep_hashes(); deps.push_back(B->get_prev_hash()); - for (auto &dep : deps) { - auto dep_block = get_block(dep); + for (const CatChainBlockHash &dep : deps) { + CatChainReceivedBlock *dep_block = get_block(dep); if (!dep_block || !dep_block->initialized()) { pending_in_db_++; auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), dep](td::Result R) { R.ensure(); - auto g = R.move_as_ok(); + DbType::GetResult g = R.move_as_ok(); CHECK(g.status == td::KeyValue::GetStatus::Ok); td::actor::send_closure(SelfId, &CatChainReceiverImpl::read_block_from_db, dep, std::move(g.value)); @@ -587,25 +636,28 @@ void CatChainReceiverImpl::read_db() { read_db_ = true; - next_rotate_ = td::Timestamp::in(60 + td::Random::fast(0, 60)); - next_sync_ = td::Timestamp::in(0.001 * td::Random::fast(0, 60)); + next_rotate_ = td::Timestamp::in(td::Random::fast(NEIGHBOURS_ROTATE_INTERVAL_MIN, NEIGHBOURS_ROTATE_INTERVAL_MAX)); + next_sync_ = td::Timestamp::in( + 0.001 * td::Random::fast(NEIGHBOURS_ROTATE_INTERVAL_MIN, NEIGHBOURS_ROTATE_INTERVAL_MAX)); + initial_sync_complete_at_ = td::Timestamp::in( + allow_unsafe_self_blocks_resync_ ? EXPECTED_UNSAFE_INITIAL_SYNC_DURATION : EXPECTED_INITIAL_SYNC_DURATION); alarm_timestamp().relax(next_rotate_); alarm_timestamp().relax(next_sync_); - - callback_->start(); + alarm_timestamp().relax(initial_sync_complete_at_); } td::actor::ActorOwn CatChainReceiverInterface::create( - std::unique_ptr callback, CatChainOptions opts, td::actor::ActorId keyring, + std::unique_ptr callback, const CatChainOptions &opts, td::actor::ActorId keyring, td::actor::ActorId adnl, td::actor::ActorId overlay_manager, - std::vector ids, PublicKeyHash local_id, CatChainSessionId unique_hash, std::string db_root) { - auto A = td::actor::create_actor("catchainreceiver", std::move(callback), std::move(opts), - keyring, adnl, overlay_manager, std::move(ids), local_id, - unique_hash, db_root); + const std::vector &ids, const PublicKeyHash &local_id, const CatChainSessionId &unique_hash, + std::string db_root, std::string db_suffix, bool allow_unsafe_self_blocks_resync) { + auto A = td::actor::create_actor( + "catchainreceiver", std::move(callback), opts, std::move(keyring), std::move(adnl), std::move(overlay_manager), + ids, local_id, unique_hash, std::move(db_root), std::move(db_suffix), allow_unsafe_self_blocks_resync); return std::move(A); } -CatChainReceiverSource *CatChainReceiverImpl::get_source_by_hash(PublicKeyHash source_hash) const { +CatChainReceiverSource *CatChainReceiverImpl::get_source_by_hash(const PublicKeyHash &source_hash) const { auto it = sources_hashes_.find(source_hash); if (it == sources_hashes_.end()) { return nullptr; @@ -631,82 +683,30 @@ void CatChainReceiverImpl::receive_query_from_overlay(adnl::AdnlNodeIdShort src, auto F = fetch_tl_object(data.clone(), true); if (F.is_error()) { callback_->on_custom_query(get_source_by_adnl_id(src)->get_hash(), std::move(data), std::move(promise)); - //LOG(WARNING) << this << ": unknown query from " << src; return; } auto f = F.move_as_ok(); - ton_api::downcast_call(*f.get(), [&](auto &obj) { this->process_query(src, obj, std::move(promise)); }); + ton_api::downcast_call(*f, [&](auto &obj) { this->process_query(src, std::move(obj), std::move(promise)); }); } -void CatChainReceiverImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::catchain_getBlock &query, +void CatChainReceiverImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::catchain_getBlock query, td::Promise promise) { auto it = blocks_.find(query.block_); if (it == blocks_.end() || it->second->get_height() == 0 || !it->second->initialized()) { promise.set_value(serialize_tl_object(create_tl_object(), true)); } else { - promise.set_value(serialize_tl_object(create_tl_object(it->second->export_tl()), - true, it->second->get_payload().as_slice())); - } -} - -void CatChainReceiverImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::catchain_getBlocks &query, - td::Promise promise) { - if (query.blocks_.size() > 100) { - promise.set_error(td::Status::Error(ErrorCode::protoviolation, "too many blocks")); - return; - } - td::int32 cnt = 0; - for (auto &b : query.blocks_) { - auto it = blocks_.find(b); - if (it != blocks_.end() && it->second->get_height() > 0) { - auto block = create_tl_object(it->second->export_tl()); - CHECK(it->second->get_payload().size() > 0); - auto B = serialize_tl_object(block, true, it->second->get_payload().clone()); - td::actor::send_closure(overlay_manager_, &overlay::Overlays::send_message, src, - get_source(local_idx_)->get_adnl_id(), overlay_id_, std::move(B)); - cnt++; + CatChainReceiverSource *S = get_source_by_adnl_id(src); + CHECK(S != nullptr); + if (S->allow_send_block(it->second->get_hash())) { + promise.set_value(serialize_tl_object(create_tl_object(it->second->export_tl()), + true, it->second->get_payload().as_slice())); + } else { + promise.set_error(td::Status::Error("block was requested too many times")); } } - promise.set_value(serialize_tl_object(create_tl_object(cnt), true)); } -void CatChainReceiverImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::catchain_getBlockHistory &query, - td::Promise promise) { - auto h = query.height_; - if (h <= 0) { - promise.set_error(td::Status::Error(ErrorCode::protoviolation, "not-positive height")); - return; - } - if (h > 100) { - h = 100; - } - std::set s{query.stop_if_.begin(), query.stop_if_.end()}; - - auto B = get_block(query.block_); - if (B == nullptr) { - promise.set_value(serialize_tl_object(create_tl_object(0), true)); - return; - } - if (static_cast(h) > B->get_height()) { - h = B->get_height(); - } - td::uint32 cnt = 0; - while (h-- > 0) { - if (s.find(B->get_hash()) != s.end()) { - break; - } - auto block = create_tl_object(B->export_tl()); - CHECK(B->get_payload().size() > 0); - auto BB = serialize_tl_object(block, true, B->get_payload().as_slice()); - td::actor::send_closure(overlay_manager_, &overlay::Overlays::send_message, src, - get_source(local_idx_)->get_adnl_id(), overlay_id_, std::move(BB)); - B = B->get_prev(); - cnt++; - } - promise.set_value(serialize_tl_object(create_tl_object(cnt), true)); -} - -void CatChainReceiverImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::catchain_getDifference &query, +void CatChainReceiverImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::catchain_getDifference query, td::Promise promise) { auto &vt = query.rt_; if (vt.size() != get_sources_cnt()) { @@ -716,7 +716,7 @@ void CatChainReceiverImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::cat } for (td::uint32 i = 0; i < get_sources_cnt(); i++) { if (vt[i] >= 0) { - auto S = get_source(i); + CatChainReceiverSource *S = get_source(i); if (S->fork_is_found()) { auto obj = fetch_tl_object(S->fork_proof(), true); obj.ensure(); @@ -729,26 +729,21 @@ void CatChainReceiverImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::cat } std::vector my_vt(get_sources_cnt()); - td::uint64 total = 0; for (td::uint32 i = 0; i < get_sources_cnt(); i++) { if (vt[i] >= 0) { - auto x = static_cast(vt[i]); - auto S = get_source(i); - if (S->delivered_height() > x) { - total += S->delivered_height() - x; - } - my_vt[i] = S->delivered_height(); + CatChainReceiverSource *S = get_source(i); + my_vt[i] = static_cast(S->delivered_height()); } else { my_vt[i] = -1; } } - const td::uint32 max_send = 100; + const td::uint32 max_send = GET_DIFFERENCE_MAX_SEND; - td::int32 l = 0; - td::int32 r = max_send + 1; - while (r - l > 1) { - td::int32 x = (r + l) / 2; + td::int32 left = 0; + td::int32 right = max_send + 1; + while (right - left > 1) { + td::int32 x = (right + left) / 2; td::uint64 sum = 0; for (td::uint32 i = 0; i < get_sources_cnt(); i++) { if (vt[i] >= 0 && my_vt[i] > vt[i]) { @@ -756,25 +751,29 @@ void CatChainReceiverImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::cat } } if (sum > max_send) { - r = x; + right = x; } else { - l = x; + left = x; } } - CHECK(r > 0); + CHECK(right > 0); + CatChainReceiverSource *S0 = get_source_by_adnl_id(src); + CHECK(S0 != nullptr); for (td::uint32 i = 0; i < get_sources_cnt(); i++) { if (vt[i] >= 0 && my_vt[i] > vt[i]) { - auto S = get_source(i); - auto t = (my_vt[i] - vt[i] > r) ? r : (my_vt[i] - vt[i]); - CHECK(t > 0); + CatChainReceiverSource *S = get_source(i); + td::int32 t = (my_vt[i] - vt[i] > right) ? right : (my_vt[i] - vt[i]); while (t-- > 0) { - auto M = S->get_block(++vt[i]); + CatChainReceivedBlock *M = S->get_block(++vt[i]); CHECK(M != nullptr); - auto block = create_tl_object(M->export_tl()); - CHECK(M->get_payload().size() > 0); - auto BB = serialize_tl_object(block, true, M->get_payload().as_slice()); - td::actor::send_closure(overlay_manager_, &overlay::Overlays::send_message, src, - get_source(local_idx_)->get_adnl_id(), overlay_id_, std::move(BB)); + if (S0->allow_send_block(M->get_hash())) { + auto block = create_tl_object(M->export_tl()); + CHECK(!M->get_payload().empty()); + td::BufferSlice BB = serialize_tl_object(block, true, M->get_payload().as_slice()); + CHECK(BB.size() <= opts_.max_serialized_block_size); + td::actor::send_closure(overlay_manager_, &overlay::Overlays::send_message, src, + get_source(local_idx_)->get_adnl_id(), overlay_id_, std::move(BB)); + } } } } @@ -783,7 +782,7 @@ void CatChainReceiverImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::cat } void CatChainReceiverImpl::got_fork_proof(td::BufferSlice data) { - auto F = fetch_tl_object(std::move(data), true); + auto F = fetch_tl_object(data, true); if (F.is_error()) { VLOG(CATCHAIN_WARNING) << this << ": received bad fork proof: " << F.move_as_error(); return; @@ -810,7 +809,7 @@ void CatChainReceiverImpl::got_fork_proof(td::BufferSlice data) { return; } - auto S = get_source(f->left_->src_); + CatChainReceiverSource *S = get_source(f->left_->src_); S->on_found_fork_proof( create_serialize_tl_object(std::move(f->left_), std::move(f->right_))); S->blame(); @@ -820,24 +819,24 @@ void CatChainReceiverImpl::synchronize_with(CatChainReceiverSource *S) { CHECK(!S->blamed()); std::vector rt(get_sources_cnt()); for (td::uint32 i = 0; i < get_sources_cnt(); i++) { - auto SS = get_source(i); + CatChainReceiverSource *SS = get_source(i); if (SS->blamed()) { rt[i] = -1; } else { - rt[i] = S->delivered_height(); + rt[i] = static_cast(S->delivered_height()); } } auto P = td::PromiseCreator::lambda( [SelfId = actor_id(this), src = S->get_hash(), print_id = print_id()](td::Result R) { if (R.is_error()) { - VLOG(CATCHAIN_INFO) << print_id << ": timedout syncronize query to " << src; + VLOG(CATCHAIN_INFO) << print_id << ": timedout synchronize query to " << src; return; } - auto data = R.move_as_ok(); + td::BufferSlice data = R.move_as_ok(); auto X = fetch_tl_object(data.clone(), true); if (X.is_error()) { - VLOG(CATCHAIN_WARNING) << print_id << ": received incorrect answer to syncronize query from " << src << ": " + VLOG(CATCHAIN_WARNING) << print_id << ": received incorrect answer to synchronize query from " << src << ": " << X.move_as_error(); return; } @@ -851,49 +850,49 @@ void CatChainReceiverImpl::synchronize_with(CatChainReceiverSource *S) { }); td::actor::send_closure(overlay_manager_, &overlay::Overlays::send_query, S->get_adnl_id(), get_source(local_idx_)->get_adnl_id(), overlay_id_, "sync", std::move(P), - td::Timestamp::in(5.0), + td::Timestamp::in(GET_DIFFERENCE_TIMEOUT), serialize_tl_object(create_tl_object(std::move(rt)), true)); if (S->delivered_height() < S->received_height()) { - auto B = S->get_block(S->delivered_height() + 1); + CatChainReceivedBlock *B = S->get_block(S->delivered_height() + 1); CHECK(B->initialized()); std::vector vec; - B->find_pending_deps(vec, 16); + B->find_pending_deps(vec, MAX_PENDING_DEPS); - for (auto &hash : vec) { - auto P = td::PromiseCreator::lambda( + for (const CatChainBlockHash &hash : vec) { + auto PP = td::PromiseCreator::lambda( [SelfId = actor_id(this), print_id = print_id(), src = S->get_adnl_id()](td::Result R) { if (R.is_error()) { - VLOG(CATCHAIN_INFO) << print_id << ": timedout syncronize query to " << src; + VLOG(CATCHAIN_INFO) << print_id << ": timedout synchronize query to " << src; } else { td::actor::send_closure(SelfId, &CatChainReceiverImpl::receive_block_answer, src, R.move_as_ok()); } }); - auto query = serialize_tl_object(create_tl_object(hash), true); + td::BufferSlice query = serialize_tl_object(create_tl_object(hash), true); td::actor::send_closure(overlay_manager_, &overlay::Overlays::send_query, S->get_adnl_id(), - get_source(local_idx_)->get_adnl_id(), overlay_id_, "sync blocks", std::move(P), - td::Timestamp::in(2.0), std::move(query)); + get_source(local_idx_)->get_adnl_id(), overlay_id_, "sync blocks", std::move(PP), + td::Timestamp::in(GET_BLOCK_TIMEOUT), std::move(query)); } } } void CatChainReceiverImpl::choose_neighbours() { std::vector n; - n.resize(get_max_neighbours()); + n.resize(MAX_NEIGHBOURS); td::uint32 size = 0; for (td::uint32 i = 0; i < get_sources_cnt(); i++) { if (i == local_idx_) { continue; } - auto S = get_source(i); + CatChainReceiverSource *S = get_source(i); if (!S->blamed()) { size++; if (size <= n.size()) { n[size - 1] = i; } else { - td::uint32 id = td::Random::fast(0, size - 1); + td::uint32 id = td::Random::fast(0, static_cast(size) - 1); if (id < n.size()) { n[id] = i; } @@ -906,12 +905,70 @@ void CatChainReceiverImpl::choose_neighbours() { neighbours_ = std::move(n); } +bool CatChainReceiverImpl::unsafe_start_up_check_completed() { + CatChainReceiverSource *S = get_source(local_idx_); + CHECK(!S->blamed()); + if (S->has_unreceived() || S->has_undelivered()) { + LOG(INFO) << "catchain: has_unreceived=" << S->has_unreceived() << " has_undelivered=" << S->has_undelivered(); + run_scheduler(); + initial_sync_complete_at_ = td::Timestamp::in(EXPECTED_INITIAL_SYNC_DURATION_WITH_UNPROCESSED); + return false; + } + CatChainBlockHeight h = S->delivered_height(); + if (h == 0) { + CHECK(last_sent_block_->get_height() == 0); + CHECK(!unsafe_root_block_writing_); + return true; + } + if (last_sent_block_->get_height() == h) { + CHECK(!unsafe_root_block_writing_); + return true; + } + if (unsafe_root_block_writing_) { + initial_sync_complete_at_ = td::Timestamp::in(EXPECTED_INITIAL_SYNC_DURATION); + LOG(INFO) << "catchain: writing=true"; + return false; + } + + unsafe_root_block_writing_ = true; + CatChainReceivedBlock *B = S->get_block(h); + CHECK(B != nullptr); + CHECK(B->delivered()); + CHECK(B->in_db()); + + CatChainBlockHash id = B->get_hash(); + + td::BufferSlice raw_data{id.as_array().size()}; + raw_data.as_slice().copy_from(as_slice(id)); + + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), block = B](td::Result R) mutable { + R.ensure(); + td::actor::send_closure(SelfId, &CatChainReceiverImpl::written_unsafe_root_block, block); + }); + + db_.set(CatChainBlockHash::zero(), std::move(raw_data), std::move(P), 0); + initial_sync_complete_at_ = td::Timestamp::in(EXPECTED_INITIAL_SYNC_DURATION); + LOG(INFO) << "catchain: need update root"; + return false; +} + +void CatChainReceiverImpl::written_unsafe_root_block(CatChainReceivedBlock *block) { + CHECK(last_sent_block_->get_height() < block->get_height()); + last_sent_block_ = block; + unsafe_root_block_writing_ = false; +} + void CatChainReceiverImpl::alarm() { alarm_timestamp() = td::Timestamp::never(); - if (next_sync_ && next_sync_.is_in_past()) { - next_sync_ = td::Timestamp::in(td::Random::fast(0.1, 0.2)); - for (auto i = 0; i < 3; i++) { - auto S = get_source(td::Random::fast(0, get_sources_cnt() - 1)); + if (next_sync_ && next_sync_.is_in_past() && get_sources_cnt() > 1) { + next_sync_ = td::Timestamp::in(td::Random::fast(SYNC_INTERVAL_MIN, SYNC_INTERVAL_MAX)); + for (unsigned i = 0; i < SYNC_ITERATIONS; i++) { + auto idx = td::Random::fast(1, static_cast(get_sources_cnt()) - 1); + if (idx == static_cast(local_idx_)) { + idx = 0; + } + // idx is a random number in [0, get_sources_cnt-1] not equal to local_idx + CatChainReceiverSource *S = get_source(idx); CHECK(S != nullptr); if (!S->blamed()) { synchronize_with(S); @@ -920,69 +977,110 @@ void CatChainReceiverImpl::alarm() { } } if (next_rotate_ && next_rotate_.is_in_past()) { - next_rotate_ = td::Timestamp::in(td::Random::fast(60.0, 120.0)); + next_rotate_ = td::Timestamp::in(td::Random::fast(NEIGHBOURS_ROTATE_INTERVAL_MIN, NEIGHBOURS_ROTATE_INTERVAL_MAX)); choose_neighbours(); } + if (!started_ && read_db_ && initial_sync_complete_at_ && initial_sync_complete_at_.is_in_past()) { + bool allow = false; + if (allow_unsafe_self_blocks_resync_) { + allow = unsafe_start_up_check_completed(); + } else { + allow = true; + } + if (allow) { + initial_sync_complete_at_ = td::Timestamp::never(); + started_ = true; + callback_->start(); + } + } alarm_timestamp().relax(next_rotate_); alarm_timestamp().relax(next_sync_); + alarm_timestamp().relax(initial_sync_complete_at_); } void CatChainReceiverImpl::send_fec_broadcast(td::BufferSlice data) { td::actor::send_closure(overlay_manager_, &overlay::Overlays::send_broadcast_fec_ex, get_source(local_idx_)->get_adnl_id(), overlay_id_, local_id_, 0, std::move(data)); } -void CatChainReceiverImpl::send_custom_query_data(PublicKeyHash dst, std::string name, +void CatChainReceiverImpl::send_custom_query_data(const PublicKeyHash &dst, std::string name, td::Promise promise, td::Timestamp timeout, td::BufferSlice query) { - auto S = get_source_by_hash(dst); + CatChainReceiverSource *S = get_source_by_hash(dst); + CHECK(S != nullptr); td::actor::send_closure(overlay_manager_, &overlay::Overlays::send_query, S->get_adnl_id(), get_source(local_idx_)->get_adnl_id(), overlay_id_, std::move(name), std::move(promise), timeout, std::move(query)); } -void CatChainReceiverImpl::send_custom_query_data_via(PublicKeyHash dst, std::string name, +void CatChainReceiverImpl::send_custom_query_data_via(const PublicKeyHash &dst, std::string name, td::Promise promise, td::Timestamp timeout, td::BufferSlice query, td::uint64 max_answer_size, td::actor::ActorId via) { - auto S = get_source_by_hash(dst); + CatChainReceiverSource *S = get_source_by_hash(dst); + CHECK(S != nullptr); td::actor::send_closure(overlay_manager_, &overlay::Overlays::send_query_via, S->get_adnl_id(), get_source(local_idx_)->get_adnl_id(), overlay_id_, std::move(name), std::move(promise), timeout, std::move(query), max_answer_size, via); } -void CatChainReceiverImpl::send_custom_message_data(PublicKeyHash dst, td::BufferSlice data) { - auto S = get_source_by_hash(dst); +void CatChainReceiverImpl::send_custom_message_data(const PublicKeyHash &dst, td::BufferSlice data) { + CatChainReceiverSource *S = get_source_by_hash(dst); + CHECK(S != nullptr); td::actor::send_closure(overlay_manager_, &overlay::Overlays::send_message, S->get_adnl_id(), get_source(local_idx_)->get_adnl_id(), overlay_id_, std::move(data)); } void CatChainReceiverImpl::block_written_to_db(CatChainBlockHash hash) { - auto block = get_block(hash); + CatChainReceivedBlock *block = get_block(hash); CHECK(block); block->written(); run_scheduler(); } -static void destroy_db(std::string name, td::uint32 attempt) { +static void destroy_db(const std::string& name, td::uint32 attempt) { auto S = td::RocksDb::destroy(name); if (S.is_ok()) { return; } - if (S.is_error() && attempt >= 10) { - LOG(ERROR) << "failed to destroy catchain " << name << ": " << S; - } else { + if (S.is_error()) { LOG(DEBUG) << "failed to destroy catchain " << name << ": " << S; - delay_action([name, attempt]() { destroy_db(name, attempt); }, td::Timestamp::in(1.0)); + if (attempt < DESTROY_DB_MAX_ATTEMPTS) { + delay_action([name, attempt]() { destroy_db(name, attempt + 1); }, td::Timestamp::in(DESTROY_DB_DELAY)); + } + } +} + +void CatChainReceiverImpl::on_found_fork_proof(td::uint32 source_id, td::BufferSlice data) { + if (blame_processed_[source_id]) { + add_block(std::move(data), std::vector()); + } else { + pending_fork_proofs_[source_id] = std::move(data); + } +} + +void CatChainReceiverImpl::on_blame_processed(td::uint32 source_id) { + blame_processed_[source_id] = true; + auto it = pending_fork_proofs_.find(source_id); + if (it != pending_fork_proofs_.end()) { + add_block(std::move(it->second), std::vector()); + pending_fork_proofs_.erase(it); } } void CatChainReceiverImpl::destroy() { - auto name = db_root_ + "/catchainreceiver-" + td::base64url_encode(as_slice(incarnation_)); - delay_action([name]() { destroy_db(name, 0); }, td::Timestamp::in(1.0)); + auto name = db_root_ + "/catchainreceiver" + db_suffix_ + td::base64url_encode(as_slice(incarnation_)); + delay_action([name]() { destroy_db(name, 0); }, td::Timestamp::in(DESTROY_DB_DELAY)); stop(); } +td::uint64 get_max_block_height(const CatChainOptions& opts, size_t sources_cnt) { + if (opts.max_block_height_coeff == 0) { + return std::numeric_limits::max(); + } + return opts.max_block_height_coeff * (1 + (sources_cnt + opts.max_deps - 1) / opts.max_deps) / 1000; +} + } // namespace catchain } // namespace ton diff --git a/catchain/catchain-receiver.h b/catchain/catchain-receiver.h index ab04b312..75c87351 100644 --- a/catchain/catchain-receiver.h +++ b/catchain/catchain-receiver.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -44,9 +44,6 @@ class CatChainReceiver : public CatChainReceiverInterface { CatChainSessionId instance_; PublicKeyHash local_id_; }; - td::uint32 get_max_neighbours() const { - return 5; - } virtual PrintId print_id() const = 0; virtual CatChainReceivedBlock *create_block(tl_object_ptr block, td::SharedSlice payload) = 0; @@ -59,17 +56,20 @@ class CatChainReceiver : public CatChainReceiverInterface { virtual void run_block(CatChainReceivedBlock *block) = 0; virtual void deliver_block(CatChainReceivedBlock *block) = 0; virtual td::uint32 add_fork() = 0; - virtual void add_prepared_event(td::BufferSlice data) = 0; + virtual void on_found_fork_proof(td::uint32 source_id, td::BufferSlice data) = 0; virtual void on_blame(td::uint32 source_id) = 0; virtual const CatChainOptions &opts() const = 0; - virtual td::Status validate_block_sync(tl_object_ptr &dep) = 0; - virtual td::Status validate_block_sync(tl_object_ptr &block, td::Slice payload) = 0; + virtual td::Status validate_block_sync(const tl_object_ptr &dep) const = 0; + virtual td::Status validate_block_sync(const tl_object_ptr &block, + const td::Slice &payload) const = 0; virtual ~CatChainReceiver() = default; }; +td::uint64 get_max_block_height(const CatChainOptions& opts, size_t sources_cnt); + } // namespace catchain } // namespace ton diff --git a/catchain/catchain-receiver.hpp b/catchain/catchain-receiver.hpp index a75d4cec..5c4d3764 100644 --- a/catchain/catchain-receiver.hpp +++ b/catchain/catchain-receiver.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -33,15 +33,12 @@ namespace ton { namespace catchain { -class CatChainReceiverImpl : public CatChainReceiver { +class CatChainReceiverImpl final : public CatChainReceiver { public: PrintId print_id() const override { return PrintId{incarnation_, local_id_}; } - void add_prepared_event(td::BufferSlice data) override { - add_block(std::move(data), std::vector()); - } CatChainSessionId get_incarnation() const override { return incarnation_; } @@ -62,7 +59,7 @@ class CatChainReceiverImpl : public CatChainReceiver { return sources_[source_id].get(); } PublicKeyHash get_source_hash(td::uint32 source_id) const override; - CatChainReceiverSource *get_source_by_hash(PublicKeyHash source_hash) const; + CatChainReceiverSource *get_source_by_hash(const PublicKeyHash &source_hash) const; CatChainReceiverSource *get_source_by_adnl_id(adnl::AdnlNodeIdShort source_hash) const; td::uint32 add_fork() override; @@ -72,39 +69,33 @@ class CatChainReceiverImpl : public CatChainReceiver { void receive_message_from_overlay(adnl::AdnlNodeIdShort src, td::BufferSlice data); void receive_query_from_overlay(adnl::AdnlNodeIdShort src, td::BufferSlice data, td::Promise promise); - void process_query(adnl::AdnlNodeIdShort src, ton_api::catchain_getBlock &query, - td::Promise promise); - void process_query(adnl::AdnlNodeIdShort src, ton_api::catchain_getBlocks &query, - td::Promise promise); - void process_query(adnl::AdnlNodeIdShort src, ton_api::catchain_getBlockHistory &query, - td::Promise promise); - void process_query(adnl::AdnlNodeIdShort src, ton_api::catchain_getDifference &query, + void process_query(adnl::AdnlNodeIdShort src, ton_api::catchain_getBlock query, td::Promise promise); + void process_query(adnl::AdnlNodeIdShort src, ton_api::catchain_getDifference query, td::Promise promise); template - void process_query(adnl::AdnlNodeIdShort src, T &query, td::Promise promise) { - //LOG(WARNING) << this << ": unknown query from " << src; + void process_query(adnl::AdnlNodeIdShort src, const T &query, td::Promise promise) { callback_->on_custom_query(get_source_by_adnl_id(src)->get_hash(), serialize_tl_object(&query, true), std::move(promise)); } - void receive_broadcast_from_overlay(PublicKeyHash src, td::BufferSlice data); + void receive_broadcast_from_overlay(const PublicKeyHash &src, td::BufferSlice data); void receive_block(adnl::AdnlNodeIdShort src, tl_object_ptr block, td::BufferSlice payload); void receive_block_answer(adnl::AdnlNodeIdShort src, td::BufferSlice); - //void send_block(PublicKeyHash src, tl_object_ptr block, td::BufferSlice payload); CatChainReceivedBlock *create_block(tl_object_ptr block, td::SharedSlice payload) override; CatChainReceivedBlock *create_block(tl_object_ptr block) override; - td::Status validate_block_sync(tl_object_ptr &dep) override; - td::Status validate_block_sync(tl_object_ptr &block, td::Slice payload) override; + td::Status validate_block_sync(const tl_object_ptr &dep) const override; + td::Status validate_block_sync(const tl_object_ptr &block, + const td::Slice &payload) const override; void send_fec_broadcast(td::BufferSlice data) override; - void send_custom_query_data(PublicKeyHash dst, std::string name, td::Promise promise, + void send_custom_query_data(const PublicKeyHash &dst, std::string name, td::Promise promise, td::Timestamp timeout, td::BufferSlice query) override; - void send_custom_query_data_via(PublicKeyHash dst, std::string name, td::Promise promise, + void send_custom_query_data_via(const PublicKeyHash &dst, std::string name, td::Promise promise, td::Timestamp timeout, td::BufferSlice query, td::uint64 max_answer_size, td::actor::ActorId via) override; - void send_custom_message_data(PublicKeyHash dst, td::BufferSlice query) override; + void send_custom_message_data(const PublicKeyHash &dst, td::BufferSlice query) override; void run_scheduler(); void add_block(td::BufferSlice data, std::vector deps) override; @@ -117,8 +108,8 @@ class CatChainReceiverImpl : public CatChainReceiver { void on_blame(td::uint32 src) override { callback_->blame(src); } - void blame_node(td::uint32 idx) override { - } + void on_found_fork_proof(td::uint32 source_id, td::BufferSlice data) override; + void on_blame_processed(td::uint32 source_id) override; const CatChainOptions &opts() const override { return opts_; } @@ -134,14 +125,18 @@ class CatChainReceiverImpl : public CatChainReceiver { void block_written_to_db(CatChainBlockHash hash); + bool unsafe_start_up_check_completed(); + void written_unsafe_root_block(CatChainReceivedBlock *block); + void destroy() override; CatChainReceivedBlock *get_block(CatChainBlockHash hash) const; - CatChainReceiverImpl(std::unique_ptr callback, CatChainOptions opts, + CatChainReceiverImpl(std::unique_ptr callback, const CatChainOptions &opts, td::actor::ActorId keyring, td::actor::ActorId adnl, - td::actor::ActorId, std::vector ids, PublicKeyHash local_id, - CatChainBlockHash unique_hash, std::string db_root); + td::actor::ActorId overlays, const std::vector &ids, + const PublicKeyHash &local_id, const CatChainBlockHash &unique_hash, std::string db_root, + std::string db_suffix, bool allow_unsafe_self_blocks_resync); private: std::unique_ptr make_callback() { @@ -160,7 +155,7 @@ class CatChainReceiverImpl : public CatChainReceiver { void receive_broadcast(PublicKeyHash src, overlay::OverlayIdShort overlay_id, td::BufferSlice data) override { td::actor::send_closure(id_, &CatChainReceiverImpl::receive_broadcast_from_overlay, src, std::move(data)); } - Callback(td::actor::ActorId id) : id_(std::move(id)) { + explicit Callback(td::actor::ActorId id) : id_(std::move(id)) { } private: @@ -195,16 +190,13 @@ class CatChainReceiverImpl : public CatChainReceiver { CatChainReceivedBlock *root_block_; CatChainReceivedBlock *last_sent_block_; - CatChainSessionId incarnation_; + CatChainSessionId incarnation_{}; std::unique_ptr callback_; CatChainOptions opts_; std::vector neighbours_; - //std::queue> events_; - //std::queue raw_events_; - td::actor::ActorId keyring_; td::actor::ActorId adnl_; td::actor::ActorId overlay_manager_; @@ -217,13 +209,21 @@ class CatChainReceiverImpl : public CatChainReceiver { td::Timestamp next_rotate_; std::string db_root_; + std::string db_suffix_; using DbType = td::KeyValueAsync; DbType db_; bool intentional_fork_ = false; + td::Timestamp initial_sync_complete_at_{td::Timestamp::never()}; + bool allow_unsafe_self_blocks_resync_{false}; + bool unsafe_root_block_writing_{false}; + bool started_{false}; std::list to_run_; + + std::vector blame_processed_; + std::map pending_fork_proofs_; }; } // namespace catchain diff --git a/catchain/catchain-types.h b/catchain/catchain-types.h index 217ef753..4031acc5 100644 --- a/catchain/catchain-types.h +++ b/catchain/catchain-types.h @@ -14,12 +14,13 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "td/utils/int_types.h" #include "adnl/adnl-node-id.hpp" +#include "ton/ton-types.h" namespace ton { @@ -35,13 +36,6 @@ struct CatChainNode { PublicKey pub_key; }; -struct CatChainOptions { - td::Clocks::Duration idle_timeout = 16.0; - td::uint32 max_deps = 4; - - bool debug_disable_db = false; -}; - } // namespace catchain } // namespace ton diff --git a/catchain/catchain.cpp b/catchain/catchain.cpp index 56f70028..53ae16b5 100644 --- a/catchain/catchain.cpp +++ b/catchain/catchain.cpp @@ -14,13 +14,13 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "catchain-types.h" #include "catchain.hpp" -#include "catchain-receiver.h" -#include "adnl/utils.hpp" +#include +#include "catchain-receiver.h" namespace ton { @@ -32,10 +32,11 @@ void CatChainImpl::send_process() { std::vector v; std::vector w; while (top_blocks_.size() > 0 && v.size() < opts_.max_deps) { - auto B = *top_blocks_.get_random(); + CatChainBlock *B = *top_blocks_.get_random(); CHECK(B != nullptr); top_blocks_.remove(B->hash()); - if (B->source() == sources_.size() || !blamed_sources_[B->source()]) { + CHECK(B->source() < sources_.size()); + if (!blamed_sources_[B->source()]) { w.push_back(B->hash()); v.push_back(B); set_processed(B); @@ -52,13 +53,13 @@ void CatChainImpl::send_preprocess(CatChainBlock *block) { if (block->preprocess_is_sent()) { return; } - auto prev = block->prev(); + CatChainBlock *prev = block->prev(); if (prev) { send_preprocess(prev); } - auto deps = block->deps(); - for (auto X : deps) { + const std::vector &deps = block->deps(); + for (CatChainBlock *X : deps) { send_preprocess(X); } @@ -72,13 +73,13 @@ void CatChainImpl::set_processed(CatChainBlock *block) { if (block->is_processed()) { return; } - auto prev = block->prev(); + CatChainBlock *prev = block->prev(); if (prev) { set_processed(prev); } - auto deps = block->deps(); - for (auto X : deps) { + const std::vector &deps = block->deps(); + for (CatChainBlock *X : deps) { set_processed(X); } @@ -107,11 +108,12 @@ void CatChainImpl::need_new_block(td::Timestamp t) { if (!receiver_started_) { return; } - if (!force_process_) { + if (!force_process_ || !active_process_) { VLOG(CATCHAIN_INFO) << this << ": forcing creation of new block"; } - force_process_ = true; - if (!active_process_) { + if (active_process_) { + force_process_ = true; + } else { alarm_timestamp().relax(t); } } @@ -133,6 +135,7 @@ void CatChainImpl::on_new_block(td::uint32 src_id, td::uint32 fork, CatChainBloc } } + CHECK(src_id < sources_.size()); std::vector v; v.resize(deps.size()); for (size_t i = 0; i < deps.size(); i++) { @@ -143,12 +146,12 @@ void CatChainImpl::on_new_block(td::uint32 src_id, td::uint32 fork, CatChainBloc CHECK(v[i] != nullptr); } - CHECK(src_id < sources_.size()); - auto src_hash = sources_[src_id]; + CHECK(height <= get_max_block_height(opts_, sources_.size())); + PublicKeyHash src_hash = sources_[src_id]; blocks_[hash] = CatChainBlock::create(src_id, fork, src_hash, height, hash, std::move(data), p, std::move(v), std::move(vt)); - auto B = get_block(hash); + CatChainBlock *B = get_block(hash); CHECK(B != nullptr); if (!blamed_sources_[src_id]) { @@ -177,7 +180,7 @@ void CatChainImpl::on_blame(td::uint32 src_id) { auto size = static_cast(sources_.size()); for (td::uint32 i = 0; i < size; i++) { if (!blamed_sources_[i] && top_source_blocks_[i] && i != local_idx_) { - auto B = top_source_blocks_[i]; + CatChainBlock *B = top_source_blocks_[i]; bool f = true; if (B->is_processed()) { continue; @@ -195,17 +198,21 @@ void CatChainImpl::on_blame(td::uint32 src_id) { } } } + for (td::uint32 i = 0; i < process_deps_.size(); ++i) { + if (blocks_[process_deps_[i]]->source() == src_id) { + process_deps_[i] = process_deps_.back(); + process_deps_.pop_back(); + --i; + } + } + td::actor::send_closure(receiver_, &CatChainReceiverInterface::on_blame_processed, src_id); } -void CatChainImpl::on_custom_message(PublicKeyHash src, td::BufferSlice data) { - callback_->process_message(src, std::move(data)); -} - -void CatChainImpl::on_custom_query(PublicKeyHash src, td::BufferSlice data, td::Promise promise) { +void CatChainImpl::on_custom_query(const PublicKeyHash &src, td::BufferSlice data, td::Promise promise) { callback_->process_query(src, std::move(data), std::move(promise)); } -void CatChainImpl::on_broadcast(PublicKeyHash src, td::BufferSlice data) { +void CatChainImpl::on_broadcast(const PublicKeyHash &src, td::BufferSlice data) { VLOG(CATCHAIN_INFO) << this << ": processing broadcast"; callback_->process_broadcast(src, std::move(data)); VLOG(CATCHAIN_INFO) << this << ": sent processing broadcast"; @@ -219,14 +226,18 @@ void CatChainImpl::on_receiver_started() { send_process(); } -CatChainImpl::CatChainImpl(std::unique_ptr callback, CatChainOptions opts, +CatChainImpl::CatChainImpl(std::unique_ptr callback, const CatChainOptions &opts, td::actor::ActorId keyring, td::actor::ActorId adnl, td::actor::ActorId overlay_manager, std::vector ids, - PublicKeyHash local_id, CatChainSessionId unique_hash, std::string db_root) - : opts_(std::move(opts)), db_root_(db_root) { + const PublicKeyHash &local_id, const CatChainSessionId &unique_hash, + std::string db_root, std::string db_suffix, bool allow_unsafe_self_blocks_resync) + : opts_(opts) + , unique_hash_(unique_hash) + , db_root_(std::move(db_root)) + , db_suffix_(std::move(db_suffix)) + , allow_unsafe_self_blocks_resync_(allow_unsafe_self_blocks_resync) { callback_ = std::move(callback); sources_.resize(ids.size()); - unique_hash_ = unique_hash; for (size_t i = 0; i < ids.size(); i++) { sources_[i] = ids[i].pub_key.compute_short_id(); if (sources_[i] == local_id) { @@ -236,7 +247,8 @@ CatChainImpl::CatChainImpl(std::unique_ptr callback, CatChainOptions o blamed_sources_.resize(ids.size(), false); top_source_blocks_.resize(ids.size(), nullptr); - args_ = std::make_unique(keyring, adnl, overlay_manager, std::move(ids), local_id, unique_hash); + args_ = std::make_unique(std::move(keyring), std::move(adnl), std::move(overlay_manager), std::move(ids), + local_id, unique_hash); } void CatChainImpl::alarm() { @@ -259,19 +271,16 @@ void CatChainImpl::start_up() { void blame(td::uint32 src_id) override { td::actor::send_closure(id_, &CatChainImpl::on_blame, src_id); } - void on_custom_message(PublicKeyHash src, td::BufferSlice data) override { - td::actor::send_closure(id_, &CatChainImpl::on_custom_message, src, std::move(data)); - } - void on_custom_query(PublicKeyHash src, td::BufferSlice data, td::Promise promise) override { + void on_custom_query(const PublicKeyHash &src, td::BufferSlice data, td::Promise promise) override { td::actor::send_closure(id_, &CatChainImpl::on_custom_query, src, std::move(data), std::move(promise)); } - void on_broadcast(PublicKeyHash src, td::BufferSlice data) override { + void on_broadcast(const PublicKeyHash &src, td::BufferSlice data) override { td::actor::send_closure(id_, &CatChainImpl::on_broadcast, src, std::move(data)); } void start() override { td::actor::send_closure(id_, &CatChainImpl::on_receiver_started); } - ChainCb(td::actor::ActorId id) : id_(id) { + explicit ChainCb(td::actor::ActorId id) : id_(std::move(id)) { } private: @@ -280,21 +289,24 @@ void CatChainImpl::start_up() { auto cb = std::make_unique(actor_id(this)); - receiver_ = - CatChainReceiverInterface::create(std::move(cb), opts_, args_->keyring, args_->adnl, args_->overlay_manager, - std::move(args_->ids), args_->local_id, args_->unique_hash, db_root_); + receiver_ = CatChainReceiverInterface::create( + std::move(cb), opts_, args_->keyring, args_->adnl, args_->overlay_manager, args_->ids, args_->local_id, + args_->unique_hash, db_root_, db_suffix_, allow_unsafe_self_blocks_resync_); args_ = nullptr; //alarm_timestamp() = td::Timestamp::in(opts_.idle_timeout); } -td::actor::ActorOwn CatChain::create(std::unique_ptr callback, CatChainOptions opts, +td::actor::ActorOwn CatChain::create(std::unique_ptr callback, const CatChainOptions &opts, td::actor::ActorId keyring, td::actor::ActorId adnl, td::actor::ActorId overlay_manager, - std::vector ids, PublicKeyHash local_id, - CatChainSessionId unique_hash, std::string db_root) { - return td::actor::create_actor("catchain", std::move(callback), std::move(opts), keyring, adnl, - overlay_manager, std::move(ids), local_id, unique_hash, db_root); + std::vector ids, const PublicKeyHash &local_id, + const CatChainSessionId &unique_hash, std::string db_root, + std::string db_suffix, bool allow_unsafe_self_blocks_resync) { + return td::actor::create_actor("catchain", std::move(callback), opts, std::move(keyring), + std::move(adnl), std::move(overlay_manager), std::move(ids), + local_id, unique_hash, std::move(db_root), std::move(db_suffix), + allow_unsafe_self_blocks_resync); } CatChainBlock *CatChainImpl::get_block(CatChainBlockHash hash) const { diff --git a/catchain/catchain.h b/catchain/catchain.h index 6b622913..912957e5 100644 --- a/catchain/catchain.h +++ b/catchain/catchain.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -58,8 +58,8 @@ class CatChainBlock { virtual bool is_descendant_of(CatChainBlock *block) = 0; - static std::unique_ptr create(td::uint32 src, td::uint32 fork_id, PublicKeyHash src_hash, - CatChainBlockHeight height, CatChainBlockHash hash, + static std::unique_ptr create(td::uint32 src, td::uint32 fork_id, const PublicKeyHash &src_hash, + CatChainBlockHeight height, const CatChainBlockHash &hash, td::SharedSlice payload, CatChainBlock *prev, std::vector deps, std::vector vt); @@ -73,9 +73,10 @@ class CatChain : public td::actor::Actor { virtual void process_blocks(std::vector blocks) = 0; virtual void finished_processing() = 0; virtual void preprocess_block(CatChainBlock *block) = 0; - virtual void process_broadcast(PublicKeyHash src, td::BufferSlice data) = 0; - virtual void process_message(PublicKeyHash src, td::BufferSlice data) = 0; - virtual void process_query(PublicKeyHash src, td::BufferSlice data, td::Promise promise) = 0; + virtual void process_broadcast(const PublicKeyHash &src, td::BufferSlice data) = 0; + virtual void process_message(const PublicKeyHash &src, td::BufferSlice data) = 0; + virtual void process_query(const PublicKeyHash &src, td::BufferSlice data, + td::Promise promise) = 0; virtual void started() = 0; virtual ~Callback() = default; }; @@ -89,21 +90,22 @@ class CatChain : public td::actor::Actor { virtual void debug_add_fork(td::BufferSlice payload, CatChainBlockHeight height) = 0; virtual void send_broadcast(td::BufferSlice data) = 0; - virtual void send_message(PublicKeyHash dst, td::BufferSlice data) = 0; - virtual void send_query(PublicKeyHash dst, std::string name, td::Promise promise, + virtual void send_message(const PublicKeyHash &dst, td::BufferSlice data) = 0; + virtual void send_query(const PublicKeyHash &dst, std::string name, td::Promise promise, td::Timestamp timeout, td::BufferSlice query) = 0; - virtual void send_query_via(PublicKeyHash dst, std::string name, td::Promise promise, + virtual void send_query_via(const PublicKeyHash &dst, std::string name, td::Promise promise, td::Timestamp timeout, td::BufferSlice query, td::uint64 max_answer_size, td::actor::ActorId via) = 0; virtual void destroy() = 0; - static td::actor::ActorOwn create(std::unique_ptr callback, CatChainOptions opts, + static td::actor::ActorOwn create(std::unique_ptr callback, const CatChainOptions &opts, td::actor::ActorId keyring, td::actor::ActorId adnl, td::actor::ActorId overlay_manager, - std::vector ids, PublicKeyHash local_id, - CatChainSessionId unique_hash, std::string db_root); - virtual ~CatChain() = default; + std::vector ids, const PublicKeyHash &local_id, + const CatChainSessionId &unique_hash, std::string db_root, + std::string db_suffix, bool allow_unsafe_self_blocks_resync); + ~CatChain() override = default; }; } // namespace catchain diff --git a/catchain/catchain.hpp b/catchain/catchain.hpp index 176491f5..8c8bb99a 100644 --- a/catchain/catchain.hpp +++ b/catchain/catchain.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -51,6 +51,8 @@ class CatChainImpl : public CatChain { bool receiver_started_ = false; std::string db_root_; + std::string db_suffix_; + bool allow_unsafe_self_blocks_resync_; void send_process(); void send_preprocess(CatChainBlock *block); @@ -65,11 +67,11 @@ class CatChainImpl : public CatChain { CatChainSessionId unique_hash; Args(td::actor::ActorId keyring, td::actor::ActorId adnl, - td::actor::ActorId overlay_manager, std::vector ids, PublicKeyHash local_id, - CatChainSessionId unique_hash) - : keyring(keyring) - , adnl(adnl) - , overlay_manager(overlay_manager) + td::actor::ActorId overlay_manager, std::vector ids, + const PublicKeyHash &local_id, const CatChainSessionId &unique_hash) + : keyring(std::move(keyring)) + , adnl(std::move(adnl)) + , overlay_manager(std::move(overlay_manager)) , ids(std::move(ids)) , local_id(local_id) , unique_hash(unique_hash) { @@ -86,9 +88,8 @@ class CatChainImpl : public CatChain { CatChainBlockHash prev, std::vector deps, std::vector vt, td::SharedSlice data); void on_blame(td::uint32 src_id); - void on_custom_message(PublicKeyHash src, td::BufferSlice data); - void on_custom_query(PublicKeyHash src, td::BufferSlice data, td::Promise promise); - void on_broadcast(PublicKeyHash src, td::BufferSlice data); + void on_custom_query(const PublicKeyHash &src, td::BufferSlice data, td::Promise promise); + void on_broadcast(const PublicKeyHash &src, td::BufferSlice data); void on_receiver_started(); void processed_block(td::BufferSlice payload) override; void need_new_block(td::Timestamp t) override; @@ -100,25 +101,26 @@ class CatChainImpl : public CatChain { void send_broadcast(td::BufferSlice data) override { td::actor::send_closure(receiver_, &CatChainReceiverInterface::send_fec_broadcast, std::move(data)); } - void send_message(PublicKeyHash dst, td::BufferSlice data) override { + void send_message(const PublicKeyHash &dst, td::BufferSlice data) override { td::actor::send_closure(receiver_, &CatChainReceiverInterface::send_custom_message_data, dst, std::move(data)); } - void send_query(PublicKeyHash dst, std::string name, td::Promise promise, td::Timestamp timeout, - td::BufferSlice query) override { + void send_query(const PublicKeyHash &dst, std::string name, td::Promise promise, + td::Timestamp timeout, td::BufferSlice query) override { td::actor::send_closure(receiver_, &CatChainReceiverInterface::send_custom_query_data, dst, name, std::move(promise), timeout, std::move(query)); } - void send_query_via(PublicKeyHash dst, std::string name, td::Promise promise, td::Timestamp timeout, - td::BufferSlice query, td::uint64 max_answer_size, + void send_query_via(const PublicKeyHash &dst, std::string name, td::Promise promise, + td::Timestamp timeout, td::BufferSlice query, td::uint64 max_answer_size, td::actor::ActorId via) override { td::actor::send_closure(receiver_, &CatChainReceiverInterface::send_custom_query_data_via, dst, name, std::move(promise), timeout, std::move(query), max_answer_size, via); } void destroy() override; - CatChainImpl(std::unique_ptr callback, CatChainOptions opts, td::actor::ActorId keyring, - td::actor::ActorId adnl, td::actor::ActorId overlay_manager, - std::vector ids, PublicKeyHash local_id, CatChainSessionId unique_hash, - std::string db_root); + CatChainImpl(std::unique_ptr callback, const CatChainOptions &opts, + td::actor::ActorId keyring, td::actor::ActorId adnl, + td::actor::ActorId overlay_manager, std::vector ids, + const PublicKeyHash &local_id, const CatChainSessionId &unique_hash, std::string db_root, + std::string db_suffix, bool allow_unsafe_self_blocks_resync); void alarm() override; void start_up() override; diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 8fd70f7b..88a3671b 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) set(COMMON_SOURCE checksum.h @@ -8,7 +8,8 @@ set(COMMON_SOURCE errorlog.h errorlog.cpp -) + + global-version.h) add_library(common STATIC ${COMMON_SOURCE}) diff --git a/common/checksum.h b/common/checksum.h index 440d9359..ff692239 100644 --- a/common/checksum.h +++ b/common/checksum.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/common/delay.h b/common/delay.h index 111a9f35..9b98c58e 100644 --- a/common/delay.h +++ b/common/delay.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/common/errorcode.h b/common/errorcode.h index 71eee62b..b95e7fc2 100644 --- a/common/errorcode.h +++ b/common/errorcode.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/common/errorlog.cpp b/common/errorlog.cpp index 3dfcf9c8..a0616698 100644 --- a/common/errorlog.cpp +++ b/common/errorlog.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "errorlog.h" #include "checksum.h" diff --git a/common/errorlog.h b/common/errorlog.h index ed233000..884bfc4d 100644 --- a/common/errorlog.h +++ b/common/errorlog.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/tonlib/tonlib/ClientActor.cpp b/common/global-version.h similarity index 88% rename from tonlib/tonlib/ClientActor.cpp rename to common/global-version.h index d93ab1b9..a6775ffa 100644 --- a/tonlib/tonlib/ClientActor.cpp +++ b/common/global-version.h @@ -13,6 +13,12 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - - Copyright 2017-2019 Telegram Systems LLP */ +#pragma once + +namespace ton { + +// See doc/GlobalVersions.md +const int SUPPORTED_VERSION = 7; + +} diff --git a/common/int-to-string.hpp b/common/int-to-string.hpp index 414342cd..88aecc65 100644 --- a/common/int-to-string.hpp +++ b/common/int-to-string.hpp @@ -1,3 +1,21 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ #pragma once #include "td/utils/int_types.h" diff --git a/common/io.hpp b/common/io.hpp index c222e95a..e328636f 100644 --- a/common/io.hpp +++ b/common/io.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/common/status.h b/common/status.h index ddcdbfb9..dacc7ee5 100644 --- a/common/status.h +++ b/common/status.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/create-hardfork/CMakeLists.txt b/create-hardfork/CMakeLists.txt new file mode 100644 index 00000000..41b94b52 --- /dev/null +++ b/create-hardfork/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) + +if (NOT OPENSSL_FOUND) + find_package(OpenSSL REQUIRED) +endif() + + +set(CREATE_HARDFORK_SOURCE + create-hardfork.cpp +) + +add_executable(create-hardfork ${CREATE_HARDFORK_SOURCE}) +target_link_libraries(create-hardfork overlay tdutils tdactor adnl tl_api dht + rldp catchain validatorsession full-node validator-hardfork ton_validator + validator-hardfork fift-lib memprof git ${JEMALLOC_LIBRARIES}) + +install(TARGETS create-hardfork RUNTIME DESTINATION bin) diff --git a/create-hardfork/create-hardfork.cpp b/create-hardfork/create-hardfork.cpp new file mode 100644 index 00000000..c5f1add8 --- /dev/null +++ b/create-hardfork/create-hardfork.cpp @@ -0,0 +1,387 @@ +/* + This file is part of TON Blockchain source code. + + TON Blockchain is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + TON Blockchain 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TON Blockchain. If not, see . + + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement + from all source files in the program, then also delete it here. + + Copyright 2017-2020 Telegram Systems LLP +*/ +#include "adnl/adnl.h" +#include "adnl/utils.hpp" +#include "auto/tl/ton_api_json.h" +#include "dht/dht.h" +#include "overlay/overlays.h" +#include "td/utils/OptionParser.h" +#include "td/utils/Time.h" +#include "td/utils/filesystem.h" +#include "td/utils/format.h" +#include "td/utils/Random.h" +#include "td/utils/port/signals.h" +#include "td/utils/port/FileFd.h" +#include "catchain/catchain.h" +#include "validator-session/validator-session.h" +#include "validator/manager-hardfork.h" +#include "td/utils/filesystem.h" +#include "td/utils/port/path.h" + +#include "ton/ton-types.h" +#include "ton/ton-tl.hpp" +#include "ton/ton-io.hpp" + +#include "validator/fabric.h" +#include "validator/impl/collator.h" +#include "crypto/vm/vm.h" +#include "crypto/block/block-db.h" + +#include "common/errorlog.h" + +#if TD_DARWIN || TD_LINUX +#include +#endif +#include +#include +#include "git.h" + +int verbosity; + +struct IntError { + std::string err_msg; + IntError(std::string _msg) : err_msg(_msg) { + } + IntError(const char *_msg) : err_msg(_msg) { + } + IntError(td::Status _err) : err_msg(_err.to_string()) { + } + void show() const { + std::cerr << "fatal: " << err_msg << std::endl; + } +}; + +class HardforkCreator : public td::actor::Actor { + private: + td::actor::ActorOwn validator_manager_; + + std::string db_root_ = "/var/ton-work/db/"; + std::string global_config_; + td::Ref opts_; + td::BufferSlice bs_; + std::vector ext_msgs_; + std::vector top_shard_descrs_; + bool need_save_file_{false}; + bool tdescr_save_{false}; + std::string tdescr_pfx_; + ton::BlockIdExt shard_top_block_id_; + + ton::ShardIdFull shard_{ton::masterchainId}; + + public: + void set_db_root(std::string db_root) { + db_root_ = db_root; + } + void set_global_config_path(std::string path) { + global_config_ = path; + } + void set_shard(ton::ShardIdFull shard) { + LOG(DEBUG) << "setting shard to " << shard.to_str(); + shard_ = shard; + } + void set_shard_top_block(ton::BlockIdExt block_id) { + shard_top_block_id_ = block_id; + } + void set_top_descr_prefix(std::string tdescr_pfx) { + tdescr_pfx_ = tdescr_pfx; + tdescr_save_ = true; + } + void set_collator_flags(int flags) { + ton::collator_settings |= flags; + } + void start_up() override { + } + void alarm() override { + } + HardforkCreator() { + } + + void load_ext_message(std::string filename) { + try { + auto res1 = block::load_binary_file(filename); + if (res1.is_error()) { + throw IntError{res1.move_as_error()}; + } + ext_msgs_.emplace_back(res1.move_as_ok()); + } catch (IntError err) { + err.show(); + std::exit(7); + } + } + + void load_shard_block_message(std::string filename) { + try { + auto res1 = block::load_binary_file(filename); + if (res1.is_error()) { + throw IntError{res1.move_as_error()}; + } + top_shard_descrs_.emplace_back(res1.move_as_ok()); + } catch (IntError err) { + err.show(); + std::exit(7); + } + } + + void do_save_file() { + } + + td::Status create_validator_options() { + if(!global_config_.length()) { + opts_ = ton::validator::ValidatorManagerOptions::create( + ton::BlockIdExt{ton::masterchainId, ton::shardIdAll, 0, ton::RootHash::zero(), ton::FileHash::zero()}, + ton::BlockIdExt{ton::masterchainId, ton::shardIdAll, 0, ton::RootHash::zero(), ton::FileHash::zero()}); + return td::Status::OK(); + } + TRY_RESULT_PREFIX(conf_data, td::read_file(global_config_), "failed to read: "); + TRY_RESULT_PREFIX(conf_json, td::json_decode(conf_data.as_slice()), "failed to parse json: "); + + ton::ton_api::config_global conf; + TRY_STATUS_PREFIX(ton::ton_api::from_json(conf, conf_json.get_object()), "json does not fit TL scheme: "); + + auto zero_state = ton::create_block_id(conf.validator_->zero_state_); + ton::BlockIdExt init_block; + if (!conf.validator_->init_block_) { + LOG(INFO) << "no init block in config. using zero state"; + init_block = zero_state; + } else { + init_block = ton::create_block_id(conf.validator_->init_block_); + } + opts_ = ton::validator::ValidatorManagerOptions::create(zero_state, init_block); + std::vector h; + for (auto &x : conf.validator_->hardforks_) { + auto b = ton::create_block_id(x); + if (!b.is_masterchain()) { + return td::Status::Error(ton::ErrorCode::error, + "[validator/hardforks] section contains not masterchain block id"); + } + if (!b.is_valid_full()) { + return td::Status::Error(ton::ErrorCode::error, "[validator/hardforks] section contains invalid block_id"); + } + for (auto &y : h) { + if (y.is_valid() && y.seqno() >= b.seqno()) { + y.invalidate(); + } + } + h.push_back(b); + } + opts_.write().set_hardforks(std::move(h)); + return td::Status::OK(); + } + + void run() { + td::mkdir(db_root_).ensure(); + ton::errorlog::ErrorLog::create(db_root_); + if (!shard_.is_masterchain() && need_save_file_) { + td::mkdir(db_root_ + "/static").ensure(); + do_save_file(); + } + + auto Sr = create_validator_options(); + if (Sr.is_error()) { + LOG(ERROR) << "failed to load global config'" << global_config_ << "': " << Sr; + std::_Exit(2); + } + + auto opts = opts_; + opts.write().set_initial_sync_disabled(true); + validator_manager_ = + ton::validator::ValidatorManagerHardforkFactory::create(opts, shard_, shard_top_block_id_, db_root_); + for (auto &msg : ext_msgs_) { + td::actor::send_closure(validator_manager_, &ton::validator::ValidatorManager::new_external_message, + std::move(msg), 0); + } + for (auto &topmsg : top_shard_descrs_) { + td::actor::send_closure(validator_manager_, &ton::validator::ValidatorManager::new_shard_block, ton::BlockIdExt{}, + 0, std::move(topmsg)); + } + class Callback : public ton::validator::ValidatorManagerInterface::Callback { + private: + td::actor::ActorId id_; + bool tdescr_save_; + std::string tdescr_pfx_; + int tdescr_cnt_ = 0; + + public: + Callback(td::actor::ActorId id, bool tdescr_save = false, + std::string tdescr_pfx = "") + : id_(id), tdescr_save_(tdescr_save), tdescr_pfx_(tdescr_pfx) { + } + + void initial_read_complete(ton::validator::BlockHandle handle) override { + td::actor::send_closure(id_, &ton::validator::ValidatorManager::sync_complete, + td::PromiseCreator::lambda([](td::Unit) {})); + } + void add_shard(ton::ShardIdFull) override { + } + void del_shard(ton::ShardIdFull) override { + } + void send_ihr_message(ton::AccountIdPrefixFull dst, td::BufferSlice data) override { + } + void send_ext_message(ton::AccountIdPrefixFull dst, td::BufferSlice data) override { + } + void send_shard_block_info(ton::BlockIdExt block_id, ton::CatchainSeqno cc_seqno, td::BufferSlice data) override { + } + void send_broadcast(ton::BlockBroadcast broadcast) override { + } + void download_block(ton::BlockIdExt block_id, td::uint32 priority, td::Timestamp timeout, + td::Promise promise) override { + } + void download_zero_state(ton::BlockIdExt block_id, td::uint32 priority, td::Timestamp timeout, + td::Promise promise) override { + } + void download_persistent_state(ton::BlockIdExt block_id, ton::BlockIdExt masterchain_block_id, + td::uint32 priority, td::Timestamp timeout, + td::Promise promise) override { + } + void download_block_proof(ton::BlockIdExt block_id, td::uint32 priority, td::Timestamp timeout, + td::Promise promise) override { + } + void download_block_proof_link(ton::BlockIdExt block_id, td::uint32 priority, td::Timestamp timeout, + td::Promise promise) override { + } + void get_next_key_blocks(ton::BlockIdExt block_id, td::Timestamp timeout, + td::Promise> promise) override { + } + void download_archive(ton::BlockSeqno masterchain_seqno, std::string tmp_dir, td::Timestamp timeout, + + td::Promise promise) override { + } + + void new_key_block(ton::validator::BlockHandle handle) override { + } + }; + + td::actor::send_closure(validator_manager_, &ton::validator::ValidatorManagerInterface::install_callback, + std::make_unique(validator_manager_.get(), tdescr_save_, tdescr_pfx_), + td::PromiseCreator::lambda([](td::Unit) {})); + } +}; + +td::Result get_uint256(td::Slice str) { + TRY_RESULT(R, td::base64url_decode(str)); + if (R.length() != 32) { + return td::Status::Error("uint256 must have 64 bytes"); + } + td::Bits256 x; + as_slice(x).copy_from(td::Slice(R)); + return x; +} + +int parse_hex_digit(int c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + c |= 0x20; + if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + return -1; +} + +int main(int argc, char *argv[]) { + SET_VERBOSITY_LEVEL(verbosity_INFO); + td::set_default_failure_signal_handler().ensure(); + + vm::init_vm().ensure(); + + td::actor::ActorOwn x; + + td::OptionParser p; + p.set_description("test collate block"); + p.add_option('h', "help", "prints_help", [&]() { + char b[10240]; + td::StringBuilder sb(td::MutableSlice{b, 10000}); + sb << p; + std::cout << sb.as_cslice().c_str(); + std::exit(2); + }); + p.add_option('V', "version", "shows create-hardfork build information", [&]() { + std::cout << "create-hardfork build information: [ Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate() << "]\n"; + std::exit(0); + }); + p.add_option('D', "db", "root for dbs", + [&](td::Slice fname) { td::actor::send_closure(x, &HardforkCreator::set_db_root, fname.str()); }); + p.add_option('C', "config", "global config path", + [&](td::Slice fname) { td::actor::send_closure(x, &HardforkCreator::set_global_config_path, fname.str()); }); + p.add_option('m', "ext-message", "binary file with serialized inbound external message", + [&](td::Slice fname) { td::actor::send_closure(x, &HardforkCreator::load_ext_message, fname.str()); }); + p.add_option( + 'M', "top-shard-message", "binary file with serialized shard top block description", + [&](td::Slice fname) { td::actor::send_closure(x, &HardforkCreator::load_shard_block_message, fname.str()); }); + p.add_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) { + int v = VERBOSITY_NAME(FATAL) + (verbosity = td::to_integer(arg)); + SET_VERBOSITY_LEVEL(v); + }); + p.add_checked_option('w', "workchain", "[:]\tcollate block in this workchain", [&](td::Slice arg) { + ton::ShardId shard = 0; + auto pos = std::min(arg.find(':'), arg.size()); + TRY_RESULT(workchain, td::to_integer_safe(arg.substr(0, pos))); + int s = 60; + while (++pos < arg.size()) { + int x = parse_hex_digit(arg[pos]); + if (x < 0 || s < 0) { + return td::Status::Error("cannot parse hexadecimal shard id (prefix)"); + } + shard |= (ton::ShardId(x) << s); + s -= 4; + } + td::actor::send_closure(x, &HardforkCreator::set_shard, + ton::ShardIdFull{workchain, shard ? shard : ton::shardIdAll}); + return td::Status::OK(); + }); + p.add_checked_option('T', "top-block", "BlockIdExt of top block (new block will be generated atop of it)", + [&](td::Slice arg) { + ton::BlockIdExt block_id; + if (block::parse_block_id_ext(arg, block_id)) { + LOG(INFO) << "setting previous block to " << block_id.to_str(); + td::actor::send_closure(x, &HardforkCreator::set_shard_top_block, block_id); + + return td::Status::OK(); + } else { + return td::Status::Error("cannot parse BlockIdExt"); + } + }); + p.add_option('d', "daemonize", "set SIGHUP", [&]() { + td::set_signal_handler(td::SignalType::HangUp, [](int sig) { +#if TD_DARWIN || TD_LINUX + close(0); + setsid(); +#endif + }).ensure(); + }); + + td::actor::Scheduler scheduler({7}); + + scheduler.run_in_context([&] { x = td::actor::create_actor("testnode"); }); + + scheduler.run_in_context([&] { p.run(argc, argv).ensure(); }); + scheduler.run_in_context([&] { td::actor::send_closure(x, &HardforkCreator::run); }); + scheduler.run(); + + return 0; +} diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index ff7d9227..29b95466 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -1,10 +1,10 @@ -cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) if (NOT OPENSSL_FOUND) find_package(OpenSSL REQUIRED) endif() -set(TON_CRYPTO_SOURCE +set(TON_CRYPTO_CORE_SOURCE Ed25519.cpp common/bigint.cpp common/refcnt.cpp @@ -19,23 +19,8 @@ set(TON_CRYPTO_SOURCE openssl/bignum.cpp openssl/residue.cpp openssl/rand.cpp - vm/stack.cpp - vm/atom.cpp - vm/continuation.cpp - vm/dict.cpp - vm/dispatch.cpp - vm/opctable.cpp - vm/cp0.cpp - vm/stackops.cpp - vm/tupleops.cpp - vm/arithops.cpp - vm/cellops.cpp - vm/contops.cpp - vm/dictops.cpp - vm/debugops.cpp - vm/tonops.cpp vm/boc.cpp - vm/utils.cpp + vm/large-boc-serializer.cpp tl/tlblib.cpp Ed25519.h @@ -46,6 +31,8 @@ set(TON_CRYPTO_SOURCE common/refint.h common/bigexp.h common/util.h + common/linalloc.hpp + common/promiseop.hpp ellcurve/Ed25519.h ellcurve/Fp25519.h @@ -53,7 +40,7 @@ set(TON_CRYPTO_SOURCE ellcurve/TwEdwards.h openssl/bignum.h - openssl/digest.h + openssl/digest.hpp openssl/rand.hpp openssl/residue.h @@ -61,31 +48,10 @@ set(TON_CRYPTO_SOURCE tl/tlbc-data.h tl/tlblib.hpp - vm/arithops.h - vm/atom.h - vm/boc.h - vm/box.hpp - vm/cellops.h - vm/continuation.h - vm/contops.h - vm/cp0.h - vm/debugops.h - vm/dict.h - vm/dictops.h - vm/excno.hpp - vm/fmt.hpp - vm/log.h - vm/opctable.h - vm/stack.hpp - vm/stackops.h - vm/tupleops.h - vm/tonops.h - vm/vmstate.h - vm/utils.h - - vm/cells.h - vm/cellslice.h + keccak/keccak.h + keccak/keccak.cpp + vm/dict.cpp vm/cells/Cell.cpp vm/cells/CellBuilder.cpp vm/cells/CellHash.cpp @@ -98,6 +64,7 @@ set(TON_CRYPTO_SOURCE vm/cells/MerkleProof.cpp vm/cells/MerkleUpdate.cpp + vm/dict.h vm/cells/Cell.h vm/cells/CellBuilder.h vm/cells/CellHash.h @@ -116,12 +83,64 @@ set(TON_CRYPTO_SOURCE vm/cells/VirtualCell.h vm/cells/VirtualizationParameters.h + vm/cells.h + vm/cellslice.h + vm/db/StaticBagOfCellsDb.h vm/db/StaticBagOfCellsDb.cpp - vm/db/BlobView.h - vm/db/BlobView.cpp -) + vm/Hasher.h + vm/Hasher.cpp + + ellcurve/secp256k1.h + ellcurve/secp256k1.cpp + ellcurve/p256.h + ellcurve/p256.cpp) + +set(TON_CRYPTO_SOURCE + vm/stack.cpp + vm/atom.cpp + vm/continuation.cpp + vm/memo.cpp + vm/dispatch.cpp + vm/opctable.cpp + vm/cp0.cpp + vm/stackops.cpp + vm/tupleops.cpp + vm/arithops.cpp + vm/cellops.cpp + vm/contops.cpp + vm/dictops.cpp + vm/debugops.cpp + vm/tonops.cpp + vm/utils.cpp + vm/vm.cpp + vm/bls.cpp + + vm/arithops.h + vm/atom.h + vm/boc.h + vm/boc-writers.h + vm/box.hpp + vm/cellops.h + vm/continuation.h + vm/contops.h + vm/cp0.h + vm/debugops.h + vm/dictops.h + vm/excno.hpp + vm/fmt.hpp + vm/log.h + vm/memo.h + vm/opctable.h + vm/stack.hpp + vm/stackops.h + vm/tupleops.h + vm/tonops.h + vm/vmstate.h + vm/utils.h + vm/vm.h + vm/bls.h) set(TON_DB_SOURCE vm/db/DynamicBagOfCellsDb.cpp @@ -138,6 +157,8 @@ set(FIFT_SOURCE fift/Dictionary.cpp fift/Fift.cpp fift/IntCtx.cpp + fift/HashMap.cpp + fift/Continuation.cpp fift/SourceLookup.cpp fift/utils.cpp fift/words.cpp @@ -145,6 +166,8 @@ set(FIFT_SOURCE fift/Dictionary.h fift/Fift.h fift/IntCtx.h + fift/HashMap.h + fift/Continuation.h fift/SourceLookup.h fift/utils.h fift/words.h @@ -172,6 +195,7 @@ set(FUNC_LIB_SOURCE func/stack-transform.cpp func/optimize.cpp func/codegen.cpp + func/func.cpp ) set(TLB_BLOCK_AUTO @@ -189,6 +213,7 @@ set(BLOCK_SOURCE block/mc-config.cpp block/output-queue-merger.cpp block/transaction.cpp + block/precompiled-smc/PrecompiledSmartContract.cpp ${TLB_BLOCK_AUTO} block/block-binlog.h @@ -199,28 +224,33 @@ set(BLOCK_SOURCE block/check-proof.h block/output-queue-merger.h block/transaction.h + block/precompiled-smc/PrecompiledSmartContract.h + block/precompiled-smc/common.h ) set(SMC_ENVELOPE_SOURCE smc-envelope/GenericAccount.cpp smc-envelope/HighloadWallet.cpp + smc-envelope/HighloadWalletV2.cpp + smc-envelope/ManualDns.cpp smc-envelope/MultisigWallet.cpp + smc-envelope/PaymentChannel.cpp smc-envelope/SmartContract.cpp smc-envelope/SmartContractCode.cpp - smc-envelope/TestGiver.cpp - smc-envelope/TestWallet.cpp - smc-envelope/Wallet.cpp + smc-envelope/WalletInterface.cpp smc-envelope/WalletV3.cpp + smc-envelope/WalletV4.cpp smc-envelope/GenericAccount.h smc-envelope/HighloadWallet.h + smc-envelope/HighloadWalletV2.h + smc-envelope/ManualDns.h smc-envelope/MultisigWallet.h smc-envelope/SmartContract.h smc-envelope/SmartContractCode.h - smc-envelope/TestGiver.h - smc-envelope/TestWallet.h - smc-envelope/Wallet.h + smc-envelope/WalletInterface.h smc-envelope/WalletV3.h + smc-envelope/WalletV4.h ) set(ED25519_TEST_SOURCE @@ -253,15 +283,70 @@ set(FIFT_TEST_SOURCE PARENT_SCOPE ) +set(BIGINT_TEST_SOURCE + ${CMAKE_CURRENT_SOURCE_DIR}/test/test-bigint.cpp + PARENT_SCOPE +) + +set(USE_EMSCRIPTEN ${USE_EMSCRIPTEN} PARENT_SCOPE) + +add_library(ton_crypto_core STATIC ${TON_CRYPTO_CORE_SOURCE}) +target_include_directories(ton_crypto_core PUBLIC $ + $) +target_link_libraries(ton_crypto_core PUBLIC ${OPENSSL_CRYPTO_LIBRARY} tdutils tddb_utils) +if (NOT WIN32) + target_link_libraries(ton_crypto_core PUBLIC dl z) +endif() +target_include_directories(ton_crypto_core SYSTEM PUBLIC $) + add_library(ton_crypto STATIC ${TON_CRYPTO_SOURCE}) target_include_directories(ton_crypto PUBLIC $ $) -target_link_libraries(ton_crypto PUBLIC ${OPENSSL_CRYPTO_LIBRARY} tdutils) +target_link_libraries(ton_crypto PUBLIC ${OPENSSL_CRYPTO_LIBRARY} tdutils tddb_utils ton_crypto_core ton_block) +if (USE_EMSCRIPTEN) + target_link_options(ton_crypto PRIVATE -fexceptions) + target_compile_options(ton_crypto PRIVATE -fexceptions) +endif() if (NOT WIN32) - target_link_libraries(ton_crypto PUBLIC dl z) + find_library(DL dl) + if (DL) + target_link_libraries(ton_crypto PUBLIC dl z) + else() + target_link_libraries(ton_crypto PUBLIC z) + endif() endif() target_include_directories(ton_crypto SYSTEM PUBLIC $) +add_dependencies(ton_crypto blst) +target_include_directories(ton_crypto PRIVATE ${BLST_INCLUDE_DIR}) +target_link_libraries(ton_crypto PRIVATE ${BLST_LIB}) + +if (NOT USE_EMSCRIPTEN) + find_package(Secp256k1 REQUIRED) +endif() + +if (MSVC) + find_package(Sodium REQUIRED) + target_compile_definitions(ton_crypto PUBLIC SODIUM_STATIC) + target_include_directories(ton_crypto_core PUBLIC $) + target_link_libraries(ton_crypto PUBLIC ${SECP256K1_LIBRARY}) +elseif (ANDROID OR EMSCRIPTEN) + target_include_directories(ton_crypto_core PUBLIC $) + target_link_libraries(ton_crypto PUBLIC $) +else() + if (NOT SODIUM_FOUND) + find_package(Sodium REQUIRED) + else() + message(STATUS "Using Sodium ${SODIUM_LIBRARY_RELEASE}") + endif() + target_compile_definitions(ton_crypto PUBLIC SODIUM_STATIC) + target_include_directories(ton_crypto_core PUBLIC $) + target_link_libraries(ton_crypto PUBLIC ${SECP256K1_LIBRARY}) +endif() + +target_include_directories(ton_crypto_core PUBLIC $) +target_link_libraries(ton_crypto PUBLIC ${SODIUM_LIBRARY_RELEASE}) + add_library(ton_db STATIC ${TON_DB_SOURCE}) target_include_directories(ton_db PUBLIC $ $) @@ -271,39 +356,81 @@ add_executable(test-ed25519-crypto test/test-ed25519-crypto.cpp) target_include_directories(test-ed25519-crypto PUBLIC $) target_link_libraries(test-ed25519-crypto PUBLIC ton_crypto) -add_library(fift-lib ${FIFT_SOURCE}) +add_library(fift-lib STATIC ${FIFT_SOURCE}) target_include_directories(fift-lib PUBLIC $) target_link_libraries(fift-lib PUBLIC ton_crypto ton_db tdutils ton_block) +if (USE_EMSCRIPTEN) + target_link_options(fift-lib PRIVATE -fexceptions) + target_compile_options(fift-lib PRIVATE -fexceptions) +endif() set_target_properties(fift-lib PROPERTIES OUTPUT_NAME fift) add_executable(fift fift/fift-main.cpp) -target_link_libraries(fift PUBLIC fift-lib) +target_link_libraries(fift PUBLIC fift-lib git) if (WINGETOPT_FOUND) target_link_libraries_system(fift wingetopt) endif() add_library(src_parser ${PARSER_SOURCE}) target_include_directories(src_parser PUBLIC $) -target_link_libraries(src_parser PUBLIC ton_crypto) +target_link_libraries(src_parser PUBLIC ton_crypto_core) -add_executable(func func/func.cpp ${FUNC_LIB_SOURCE}) +add_library(ton_block STATIC ${BLOCK_SOURCE}) +target_include_directories(ton_block PUBLIC $ + $ $) +target_link_libraries(ton_block PUBLIC ton_crypto tdutils tdactor tl_api) +if (USE_EMSCRIPTEN) + target_link_options(ton_block PRIVATE -fexceptions) + target_compile_options(ton_block PRIVATE -fexceptions) +endif() + +add_executable(func func/func-main.cpp ${FUNC_LIB_SOURCE}) target_include_directories(func PUBLIC $) -target_link_libraries(func PUBLIC ton_crypto src_parser) +target_link_libraries(func PUBLIC ton_crypto src_parser git ton_block) if (WINGETOPT_FOUND) target_link_libraries_system(func wingetopt) endif() +if (USE_EMSCRIPTEN) + add_executable(funcfiftlib funcfiftlib/funcfiftlib.cpp ${FUNC_LIB_SOURCE}) + target_include_directories(funcfiftlib PUBLIC $) + target_link_libraries(funcfiftlib PUBLIC fift-lib src_parser git) + target_link_options(funcfiftlib PRIVATE -sEXPORTED_RUNTIME_METHODS=FS,ccall,cwrap,UTF8ToString,stringToUTF8,lengthBytesUTF8,addFunction,removeFunction,setValue) + target_link_options(funcfiftlib PRIVATE -sEXPORTED_FUNCTIONS=_func_compile,_version,_malloc,_free,_setThrew) + target_link_options(funcfiftlib PRIVATE -sEXPORT_NAME=CompilerModule) + target_link_options(funcfiftlib PRIVATE -sERROR_ON_UNDEFINED_SYMBOLS=0) + target_link_options(funcfiftlib PRIVATE -sFILESYSTEM=1 -lnodefs.js) + target_link_options(funcfiftlib PRIVATE -Oz) + target_link_options(funcfiftlib PRIVATE -sIGNORE_MISSING_MAIN=1) + target_link_options(funcfiftlib PRIVATE -sAUTO_NATIVE_LIBRARIES=0) + target_link_options(funcfiftlib PRIVATE -sMODULARIZE=1) + target_link_options(funcfiftlib PRIVATE -sTOTAL_MEMORY=33554432) + target_link_options(funcfiftlib PRIVATE -sALLOW_MEMORY_GROWTH=1) + target_link_options(funcfiftlib PRIVATE -sALLOW_TABLE_GROWTH=1) + target_link_options(funcfiftlib PRIVATE --embed-file ${CMAKE_CURRENT_SOURCE_DIR}/fift/lib@/fiftlib) + target_link_options(funcfiftlib PRIVATE --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/funcfiftlib/funcfiftlib-prejs.js) + target_link_options(funcfiftlib PRIVATE -fexceptions) + target_compile_options(funcfiftlib PRIVATE -fexceptions -fno-stack-protector) +endif() + add_executable(tlbc tl/tlbc.cpp) target_include_directories(tlbc PUBLIC $) -target_link_libraries(tlbc PUBLIC ton_crypto src_parser) +target_link_libraries(tlbc PUBLIC ton_crypto_core src_parser) if (WINGETOPT_FOUND) target_link_libraries_system(tlbc wingetopt) endif() -add_library(ton_block ${BLOCK_SOURCE}) -target_include_directories(ton_block PUBLIC $ - $ $) -target_link_libraries(ton_block PUBLIC ton_crypto tdutils tdactor tl_api) +add_library(pow-miner-lib util/Miner.cpp util/Miner.h) +target_include_directories(pow-miner-lib PUBLIC $) +target_link_libraries(pow-miner-lib PUBLIC ton_crypto ton_block) + +add_executable(pow-miner util/pow-miner.cpp) +target_link_libraries(pow-miner PRIVATE ton_crypto ton_block pow-miner-lib git) + +if (WINGETOPT_FOUND) + target_link_libraries_system(fift wingetopt) + target_link_libraries_system(pow-miner wingetopt) +endif() set(TURN_OFF_LSAN cd .) if (TON_USE_ASAN AND NOT WIN32) @@ -311,16 +438,26 @@ if (TON_USE_ASAN AND NOT WIN32) endif() file(MAKE_DIRECTORY smartcont/auto) -if (NOT CMAKE_CROSSCOMPILING) +if (NOT CMAKE_CROSSCOMPILING OR USE_EMSCRIPTEN) set(GENERATE_TLB_CMD tlbc) - add_custom_command( - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/block - COMMAND ${TURN_OFF_LSAN} - COMMAND ${GENERATE_TLB_CMD} -o block-auto -n block::gen -z block.tlb - COMMENT "Generate block tlb source files" - OUTPUT ${TLB_BLOCK_AUTO} - DEPENDS tlbc block/block.tlb - ) + if (NOT USE_EMSCRIPTEN) + add_custom_command( + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/block + COMMAND ${TURN_OFF_LSAN} + COMMAND ${GENERATE_TLB_CMD} -o block-auto -n block::gen -z block.tlb + COMMENT "Generate block tlb source files" + OUTPUT ${TLB_BLOCK_AUTO} + DEPENDS tlbc block/block.tlb + ) + else() + add_custom_command( + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/block + COMMAND ${TURN_OFF_LSAN} + COMMENT "Generate block tlb source files" + OUTPUT ${TLB_BLOCK_AUTO} + DEPENDS tlbc block/block.tlb + ) + endif() add_custom_target(tlb_generate_block DEPENDS ${TLB_BLOCK_AUTO}) add_dependencies(ton_block tlb_generate_block) @@ -331,7 +468,7 @@ if (NOT CMAKE_CROSSCOMPILING) set(multiValueArgs SOURCE) set(FUNC_LIB_SOURCE smartcont/stdlib.fc) cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) - string(REGEX REPLACE "[^a-zA-Z_]" "_" ID ${ARG_DEST}) + string(REGEX REPLACE "[^0-9a-zA-Z_]" "_" ID ${ARG_DEST}) set(ARG_DEST_FIF "${ARG_DEST}.fif") add_custom_command( COMMENT "Generate ${ARG_DEST_FIF}" @@ -347,7 +484,7 @@ if (NOT CMAKE_CROSSCOMPILING) WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND fift -Ifift/lib:smartcont -s asm-to-cpp.fif ${ARG_DEST_FIF} ${ARG_DEST_CPP} ${ARG_NAME} MAIN_DEPENDENCY ${ARG_SOURCE} - DEPENDS fift ${ARG_DEST_FIF} smartcont/asm-to-cpp.fif + DEPENDS fift ${ARG_DEST_FIF} smartcont/asm-to-cpp.fif fift/lib/Asm.fif OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/${ARG_DEST_CPP} ) add_custom_target(gen_fif_${ID} DEPENDS ${ARG_DEST_FIF} ${ARG_DEST_CPP}) @@ -359,16 +496,23 @@ if (NOT CMAKE_CROSSCOMPILING) GenFif(DEST smartcont/auto/wallet3-code SOURCE smartcont/wallet3-code.fc NAME wallet3) GenFif(DEST smartcont/auto/simple-wallet-code SOURCE smartcont/simple-wallet-code.fc NAME simple-wallet) GenFif(DEST smartcont/auto/highload-wallet-code SOURCE smartcont/highload-wallet-code.fc NAME highload-wallet) - GenFif(DEST smartcont/auto/highload-wallet-v2-code SOURCE smartcont/highload-wallet-v2-code.fc NAME highoad-wallet-v2) + GenFif(DEST smartcont/auto/highload-wallet-v2-code SOURCE smartcont/highload-wallet-v2-code.fc NAME highload-wallet-v2) GenFif(DEST smartcont/auto/elector-code SOURCE smartcont/elector-code.fc NAME elector-code) GenFif(DEST smartcont/auto/multisig-code SOURCE smartcont/multisig-code.fc NAME multisig) GenFif(DEST smartcont/auto/restricted-wallet-code SOURCE smartcont/restricted-wallet-code.fc NAME restricted-wallet) GenFif(DEST smartcont/auto/restricted-wallet2-code SOURCE smartcont/restricted-wallet2-code.fc NAME restricted-wallet2) + GenFif(DEST smartcont/auto/restricted-wallet3-code SOURCE smartcont/restricted-wallet3-code.fc NAME restricted-wallet3) + GenFif(DEST smartcont/auto/pow-testgiver-code SOURCE smartcont/pow-testgiver-code.fc NAME pow-testgiver) + + GenFif(DEST smartcont/auto/dns-manual-code SOURCE smartcont/dns-manual-code.fc NAME dns-manual) + GenFif(DEST smartcont/auto/dns-auto-code SOURCE smartcont/dns-auto-code.fc NAME dns-auto) + + GenFif(DEST smartcont/auto/payment-channel-code SOURCE smartcont/payment-channel-code.fc NAME payment-channel) GenFif(DEST smartcont/auto/simple-wallet-ext-code SOURCE smartcont/simple-wallet-ext-code.fc NAME simple-wallet-ext) endif() -add_library(smc-envelope ${SMC_ENVELOPE_SOURCE}) +add_library(smc-envelope STATIC ${SMC_ENVELOPE_SOURCE}) target_include_directories(smc-envelope PUBLIC $) target_link_libraries(smc-envelope PUBLIC ton_crypto PRIVATE tdutils ton_block) if (NOT CMAKE_CROSSCOMPILING) @@ -378,7 +522,15 @@ endif() add_executable(create-state block/create-state.cpp) target_include_directories(create-state PUBLIC $ $) -target_link_libraries(create-state PUBLIC ton_crypto fift-lib ton_block) +if (INTERNAL_COMPILE) + target_link_libraries(create-state PUBLIC ton_crypto fift-lib ton_block tonlib git) +else() + if (TONLIB_COMPILE) + target_link_libraries(create-state PUBLIC ton_crypto fift-lib ton_block tonlib git) + else() + target_link_libraries(create-state PUBLIC ton_crypto fift-lib ton_block git) + endif() +endif() if (WINGETOPT_FOUND) target_link_libraries_system(create-state wingetopt) endif() @@ -386,7 +538,7 @@ endif() add_executable(dump-block block/dump-block.cpp) target_include_directories(dump-block PUBLIC $ $) -target_link_libraries(dump-block PUBLIC ton_crypto fift-lib ton_block) +target_link_libraries(dump-block PUBLIC ton_crypto fift-lib ton_block git) if (WINGETOPT_FOUND) target_link_libraries_system(dump-block wingetopt) endif() @@ -394,10 +546,20 @@ endif() add_executable(adjust-block block/adjust-block.cpp) target_include_directories(adjust-block PUBLIC $ $) -target_link_libraries(adjust-block PUBLIC ton_crypto fift-lib ton_block) +target_link_libraries(adjust-block PUBLIC ton_crypto fift-lib ton_block git) if (WINGETOPT_FOUND) target_link_libraries_system(dump-block wingetopt) + target_link_libraries_system(adjust-block wingetopt) endif() -install(TARGETS fift func RUNTIME DESTINATION bin) +add_executable(test-weight-distr block/test-weight-distr.cpp) +target_include_directories(test-weight-distr PUBLIC $ + $) +target_link_libraries(test-weight-distr PUBLIC ton_crypto fift-lib ton_block git) +if (WINGETOPT_FOUND) + target_link_libraries_system(test-weight-distr wingetopt) +endif() + +install(TARGETS fift func create-state tlbc RUNTIME DESTINATION bin) install(DIRECTORY fift/lib/ DESTINATION lib/fift) +install(DIRECTORY smartcont DESTINATION share/ton) diff --git a/crypto/Ed25519.cpp b/crypto/Ed25519.cpp index 6a21f83f..c263a0cf 100644 --- a/crypto/Ed25519.cpp +++ b/crypto/Ed25519.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "crypto/Ed25519.h" @@ -22,7 +22,7 @@ #include -#if OPENSSL_VERSION_NUMBER >= 0x10101000L && OPENSSL_VERSION_NUMBER != 0x20000000L +#if OPENSSL_VERSION_NUMBER >= 0x10101000L && OPENSSL_VERSION_NUMBER != 0x20000000L || defined(OPENSSL_IS_BORINGSSL) #include "td/utils/base64.h" #include "td/utils/BigNum.h" @@ -57,7 +57,7 @@ SecureString Ed25519::PrivateKey::as_octet_string() const { return octet_string_.copy(); } -#if OPENSSL_VERSION_NUMBER >= 0x10101000L && OPENSSL_VERSION_NUMBER != 0x20000000L +#if OPENSSL_VERSION_NUMBER >= 0x10101000L && OPENSSL_VERSION_NUMBER != 0x20000000L || defined(OPENSSL_IS_BORINGSSL) namespace detail { @@ -310,6 +310,10 @@ Result Ed25519::compute_shared_secret(const PublicKey &public_key, return std::move(result); } +int Ed25519::version() { + return OPENSSL_VERSION_NUMBER; +} + #else Result Ed25519::generate_private_key() { @@ -387,6 +391,10 @@ Result Ed25519::compute_shared_secret(const PublicKey &public_key, return std::move(shared_secret); } +int Ed25519::version() { + return 0; +} + #endif } // namespace td diff --git a/crypto/Ed25519.h b/crypto/Ed25519.h index 21dcf7dd..a3340d2e 100644 --- a/crypto/Ed25519.h +++ b/crypto/Ed25519.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -65,6 +65,8 @@ class Ed25519 { static Result generate_private_key(); static Result compute_shared_secret(const PublicKey &public_key, const PrivateKey &private_key); + + static int version(); }; } // namespace td diff --git a/crypto/block/Binlog.cpp b/crypto/block/Binlog.cpp index ac1f1415..b774e693 100644 --- a/crypto/block/Binlog.cpp +++ b/crypto/block/Binlog.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "crypto/block/Binlog.h" diff --git a/crypto/block/Binlog.h b/crypto/block/Binlog.h index f5b66060..64142fe8 100644 --- a/crypto/block/Binlog.h +++ b/crypto/block/Binlog.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/crypto/block/adjust-block.cpp b/crypto/block/adjust-block.cpp index ba9d22cd..16c77691 100644 --- a/crypto/block/adjust-block.cpp +++ b/crypto/block/adjust-block.cpp @@ -23,7 +23,7 @@ exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "block/block.h" #include "vm/boc.h" @@ -34,6 +34,7 @@ #include "vm/cp0.h" #include "td/utils/crypto.h" #include +#include "git.h" using td::Ref; using namespace std::literals::string_literals; @@ -144,7 +145,7 @@ int main(int argc, char* const argv[]) { int i, vseqno_incr = 1; int new_verbosity_level = VERBOSITY_NAME(INFO); std::string in_fname, out_fname; - while ((i = getopt(argc, argv, "hi:v:")) != -1) { + while ((i = getopt(argc, argv, "hi:v:V")) != -1) { switch (i) { case 'h': usage(); @@ -156,6 +157,10 @@ int main(int argc, char* const argv[]) { case 'v': new_verbosity_level = VERBOSITY_NAME(FATAL) + (verbosity = td::to_integer(td::Slice(optarg))); break; + case 'V': + std::cout << "adjust-block build information: [ Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate() << "]\n"; + std::exit(0); + break; default: usage(); break; diff --git a/crypto/block/block-binlog.h b/crypto/block/block-binlog.h index 48346e77..904f4c73 100644 --- a/crypto/block/block-binlog.h +++ b/crypto/block/block-binlog.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "td/utils/int_types.h" diff --git a/crypto/block/block-db-impl.h b/crypto/block/block-db-impl.h index 868206d8..07869ca9 100644 --- a/crypto/block/block-db-impl.h +++ b/crypto/block/block-db-impl.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include diff --git a/crypto/block/block-db.cpp b/crypto/block/block-db.cpp index f2018b80..21c7c0a0 100644 --- a/crypto/block/block-db.cpp +++ b/crypto/block/block-db.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "block-db.h" #include "block-db-impl.h" @@ -624,6 +624,7 @@ void BlockDbImpl::get_block_by_id(ton::BlockId blk_id, bool need_data, td::Promi } } promise(it->second); + return; } promise(td::Status::Error(-666, "block not found in database")); } @@ -642,6 +643,7 @@ void BlockDbImpl::get_state_by_id(ton::BlockId blk_id, bool need_data, td::Promi } } promise(it->second); + return; } if (zerostate.not_null() && blk_id == zerostate->blk.id) { LOG(DEBUG) << "get_state_by_id(): zerostate requested"; @@ -666,6 +668,7 @@ void BlockDbImpl::get_out_queue_info_by_id(ton::BlockId blk_id, td::Promisesecond->data.is_null()) { LOG(DEBUG) << "loading data for state " << blk_id.to_str(); @@ -679,6 +682,7 @@ void BlockDbImpl::get_out_queue_info_by_id(ton::BlockId blk_id, td::Promisesecond->data.clone(), options); @@ -707,10 +711,12 @@ void BlockDbImpl::get_out_queue_info_by_id(ton::BlockId blk_id, td::Promisesecond->blk.root_hash != state_root->get_hash().bits()) { promise(td::Status::Error( -668, std::string{"state for block "} + blk_id.to_str() + " is invalid : state root hash mismatch")); + return; } vm::CellSlice cs = vm::load_cell_slice(state_root); if (!cs.have(64, 1) || cs.prefetch_ulong(32) != 0x9023afde) { promise(td::Status::Error(-668, std::string{"state for block "} + blk_id.to_str() + " is invalid")); + return; } auto out_queue_info = cs.prefetch_ref(); promise(Ref{true, blk_id, it2->second->blk.root_hash.cbits(), state_root->get_hash().bits(), @@ -758,6 +764,7 @@ void BlockDbImpl::save_new_block(ton::BlockIdExt id, td::BufferSlice data, int a auto save_res = save_db_file(id.file_hash, data, FMode::chk_if_exists | FMode::overwrite | FMode::chk_file_hash); if (save_res.is_error()) { promise(std::move(save_res)); + return; } auto sz = data.size(); auto lev = bb.alloc(id.id, id.root_hash, id.file_hash, data.size(), authority & 0xff); @@ -780,6 +787,7 @@ void BlockDbImpl::save_new_state(ton::BlockIdExt id, td::BufferSlice data, int a auto save_res = save_db_file(id.file_hash, data, FMode::chk_if_exists | FMode::overwrite | FMode::chk_file_hash); if (save_res.is_error()) { promise(std::move(save_res)); + return; } auto sz = data.size(); auto lev = bb.alloc(id.id, id.root_hash, id.file_hash, data.size(), authority & 0xff); diff --git a/crypto/block/block-db.h b/crypto/block/block-db.h index 755403c1..cd9498fb 100644 --- a/crypto/block/block-db.h +++ b/crypto/block/block-db.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "td/utils/int_types.h" diff --git a/crypto/block/block-parse.cpp b/crypto/block/block-parse.cpp index ae1df343..7d51b2e2 100644 --- a/crypto/block/block-parse.cpp +++ b/crypto/block/block-parse.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "td/utils/bits.h" #include "block/block-parse.h" @@ -80,7 +80,7 @@ bool Maybe_Anycast::skip_get_depth(vm::CellSlice& cs, int& depth) const { const Maybe_Anycast t_Maybe_Anycast; -bool MsgAddressInt::validate_skip(vm::CellSlice& cs, bool weak) const { +bool MsgAddressInt::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { if (!cs.have(3)) { return false; } @@ -241,6 +241,14 @@ bool MsgAddressInt::extract_std_address(vm::CellSlice& cs, ton::WorkchainId& wor return false; } +bool MsgAddressInt::extract_std_address(Ref cs_ref, block::StdAddress& addr, bool rewrite) const { + return extract_std_address(std::move(cs_ref), addr.workchain, addr.addr, rewrite); +} + +bool MsgAddressInt::extract_std_address(vm::CellSlice& cs, block::StdAddress& addr, bool rewrite) const { + return extract_std_address(cs, addr.workchain, addr.addr, rewrite); +} + bool MsgAddressInt::store_std_address(vm::CellBuilder& cb, ton::WorkchainId workchain, const ton::StdSmcAddress& addr) const { if (workchain >= -128 && workchain < 128) { @@ -263,16 +271,24 @@ Ref MsgAddressInt::pack_std_address(ton::WorkchainId workchain, c } } +bool MsgAddressInt::store_std_address(vm::CellBuilder& cb, const block::StdAddress& addr) const { + return store_std_address(cb, addr.workchain, addr.addr); +} + +Ref MsgAddressInt::pack_std_address(const block::StdAddress& addr) const { + return pack_std_address(addr.workchain, addr.addr); +} + const MsgAddressInt t_MsgAddressInt; -bool MsgAddress::validate_skip(vm::CellSlice& cs, bool weak) const { +bool MsgAddress::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { switch (get_tag(cs)) { case addr_none: case addr_ext: - return t_MsgAddressExt.validate_skip(cs, weak); + return t_MsgAddressExt.validate_skip(ops, cs, weak); case addr_std: case addr_var: - return t_MsgAddressInt.validate_skip(cs, weak); + return t_MsgAddressInt.validate_skip(ops, cs, weak); } return false; } @@ -284,7 +300,7 @@ bool VarUInteger::skip(vm::CellSlice& cs) const { return len >= 0 && len < n && cs.advance(len * 8); } -bool VarUInteger::validate_skip(vm::CellSlice& cs, bool weak) const { +bool VarUInteger::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { int len = (int)cs.fetch_ulong(ln); return len >= 0 && len < n && (!len || cs.prefetch_ulong(8)) && cs.advance(len * 8); } @@ -325,7 +341,7 @@ bool VarUIntegerPos::skip(vm::CellSlice& cs) const { return len > 0 && len < n && cs.advance(len * 8); } -bool VarUIntegerPos::validate_skip(vm::CellSlice& cs, bool weak) const { +bool VarUIntegerPos::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { int len = (int)cs.fetch_ulong(ln); return len > 0 && len < n && cs.prefetch_ulong(8) && cs.advance(len * 8); } @@ -344,11 +360,11 @@ unsigned long long VarUIntegerPos::as_uint(const vm::CellSlice& cs) const { bool VarUIntegerPos::store_integer_value(vm::CellBuilder& cb, const td::BigInt256& value) const { int k = value.bit_size(false); - return k <= (n - 1) * 8 && value.sgn() > 0 && cb.store_long_bool((k + 7) >> 3, ln) && + return k <= (n - 1) * 8 && value.sgn() >= (int)store_pos_only && cb.store_long_bool((k + 7) >> 3, ln) && cb.store_int256_bool(value, (k + 7) & -8, false); } -const VarUIntegerPos t_VarUIntegerPos_16{16}, t_VarUIntegerPos_32{32}; +const VarUIntegerPos t_VarUIntegerPos_16{16}, t_VarUIntegerPos_32{32}, t_VarUIntegerPosRelaxed_32{32, true}; static inline bool redundant_int(const vm::CellSlice& cs) { int t = (int)cs.prefetch_long(9); @@ -360,7 +376,7 @@ bool VarInteger::skip(vm::CellSlice& cs) const { return len >= 0 && len < n && cs.advance(len * 8); } -bool VarInteger::validate_skip(vm::CellSlice& cs, bool weak) const { +bool VarInteger::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { int len = (int)cs.fetch_ulong(ln); return len >= 0 && len < n && (!len || !redundant_int(cs)) && cs.advance(len * 8); } @@ -386,7 +402,7 @@ bool VarIntegerNz::skip(vm::CellSlice& cs) const { return len > 0 && len < n && cs.advance(len * 8); } -bool VarIntegerNz::validate_skip(vm::CellSlice& cs, bool weak) const { +bool VarIntegerNz::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { int len = (int)cs.fetch_ulong(ln); return len > 0 && len < n && !redundant_int(cs) && cs.advance(len * 8); } @@ -409,8 +425,8 @@ bool VarIntegerNz::store_integer_value(vm::CellBuilder& cb, const td::BigInt256& cb.store_int256_bool(value, (k + 7) & -8, true); } -bool Grams::validate_skip(vm::CellSlice& cs, bool weak) const { - return t_VarUInteger_16.validate_skip(cs, weak); +bool Grams::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return t_VarUInteger_16.validate_skip(ops, cs, weak); } td::RefInt256 Grams::as_integer_skip(vm::CellSlice& cs) const { @@ -464,15 +480,15 @@ bool HashmapNode::skip(vm::CellSlice& cs) const { return n ? cs.advance_refs(2) : value_type.skip(cs); } -bool HashmapNode::validate_skip(vm::CellSlice& cs, bool weak) const { +bool HashmapNode::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { assert(n >= 0); if (!n) { // hmn_leaf - return value_type.validate_skip(cs, weak); + return value_type.validate_skip(ops, cs, weak); } else { // hmn_fork Hashmap branch_type{n - 1, value_type}; - return branch_type.validate_ref(cs.fetch_ref(), weak) && branch_type.validate_ref(cs.fetch_ref(), weak); + return branch_type.validate_ref(ops, cs.fetch_ref(), weak) && branch_type.validate_ref(ops, cs.fetch_ref(), weak); } } @@ -481,9 +497,9 @@ bool Hashmap::skip(vm::CellSlice& cs) const { return HmLabel{n}.skip(cs, l) && HashmapNode{n - l, value_type}.skip(cs); } -bool Hashmap::validate_skip(vm::CellSlice& cs, bool weak) const { +bool Hashmap::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { int l; - return HmLabel{n}.validate_skip(cs, weak, l) && HashmapNode{n - l, value_type}.validate_skip(cs, weak); + return HmLabel{n}.validate_skip(cs, weak, l) && HashmapNode{n - l, value_type}.validate_skip(ops, cs, weak); } int HashmapE::get_size(const vm::CellSlice& cs) const { @@ -491,17 +507,17 @@ int HashmapE::get_size(const vm::CellSlice& cs) const { return (tag >= 0 ? (tag > 0 ? 0x10001 : 1) : -1); } -bool HashmapE::validate(const vm::CellSlice& cs, bool weak) const { +bool HashmapE::validate(int* ops, const vm::CellSlice& cs, bool weak) const { int tag = get_tag(cs); - return tag <= 0 ? !tag : root_type.validate_ref(cs.prefetch_ref(), weak); + return tag <= 0 ? !tag : root_type.validate_ref(ops, cs.prefetch_ref(), weak); } bool HashmapE::add_values(vm::CellBuilder& cb, vm::CellSlice& cs1, vm::CellSlice& cs2) const { int n = root_type.n; vm::Dictionary dict1{vm::DictAdvance(), cs1, n}, dict2{vm::DictAdvance(), cs2, n}; const TLB& vt = root_type.value_type; - vm::Dictionary::simple_combine_func_t combine = [vt](vm::CellBuilder& cb, Ref cs1_ref, - Ref cs2_ref) -> bool { + vm::Dictionary::simple_combine_func_t combine = [&vt](vm::CellBuilder& cb, Ref cs1_ref, + Ref cs2_ref) -> bool { if (!vt.add_values(cb, cs1_ref.write(), cs2_ref.write())) { throw CombineError{}; } @@ -514,8 +530,8 @@ bool HashmapE::add_values_ref(Ref& res, Ref arg1, Ref cs1_ref, - Ref cs2_ref) -> bool { + vm::Dictionary::simple_combine_func_t combine = [&vt](vm::CellBuilder& cb, Ref cs1_ref, + Ref cs2_ref) -> bool { if (!vt.add_values(cb, cs1_ref.write(), cs2_ref.write())) { throw CombineError{}; } @@ -535,8 +551,8 @@ int HashmapE::sub_values(vm::CellBuilder& cb, vm::CellSlice& cs1, vm::CellSlice& int n = root_type.n; vm::Dictionary dict1{vm::DictAdvance(), cs1, n}, dict2{vm::DictAdvance(), cs2, n}; const TLB& vt = root_type.value_type; - vm::Dictionary::simple_combine_func_t combine = [vt](vm::CellBuilder& cb, Ref cs1_ref, - Ref cs2_ref) -> bool { + vm::Dictionary::simple_combine_func_t combine = [&vt](vm::CellBuilder& cb, Ref cs1_ref, + Ref cs2_ref) -> bool { int r = vt.sub_values(cb, cs1_ref.write(), cs2_ref.write()); if (r < 0) { throw CombineError{}; @@ -555,8 +571,8 @@ int HashmapE::sub_values_ref(Ref& res, Ref arg1, Ref cs1_ref, - Ref cs2_ref) -> bool { + vm::Dictionary::simple_combine_func_t combine = [&vt](vm::CellBuilder& cb, Ref cs1_ref, + Ref cs2_ref) -> bool { int r = vt.sub_values(cb, cs1_ref.write(), cs2_ref.write()); if (r < 0) { throw CombineError{}; @@ -583,8 +599,8 @@ bool HashmapE::store_ref(vm::CellBuilder& cb, Ref arg) const { const ExtraCurrencyCollection t_ExtraCurrencyCollection; -bool CurrencyCollection::validate_skip(vm::CellSlice& cs, bool weak) const { - return t_Grams.validate_skip(cs, weak) && t_ExtraCurrencyCollection.validate_skip(cs, weak); +bool CurrencyCollection::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return t_Grams.validate_skip(ops, cs, weak) && t_ExtraCurrencyCollection.validate_skip(ops, cs, weak); } bool CurrencyCollection::skip(vm::CellSlice& cs) const { @@ -641,25 +657,25 @@ bool CurrencyCollection::pack(vm::CellBuilder& cb, const block::CurrencyCollecti const CurrencyCollection t_CurrencyCollection; -bool CommonMsgInfo::validate_skip(vm::CellSlice& cs, bool weak) const { +bool CommonMsgInfo::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { int tag = get_tag(cs); switch (tag) { case int_msg_info: - return cs.advance(4) // int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool - && t_MsgAddressInt.validate_skip(cs, weak) // src - && t_MsgAddressInt.validate_skip(cs, weak) // dest - && t_CurrencyCollection.validate_skip(cs, weak) // value - && t_Grams.validate_skip(cs, weak) // ihr_fee - && t_Grams.validate_skip(cs, weak) // fwd_fee - && cs.advance(64 + 32); // created_lt:uint64 created_at:uint32 + return cs.advance(4) // int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + && t_MsgAddressInt.validate_skip(ops, cs, weak) // src + && t_MsgAddressInt.validate_skip(ops, cs, weak) // dest + && t_CurrencyCollection.validate_skip(ops, cs, weak) // value + && t_Grams.validate_skip(ops, cs, weak) // ihr_fee + && t_Grams.validate_skip(ops, cs, weak) // fwd_fee + && cs.advance(64 + 32); // created_lt:uint64 created_at:uint32 case ext_in_msg_info: - return cs.advance(2) && t_MsgAddressExt.validate_skip(cs, weak) // src - && t_MsgAddressInt.validate_skip(cs, weak) // dest - && t_Grams.validate_skip(cs, weak); // import_fee + return cs.advance(2) && t_MsgAddressExt.validate_skip(ops, cs, weak) // src + && t_MsgAddressInt.validate_skip(ops, cs, weak) // dest + && t_Grams.validate_skip(ops, cs, weak); // import_fee case ext_out_msg_info: - return cs.advance(2) && t_MsgAddressInt.validate_skip(cs, weak) // src - && t_MsgAddressExt.validate_skip(cs, weak) // dest - && cs.advance(64 + 32); // created_lt:uint64 created_at:uint32 + return cs.advance(2) && t_MsgAddressInt.validate_skip(ops, cs, weak) // src + && t_MsgAddressExt.validate_skip(ops, cs, weak) // dest + && cs.advance(64 + 32); // created_lt:uint64 created_at:uint32 } return false; } @@ -721,28 +737,29 @@ const CommonMsgInfo t_CommonMsgInfo; const TickTock t_TickTock; const RefAnything t_RefCell; -bool StateInit::validate_skip(vm::CellSlice& cs, bool weak) const { - return Maybe{5}.validate_skip(cs, weak) // split_depth:(Maybe (## 5)) - && Maybe{}.validate_skip(cs, weak) // special:(Maybe TickTock) - && Maybe{}.validate_skip(cs, weak) // code:(Maybe ^Cell) - && Maybe{}.validate_skip(cs, weak) // data:(Maybe ^Cell) - && Maybe{}.validate_skip(cs, weak); // library:(Maybe ^Cell) +bool StateInit::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return Maybe{5}.validate_skip(ops, cs, weak) // split_depth:(Maybe (## 5)) + && Maybe{}.validate_skip(ops, cs, weak) // special:(Maybe TickTock) + && Maybe{}.validate_skip(ops, cs, weak) // code:(Maybe ^Cell) + && Maybe{}.validate_skip(ops, cs, weak) // data:(Maybe ^Cell) + && Maybe{}.validate_skip(ops, cs, weak); // library:(Maybe ^Cell) } bool StateInit::get_ticktock(vm::CellSlice& cs, int& ticktock) const { bool have_tt; ticktock = 0; - return Maybe{5}.validate_skip(cs) && cs.fetch_bool_to(have_tt) && (!have_tt || cs.fetch_uint_to(2, ticktock)); + return Maybe{5}.validate_skip_upto(1, cs) && cs.fetch_bool_to(have_tt) && + (!have_tt || cs.fetch_uint_to(2, ticktock)); } const StateInit t_StateInit; -bool Message::validate_skip(vm::CellSlice& cs, bool weak) const { +bool Message::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { static const Maybe>> init_type; static const Either body_type; - return t_CommonMsgInfo.validate_skip(cs, weak) // info:CommonMsgInfo - && init_type.validate_skip(cs, weak) // init:(Maybe (Either StateInit ^StateInit)) - && body_type.validate_skip(cs, weak); // body:(Either X ^X) + return t_CommonMsgInfo.validate_skip(ops, cs, weak) // info:CommonMsgInfo + && init_type.validate_skip(ops, cs, weak) // init:(Maybe (Either StateInit ^StateInit)) + && body_type.validate_skip(ops, cs, weak); // body:(Either X ^X) } bool Message::extract_info(vm::CellSlice& cs) const { @@ -760,7 +777,7 @@ bool Message::is_internal(Ref ref) const { const Message t_Message; const RefTo t_Ref_Message; -bool IntermediateAddress::validate_skip(vm::CellSlice& cs, bool weak) const { +bool IntermediateAddress::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { switch (get_tag(cs)) { case interm_addr_regular: return cs.advance(1) && cs.fetch_ulong(7) <= 96U; @@ -795,12 +812,12 @@ int IntermediateAddress::get_size(const vm::CellSlice& cs) const { const IntermediateAddress t_IntermediateAddress; -bool MsgEnvelope::validate_skip(vm::CellSlice& cs, bool weak) const { - return cs.fetch_ulong(4) == 4 // msg_envelope#4 - && t_IntermediateAddress.validate_skip(cs, weak) // cur_addr:IntermediateAddress - && t_IntermediateAddress.validate_skip(cs, weak) // next_addr:IntermediateAddress - && t_Grams.validate_skip(cs, weak) // fwd_fee_remaining:Grams - && t_Ref_Message.validate_skip(cs, weak); // msg:^Message +bool MsgEnvelope::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return cs.fetch_ulong(4) == 4 // msg_envelope#4 + && t_IntermediateAddress.validate_skip(ops, cs, weak) // cur_addr:IntermediateAddress + && t_IntermediateAddress.validate_skip(ops, cs, weak) // next_addr:IntermediateAddress + && t_Grams.validate_skip(ops, cs, weak) // fwd_fee_remaining:Grams + && t_Ref_Message.validate_skip(ops, cs, weak); // msg:^Message } bool MsgEnvelope::skip(vm::CellSlice& cs) const { @@ -849,10 +866,10 @@ bool MsgEnvelope::get_created_lt(const vm::CellSlice& cs, unsigned long long& cr const MsgEnvelope t_MsgEnvelope; const RefTo t_Ref_MsgEnvelope; -bool StorageUsed::validate_skip(vm::CellSlice& cs, bool weak) const { - return t_VarUInteger_7.validate_skip(cs, weak) // cells:(VarUInteger 7) - && t_VarUInteger_7.validate_skip(cs, weak) // bits:(VarUInteger 7) - && t_VarUInteger_7.validate_skip(cs, weak); // public_cells:(VarUInteger 7) +bool StorageUsed::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return t_VarUInteger_7.validate_skip(ops, cs, weak) // cells:(VarUInteger 7) + && t_VarUInteger_7.validate_skip(ops, cs, weak) // bits:(VarUInteger 7) + && t_VarUInteger_7.validate_skip(ops, cs, weak); // public_cells:(VarUInteger 7) } bool StorageUsed::skip(vm::CellSlice& cs) const { @@ -863,9 +880,9 @@ bool StorageUsed::skip(vm::CellSlice& cs) const { const StorageUsed t_StorageUsed; -bool StorageUsedShort::validate_skip(vm::CellSlice& cs, bool weak) const { - return t_VarUInteger_7.validate_skip(cs, weak) // cells:(VarUInteger 7) - && t_VarUInteger_7.validate_skip(cs, weak); // bits:(VarUInteger 7) +bool StorageUsedShort::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return t_VarUInteger_7.validate_skip(ops, cs, weak) // cells:(VarUInteger 7) + && t_VarUInteger_7.validate_skip(ops, cs, weak); // bits:(VarUInteger 7) } bool StorageUsedShort::skip(vm::CellSlice& cs) const { @@ -883,22 +900,22 @@ bool StorageInfo::skip(vm::CellSlice& cs) const { && t_Maybe_Grams.skip(cs); // due_payment:(Maybe Grams) } -bool StorageInfo::validate_skip(vm::CellSlice& cs, bool weak) const { - return t_StorageUsed.validate_skip(cs, weak) // used:StorageUsed - && cs.advance(32) // last_paid:uint32 - && t_Maybe_Grams.validate_skip(cs, weak); // due_payment:(Maybe Grams) +bool StorageInfo::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return t_StorageUsed.validate_skip(ops, cs, weak) // used:StorageUsed + && cs.advance(32) // last_paid:uint32 + && t_Maybe_Grams.validate_skip(ops, cs, weak); // due_payment:(Maybe Grams) } const StorageInfo t_StorageInfo; -bool AccountState::validate_skip(vm::CellSlice& cs, bool weak) const { +bool AccountState::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { switch (get_tag(cs)) { case account_uninit: return cs.advance(2); case account_frozen: return cs.advance(2 + 256); case account_active: - return cs.advance(1) && t_StateInit.validate_skip(cs, weak); + return cs.advance(1) && t_StateInit.validate_skip(ops, cs, weak); } return false; } @@ -921,8 +938,9 @@ bool AccountStorage::skip_copy_balance(vm::CellBuilder& cb, vm::CellSlice& cs) c return cs.advance(64) && t_CurrencyCollection.skip_copy(cb, cs) && t_AccountState.skip(cs); } -bool AccountStorage::validate_skip(vm::CellSlice& cs, bool weak) const { - return cs.advance(64) && t_CurrencyCollection.validate_skip(cs, weak) && t_AccountState.validate_skip(cs, weak); +bool AccountStorage::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return cs.advance(64) && t_CurrencyCollection.validate_skip(ops, cs, weak) && + t_AccountState.validate_skip(ops, cs, weak); } const AccountStorage t_AccountStorage; @@ -940,15 +958,15 @@ bool Account::skip(vm::CellSlice& cs) const { return false; } -bool Account::validate_skip(vm::CellSlice& cs, bool weak) const { +bool Account::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { switch (get_tag(cs)) { case account_none: return allow_empty && cs.advance(1); case account: - return cs.advance(1) // account$1 - && t_MsgAddressInt.validate_skip(cs, weak) // addr:MsgAddressInt - && t_StorageInfo.validate_skip(cs, weak) // storage_stat:StorageInfo - && t_AccountStorage.validate_skip(cs, weak); // storage:AccountStorage + return cs.advance(1) // account$1 + && t_MsgAddressInt.validate_skip(ops, cs, weak) // addr:MsgAddressInt + && t_StorageInfo.validate_skip(ops, cs, weak) // storage_stat:StorageInfo + && t_AccountStorage.validate_skip(ops, cs, weak); // storage:AccountStorage } return false; } @@ -982,7 +1000,7 @@ bool Account::skip_copy_depth_balance(vm::CellBuilder& cb, vm::CellSlice& cs) co } const Account t_Account, t_AccountE{true}; -const RefTo t_Ref_Account; +const RefTo t_Ref_AccountE{true}; bool ShardAccount::extract_account_state(Ref cs_ref, Ref& acc_state) { if (cs_ref.is_null()) { @@ -1032,19 +1050,19 @@ bool HashmapAugNode::skip(vm::CellSlice& cs) const { } } -bool HashmapAugNode::validate_skip(vm::CellSlice& cs, bool weak) const { +bool HashmapAugNode::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { if (n < 0) { return false; } if (!n) { // ahmn_leaf vm::CellSlice cs_extra{cs}; - if (!aug.extra_type.validate_skip(cs, weak)) { + if (!aug.extra_type.validate_skip(ops, cs, weak)) { return false; } cs_extra.cut_tail(cs); vm::CellSlice cs_value{cs}; - if (!aug.value_type.validate_skip(cs, weak)) { + if (!aug.value_type.validate_skip(ops, cs, weak)) { return false; } cs_value.cut_tail(cs); @@ -1055,13 +1073,14 @@ bool HashmapAugNode::validate_skip(vm::CellSlice& cs, bool weak) const { return false; } HashmapAug branch_type{n - 1, aug}; - if (!branch_type.validate_ref(cs.prefetch_ref(0), weak) || !branch_type.validate_ref(cs.prefetch_ref(1), weak)) { + if (!branch_type.validate_ref(ops, cs.prefetch_ref(0), weak) || + !branch_type.validate_ref(ops, cs.prefetch_ref(1), weak)) { return false; } auto cs_left = load_cell_slice(cs.fetch_ref()); auto cs_right = load_cell_slice(cs.fetch_ref()); vm::CellSlice cs_extra{cs}; - if (!aug.extra_type.validate_skip(cs, weak)) { + if (!aug.extra_type.validate_skip(ops, cs, weak)) { return false; } cs_extra.cut_tail(cs); @@ -1074,9 +1093,9 @@ bool HashmapAug::skip(vm::CellSlice& cs) const { return HmLabel{n}.skip(cs, l) && HashmapAugNode{n - l, aug}.skip(cs); } -bool HashmapAug::validate_skip(vm::CellSlice& cs, bool weak) const { +bool HashmapAug::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { int l; - return HmLabel{n}.validate_skip(cs, weak, l) && HashmapAugNode{n - l, aug}.validate_skip(cs, weak); + return HmLabel{n}.validate_skip(cs, weak, l) && HashmapAugNode{n - l, aug}.validate_skip(ops, cs, weak); } bool HashmapAug::extract_extra(vm::CellSlice& cs) const { @@ -1084,20 +1103,20 @@ bool HashmapAug::extract_extra(vm::CellSlice& cs) const { return HmLabel{n}.skip(cs, l) && (l == n || cs.advance_refs(2)) && aug.extra_type.extract(cs); } -bool HashmapAugE::validate_skip(vm::CellSlice& cs, bool weak) const { +bool HashmapAugE::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { Ref extra; switch (get_tag(cs)) { case ahme_empty: - return cs.advance(1) && (extra = root_type.aug.extra_type.validate_fetch(cs, weak)).not_null() && + return cs.advance(1) && (extra = root_type.aug.extra_type.validate_fetch(ops, cs, weak)).not_null() && root_type.aug.check_empty(extra.unique_write()); case ahme_root: - if (cs.advance(1) && root_type.validate_ref(cs.prefetch_ref(), weak)) { + if (cs.advance(1) && root_type.validate_ref(ops, cs.prefetch_ref(), weak)) { bool special; auto cs_root = load_cell_slice_special(cs.fetch_ref(), special); if (special) { return weak; } - return (extra = root_type.aug.extra_type.validate_fetch(cs, weak)).not_null() && + return (extra = root_type.aug.extra_type.validate_fetch(ops, cs, weak)).not_null() && root_type.extract_extra(cs_root) && extra->contents_equal(cs_root); } break; @@ -1121,9 +1140,10 @@ bool DepthBalanceInfo::skip(vm::CellSlice& cs) const { cs); // depth_balance$_ split_depth:(#<= 30) balance:CurrencyCollection = DepthBalanceInfo; } -bool DepthBalanceInfo::validate_skip(vm::CellSlice& cs, bool weak) const { - return cs.fetch_ulong(5) <= 30 && t_CurrencyCollection.validate_skip( - cs, weak); // depth_balance$_ split_depth:(#<= 30) balance:CurrencyCollection +bool DepthBalanceInfo::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return cs.fetch_ulong(5) <= 30 && + t_CurrencyCollection.validate_skip(ops, cs, + weak); // depth_balance$_ split_depth:(#<= 30) balance:CurrencyCollection } bool DepthBalanceInfo::null_value(vm::CellBuilder& cb) const { @@ -1159,10 +1179,10 @@ bool TrStoragePhase::skip(vm::CellSlice& cs) const { && t_AccStatusChange.skip(cs); // status_change:AccStatusChange } -bool TrStoragePhase::validate_skip(vm::CellSlice& cs, bool weak) const { - return t_Grams.validate_skip(cs, weak) // storage_fees_collected:Grams - && t_Maybe_Grams.validate_skip(cs, weak) // storage_fees_due:Grams - && t_AccStatusChange.validate_skip(cs, weak); // status_change:AccStatusChange +bool TrStoragePhase::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return t_Grams.validate_skip(ops, cs, weak) // storage_fees_collected:Grams + && t_Maybe_Grams.validate_skip(ops, cs, weak) // storage_fees_due:Grams + && t_AccStatusChange.validate_skip(ops, cs, weak); // status_change:AccStatusChange } bool TrStoragePhase::get_storage_fees(vm::CellSlice& cs, td::RefInt256& storage_fees) const { @@ -1186,9 +1206,9 @@ bool TrCreditPhase::skip(vm::CellSlice& cs) const { && t_CurrencyCollection.skip(cs); // credit:CurrencyCollection } -bool TrCreditPhase::validate_skip(vm::CellSlice& cs, bool weak) const { - return t_Maybe_Grams.validate_skip(cs, weak) // due_fees_collected:(Maybe Grams) - && t_CurrencyCollection.validate_skip(cs, weak); // credit:CurrencyCollection +bool TrCreditPhase::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return t_Maybe_Grams.validate_skip(ops, cs, weak) // due_fees_collected:(Maybe Grams) + && t_CurrencyCollection.validate_skip(ops, cs, weak); // credit:CurrencyCollection } const TrCreditPhase t_TrCreditPhase; @@ -1204,15 +1224,15 @@ bool TrComputeInternal1::skip(vm::CellSlice& cs) const { // vm_final_state_hash:uint256 } -bool TrComputeInternal1::validate_skip(vm::CellSlice& cs, bool weak) const { - return t_VarUInteger_7.validate_skip(cs, weak) // gas_used:(VarUInteger 7) - && t_VarUInteger_7.validate_skip(cs, weak) // gas_limit:(VarUInteger 7) - && Maybe{3}.validate_skip(cs, weak) // gas_credit:(Maybe (VarUInteger 3)) - && cs.advance(8 + 32) // mode:int8 exit_code:int32 - && Maybe{32}.validate_skip(cs, weak) // exit_arg:(Maybe int32) - && cs.advance(32 + 256 + 256); // vm_steps:uint32 - // vm_init_state_hash:uint256 - // vm_final_state_hash:uint256 +bool TrComputeInternal1::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return t_VarUInteger_7.validate_skip(ops, cs, weak) // gas_used:(VarUInteger 7) + && t_VarUInteger_7.validate_skip(ops, cs, weak) // gas_limit:(VarUInteger 7) + && Maybe{3}.validate_skip(ops, cs, weak) // gas_credit:(Maybe (VarUInteger 3)) + && cs.advance(8 + 32) // mode:int8 exit_code:int32 + && Maybe{32}.validate_skip(ops, cs, weak) // exit_arg:(Maybe int32) + && cs.advance(32 + 256 + 256); // vm_steps:uint32 + // vm_init_state_hash:uint256 + // vm_final_state_hash:uint256 } const TrComputeInternal1 t_TrComputeInternal1; @@ -1231,14 +1251,14 @@ bool TrComputePhase::skip(vm::CellSlice& cs) const { return false; } -bool TrComputePhase::validate_skip(vm::CellSlice& cs, bool weak) const { +bool TrComputePhase::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { switch (get_tag(cs)) { case tr_phase_compute_skipped: - return cs.advance(1) && t_ComputeSkipReason.validate_skip(cs, weak); + return cs.advance(1) && t_ComputeSkipReason.validate_skip(ops, cs, weak); case tr_phase_compute_vm: return cs.advance(1 + 3) // tr_phase_compute_vm$1 success:Bool msg_state_used:Bool account_activated:Bool - && t_Grams.validate_skip(cs, weak) // gas_fees:Grams - && t_Ref_TrComputeInternal1.validate_skip(cs, weak); // ^[ gas_used:(..) .. ] + && t_Grams.validate_skip(ops, cs, weak) // gas_fees:Grams + && t_Ref_TrComputeInternal1.validate_skip(ops, cs, weak); // ^[ gas_used:(..) .. ] } return false; } @@ -1258,17 +1278,17 @@ bool TrActionPhase::skip(vm::CellSlice& cs) const { && t_StorageUsedShort.skip(cs); // tot_msg_size:StorageUsedShort } -bool TrActionPhase::validate_skip(vm::CellSlice& cs, bool weak) const { - return cs.advance(3) // success:Bool valid:Bool no_funds:Bool - && t_AccStatusChange.validate_skip(cs, weak) // status_change:AccStatusChange - && t_Maybe_Grams.validate_skip(cs, weak) // total_fwd_fees:(Maybe Grams) - && t_Maybe_Grams.validate_skip(cs, weak) // total_action_fees:(Maybe Grams) - && cs.advance(32) // result_code:int32 - && Maybe{32}.validate_skip(cs, weak) // result_arg:(Maybe int32) - && cs.advance(16 * 4 + 256) // tot_actions:uint16 spec_actions:uint16 - // skipped_actions:uint16 msgs_created:uint16 - // action_list_hash:uint256 - && t_StorageUsedShort.validate_skip(cs, weak); // tot_msg_size:StorageUsed +bool TrActionPhase::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return cs.advance(3) // success:Bool valid:Bool no_funds:Bool + && t_AccStatusChange.validate_skip(ops, cs, weak) // status_change:AccStatusChange + && t_Maybe_Grams.validate_skip(ops, cs, weak) // total_fwd_fees:(Maybe Grams) + && t_Maybe_Grams.validate_skip(ops, cs, weak) // total_action_fees:(Maybe Grams) + && cs.advance(32) // result_code:int32 + && Maybe{32}.validate_skip(ops, cs, weak) // result_arg:(Maybe int32) + && cs.advance(16 * 4 + 256) // tot_actions:uint16 spec_actions:uint16 + // skipped_actions:uint16 msgs_created:uint16 + // action_list_hash:uint256 + && t_StorageUsedShort.validate_skip(ops, cs, weak); // tot_msg_size:StorageUsed } const TrActionPhase t_TrActionPhase; @@ -1290,19 +1310,19 @@ bool TrBouncePhase::skip(vm::CellSlice& cs) const { return false; } -bool TrBouncePhase::validate_skip(vm::CellSlice& cs, bool weak) const { +bool TrBouncePhase::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { switch (get_tag(cs)) { case tr_phase_bounce_negfunds: return cs.advance(2); // tr_phase_bounce_negfunds$00 case tr_phase_bounce_nofunds: - return cs.advance(2) // tr_phase_bounce_nofunds$01 - && t_StorageUsedShort.validate_skip(cs, weak) // msg_size:StorageUsedShort - && t_Grams.validate_skip(cs, weak); // req_fwd_fees:Grams + return cs.advance(2) // tr_phase_bounce_nofunds$01 + && t_StorageUsedShort.validate_skip(ops, cs, weak) // msg_size:StorageUsedShort + && t_Grams.validate_skip(ops, cs, weak); // req_fwd_fees:Grams case tr_phase_bounce_ok: - return cs.advance(1) // tr_phase_bounce_ok$1 - && t_StorageUsedShort.validate_skip(cs, weak) // msg_size:StorageUsedShort - && t_Grams.validate_skip(cs, weak) // msg_fees:Grams - && t_Grams.validate_skip(cs, weak); // fwd_fees:Grams + return cs.advance(1) // tr_phase_bounce_ok$1 + && t_StorageUsedShort.validate_skip(ops, cs, weak) // msg_size:StorageUsedShort + && t_Grams.validate_skip(ops, cs, weak) // msg_fees:Grams + && t_Grams.validate_skip(ops, cs, weak); // fwd_fees:Grams } return false; } @@ -1322,7 +1342,7 @@ bool SplitMergeInfo::skip(vm::CellSlice& cs) const { return cs.advance(6 + 6 + 256 + 256); } -bool SplitMergeInfo::validate_skip(vm::CellSlice& cs, bool weak) const { +bool SplitMergeInfo::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { if (!cs.have(6 + 6 + 256 + 256)) { return false; } @@ -1392,52 +1412,52 @@ bool TransactionDescr::skip(vm::CellSlice& cs) const { return false; } -bool TransactionDescr::validate_skip(vm::CellSlice& cs, bool weak) const { +bool TransactionDescr::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { switch (get_tag(cs)) { case trans_ord: - return cs.advance(4 + 1) // trans_ord$0000 credit_first:Bool - && Maybe{}.validate_skip(cs, weak) // storage_ph:(Maybe TrStoragePhase) - && Maybe{}.validate_skip(cs, weak) // credit_ph:(Maybe TrCreditPhase) - && t_TrComputePhase.validate_skip(cs, weak) // compute_ph:TrComputePhase - && Maybe>{}.validate_skip(cs, weak) // action:(Maybe ^TrActionPhase) - && cs.advance(1) // aborted:Bool - && Maybe{}.validate_skip(cs, weak) // bounce:(Maybe TrBouncePhase) - && cs.advance(1); // destroyed:Bool + return cs.advance(4 + 1) // trans_ord$0000 credit_first:Bool + && Maybe{}.validate_skip(ops, cs, weak) // storage_ph:(Maybe TrStoragePhase) + && Maybe{}.validate_skip(ops, cs, weak) // credit_ph:(Maybe TrCreditPhase) + && t_TrComputePhase.validate_skip(ops, cs, weak) // compute_ph:TrComputePhase + && Maybe>{}.validate_skip(ops, cs, weak) // action:(Maybe ^TrActionPhase) + && cs.advance(1) // aborted:Bool + && Maybe{}.validate_skip(ops, cs, weak) // bounce:(Maybe TrBouncePhase) + && cs.advance(1); // destroyed:Bool case trans_storage: - return cs.advance(4) // trans_storage$0001 - && t_TrStoragePhase.validate_skip(cs, weak); // storage_ph:TrStoragePhase + return cs.advance(4) // trans_storage$0001 + && t_TrStoragePhase.validate_skip(ops, cs, weak); // storage_ph:TrStoragePhase case trans_tick_tock: - return cs.advance(4) // trans_tick_tock$001 is_tock:Bool - && t_TrStoragePhase.validate_skip(cs, weak) // storage_ph:TrStoragePhase - && t_TrComputePhase.validate_skip(cs, weak) // compute_ph:TrComputePhase - && Maybe>{}.validate_skip(cs, weak) // action:(Maybe ^TrActionPhase) - && cs.advance(2); // aborted:Bool destroyed:Bool + return cs.advance(4) // trans_tick_tock$001 is_tock:Bool + && t_TrStoragePhase.validate_skip(ops, cs, weak) // storage_ph:TrStoragePhase + && t_TrComputePhase.validate_skip(ops, cs, weak) // compute_ph:TrComputePhase + && Maybe>{}.validate_skip(ops, cs, weak) // action:(Maybe ^TrActionPhase) + && cs.advance(2); // aborted:Bool destroyed:Bool case trans_split_prepare: - return cs.advance(4) // trans_split_prepare$0100 - && t_SplitMergeInfo.validate_skip(cs, weak) // split_info:SplitMergeInfo - && Maybe{}.validate_skip(cs, weak) // storage_ph:(Maybe TrStoragePhase) - && t_TrComputePhase.validate_skip(cs, weak) // compute_ph:TrComputePhase - && Maybe>{}.validate_skip(cs, weak) // action:(Maybe ^TrActionPhase) - && cs.advance(2); // aborted:Bool destroyed:Bool + return cs.advance(4) // trans_split_prepare$0100 + && t_SplitMergeInfo.validate_skip(ops, cs, weak) // split_info:SplitMergeInfo + && Maybe{}.validate_skip(ops, cs, weak) // storage_ph:(Maybe TrStoragePhase) + && t_TrComputePhase.validate_skip(ops, cs, weak) // compute_ph:TrComputePhase + && Maybe>{}.validate_skip(ops, cs, weak) // action:(Maybe ^TrActionPhase) + && cs.advance(2); // aborted:Bool destroyed:Bool case trans_split_install: - return cs.advance(4) // trans_split_install$0101 - && t_SplitMergeInfo.validate_skip(cs, weak) // split_info:SplitMergeInfo - && t_Ref_Transaction.validate_skip(cs, weak) // prepare_transaction:^Transaction - && cs.advance(1); // installed:Bool + return cs.advance(4) // trans_split_install$0101 + && t_SplitMergeInfo.validate_skip(ops, cs, weak) // split_info:SplitMergeInfo + && t_Ref_Transaction.validate_skip(ops, cs, weak) // prepare_transaction:^Transaction + && cs.advance(1); // installed:Bool case trans_merge_prepare: - return cs.advance(4) // trans_merge_prepare$0110 - && t_SplitMergeInfo.validate_skip(cs, weak) // split_info:SplitMergeInfo - && t_TrStoragePhase.validate_skip(cs, weak) // storage_ph:TrStoragePhase - && cs.advance(1); // aborted:Bool + return cs.advance(4) // trans_merge_prepare$0110 + && t_SplitMergeInfo.validate_skip(ops, cs, weak) // split_info:SplitMergeInfo + && t_TrStoragePhase.validate_skip(ops, cs, weak) // storage_ph:TrStoragePhase + && cs.advance(1); // aborted:Bool case trans_merge_install: - return cs.advance(4) // trans_merge_install$0111 - && t_SplitMergeInfo.validate_skip(cs, weak) // split_info:SplitMergeInfo - && t_Ref_Transaction.validate_skip(cs, weak) // prepare_transaction:^Transaction - && Maybe{}.validate_skip(cs, weak) // storage_ph:(Maybe TrStoragePhase) - && Maybe{}.validate_skip(cs, weak) // credit_ph:(Maybe TrCreditPhase) - && Maybe{}.validate_skip(cs, weak) // compute_ph:TrComputePhase - && Maybe>{}.validate_skip(cs, weak) // action:(Maybe ^TrActionPhase) - && cs.advance(2); // aborted:Bool destroyed:Bool + return cs.advance(4) // trans_merge_install$0111 + && t_SplitMergeInfo.validate_skip(ops, cs, weak) // split_info:SplitMergeInfo + && t_Ref_Transaction.validate_skip(ops, cs, weak) // prepare_transaction:^Transaction + && Maybe{}.validate_skip(ops, cs, weak) // storage_ph:(Maybe TrStoragePhase) + && Maybe{}.validate_skip(ops, cs, weak) // credit_ph:(Maybe TrCreditPhase) + && Maybe{}.validate_skip(ops, cs, weak) // compute_ph:TrComputePhase + && Maybe>{}.validate_skip(ops, cs, weak) // action:(Maybe ^TrActionPhase) + && cs.advance(2); // aborted:Bool destroyed:Bool } return false; } @@ -1501,9 +1521,9 @@ bool Transaction_aux::skip(vm::CellSlice& cs) const { && HashmapE{15, t_Ref_Message}.skip(cs); // out_msgs:(HashmapE 15 ^Message) } -bool Transaction_aux::validate_skip(vm::CellSlice& cs, bool weak) const { - return Maybe>{}.validate_skip(cs, weak) // in_msg:(Maybe ^Message) - && HashmapE{15, t_Ref_Message}.validate_skip(cs, weak); // out_msgs:(HashmapE 15 ^Message) +bool Transaction_aux::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return Maybe>{}.validate_skip(ops, cs, weak) // in_msg:(Maybe ^Message) + && HashmapE{15, t_Ref_Message}.validate_skip(ops, cs, weak); // out_msgs:(HashmapE 15 ^Message) } const Transaction_aux t_Transaction_aux; @@ -1520,18 +1540,18 @@ bool Transaction::skip(vm::CellSlice& cs) const { && RefTo{}.skip(cs); // description:^TransactionDescr } -bool Transaction::validate_skip(vm::CellSlice& cs, bool weak) const { +bool Transaction::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { return cs.fetch_ulong(4) == 7 // transaction$0111 && cs.advance( 256 + 64 + 256 + 64 + 32 + 15) // account_addr:uint256 lt:uint64 prev_trans_hash:bits256 prev_trans_lt:uint64 now:uint32 outmsg_cnt:uint15 - && t_AccountStatus.validate_skip(cs, weak) // orig_status:AccountStatus - && t_AccountStatus.validate_skip(cs, weak) // end_status:AccountStatus - && RefTo{}.validate_skip(cs, weak) // ^[ in_msg:... out_msgs:... ] - && t_CurrencyCollection.validate_skip(cs, weak) // total_fees:CurrencyCollection - && t_Ref_HashUpdate.validate_skip(cs, weak) // state_update:^(HASH_UPDATE Account) - && RefTo{}.validate_skip(cs, weak); // description:^TransactionDescr + && t_AccountStatus.validate_skip(ops, cs, weak) // orig_status:AccountStatus + && t_AccountStatus.validate_skip(ops, cs, weak) // end_status:AccountStatus + && RefTo{}.validate_skip(ops, cs, weak) // ^[ in_msg:... out_msgs:... ] + && t_CurrencyCollection.validate_skip(ops, cs, weak) // total_fees:CurrencyCollection + && t_Ref_HashUpdate.validate_skip(ops, cs, weak) // state_update:^(HASH_UPDATE Account) + && RefTo{}.validate_skip(ops, cs, weak); // description:^TransactionDescr } bool Transaction::get_storage_fees(Ref cell, td::RefInt256& storage_fees) const { @@ -1595,12 +1615,12 @@ bool AccountBlock::skip(vm::CellSlice& cs) const { && cs.advance_refs(1); // state_update:^(HASH_UPDATE Account) } -bool AccountBlock::validate_skip(vm::CellSlice& cs, bool weak) const { +bool AccountBlock::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { return cs.fetch_ulong(4) == 5 // acc_trans#5 && cs.advance(256) // account_addr:bits256 - && - t_AccountTransactions.validate_skip(cs, weak) // transactions:(HashmapAug 64 ^Transaction CurrencyCollection) - && t_Ref_HashUpdate.validate_skip(cs, weak); // state_update:^(HASH_UPDATE Account) + && t_AccountTransactions.validate_skip(ops, cs, + weak) // transactions:(HashmapAug 64 ^Transaction CurrencyCollection) + && t_Ref_HashUpdate.validate_skip(ops, cs, weak); // state_update:^(HASH_UPDATE Account) } bool AccountBlock::get_total_fees(vm::CellSlice&& cs, block::CurrencyCollection& total_fees) const { @@ -1620,8 +1640,8 @@ const Aug_ShardAccountBlocks aug_ShardAccountBlocks; const HashmapAugE t_ShardAccountBlocks{256, aug_ShardAccountBlocks}; // (HashmapAugE 256 AccountBlock CurrencyCollection) -bool ImportFees::validate_skip(vm::CellSlice& cs, bool weak) const { - return t_Grams.validate_skip(cs, weak) && t_CurrencyCollection.validate_skip(cs, weak); +bool ImportFees::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return t_Grams.validate_skip(ops, cs, weak) && t_CurrencyCollection.validate_skip(ops, cs, weak); } bool ImportFees::skip(vm::CellSlice& cs) const { @@ -1676,44 +1696,44 @@ bool InMsg::skip(vm::CellSlice& cs) const { return false; } -bool InMsg::validate_skip(vm::CellSlice& cs, bool weak) const { +bool InMsg::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { switch (get_tag(cs)) { case msg_import_ext: - return cs.advance(3) // msg_import_ext$000 - && t_Ref_Message.validate_skip(cs, weak) // msg:^Message - && t_Ref_Transaction.validate_skip(cs, weak); // transaction:^Transaction + return cs.advance(3) // msg_import_ext$000 + && t_Ref_Message.validate_skip(ops, cs, weak) // msg:^Message + && t_Ref_Transaction.validate_skip(ops, cs, weak); // transaction:^Transaction case msg_import_ihr: - return cs.advance(3) // msg_import_ihr$010 - && t_Ref_Message.validate_skip(cs, weak) // msg:^Message - && t_Ref_Transaction.validate_skip(cs, weak) // transaction:^Transaction - && t_Grams.validate_skip(cs, weak) // ihr_fee:Grams - && t_RefCell.validate_skip(cs, weak); // proof_created:^Cell + return cs.advance(3) // msg_import_ihr$010 + && t_Ref_Message.validate_skip(ops, cs, weak) // msg:^Message + && t_Ref_Transaction.validate_skip(ops, cs, weak) // transaction:^Transaction + && t_Grams.validate_skip(ops, cs, weak) // ihr_fee:Grams + && t_RefCell.validate_skip(ops, cs, weak); // proof_created:^Cell case msg_import_imm: - return cs.advance(3) // msg_import_imm$011 - && t_Ref_MsgEnvelope.validate_skip(cs, weak) // in_msg:^MsgEnvelope - && t_Ref_Transaction.validate_skip(cs, weak) // transaction:^Transaction - && t_Grams.validate_skip(cs, weak); // fwd_fee:Grams + return cs.advance(3) // msg_import_imm$011 + && t_Ref_MsgEnvelope.validate_skip(ops, cs, weak) // in_msg:^MsgEnvelope + && t_Ref_Transaction.validate_skip(ops, cs, weak) // transaction:^Transaction + && t_Grams.validate_skip(ops, cs, weak); // fwd_fee:Grams case msg_import_fin: - return cs.advance(3) // msg_import_fin$100 - && t_Ref_MsgEnvelope.validate_skip(cs, weak) // in_msg:^MsgEnvelope - && t_Ref_Transaction.validate_skip(cs, weak) // transaction:^Transaction - && t_Grams.validate_skip(cs, weak); // fwd_fee:Grams + return cs.advance(3) // msg_import_fin$100 + && t_Ref_MsgEnvelope.validate_skip(ops, cs, weak) // in_msg:^MsgEnvelope + && t_Ref_Transaction.validate_skip(ops, cs, weak) // transaction:^Transaction + && t_Grams.validate_skip(ops, cs, weak); // fwd_fee:Grams case msg_import_tr: - return cs.advance(3) // msg_import_tr$101 - && t_Ref_MsgEnvelope.validate_skip(cs, weak) // in_msg:^MsgEnvelope - && t_Ref_MsgEnvelope.validate_skip(cs, weak) // out_msg:^MsgEnvelope - && t_Grams.validate_skip(cs, weak); // transit_fee:Grams + return cs.advance(3) // msg_import_tr$101 + && t_Ref_MsgEnvelope.validate_skip(ops, cs, weak) // in_msg:^MsgEnvelope + && t_Ref_MsgEnvelope.validate_skip(ops, cs, weak) // out_msg:^MsgEnvelope + && t_Grams.validate_skip(ops, cs, weak); // transit_fee:Grams case msg_discard_fin: - return cs.advance(3) // msg_discard_fin$110 - && t_Ref_MsgEnvelope.validate_skip(cs, weak) // in_msg:^MsgEnvelope - && cs.advance(64) // transaction_id:uint64 - && t_Grams.validate_skip(cs, weak); // fwd_fee:Grams + return cs.advance(3) // msg_discard_fin$110 + && t_Ref_MsgEnvelope.validate_skip(ops, cs, weak) // in_msg:^MsgEnvelope + && cs.advance(64) // transaction_id:uint64 + && t_Grams.validate_skip(ops, cs, weak); // fwd_fee:Grams case msg_discard_tr: - return cs.advance(3) // msg_discard_tr$111 - && t_Ref_MsgEnvelope.validate_skip(cs, weak) // in_msg:^MsgEnvelope - && cs.advance(64) // transaction_id:uint64 - && t_Grams.validate_skip(cs, weak) // fwd_fee:Grams - && t_RefCell.validate_skip(cs, weak); // proof_delivered:^Cell + return cs.advance(3) // msg_discard_tr$111 + && t_Ref_MsgEnvelope.validate_skip(ops, cs, weak) // in_msg:^MsgEnvelope + && cs.advance(64) // transaction_id:uint64 + && t_Grams.validate_skip(ops, cs, weak) // fwd_fee:Grams + && t_RefCell.validate_skip(ops, cs, weak); // proof_delivered:^Cell } return false; } @@ -1840,9 +1860,13 @@ bool OutMsg::skip(vm::CellSlice& cs) const { && t_Ref_MsgEnvelope.skip(cs) // out_msg:^MsgEnvelope && RefTo{}.skip(cs); // reimport:^InMsg case msg_export_deq: - return cs.advance(3) // msg_export_deq$110 + return cs.advance(4) // msg_export_deq$1100 && t_Ref_MsgEnvelope.skip(cs) // out_msg:^MsgEnvelope - && cs.advance(64); // import_block_lt:uint64 + && cs.advance(63); // import_block_lt:uint63 + case msg_export_deq_short: + return cs.advance( + 4 + 256 + 32 + 64 + + 64); // msg_export_deq_short$1101 msg_env_hash:bits256 next_workchain:int32 next_addr_pfx:uint64 import_block_lt:uint64 case msg_export_tr_req: return cs.advance(3) // msg_export_tr_req$111 && t_Ref_MsgEnvelope.skip(cs) // out_msg:^MsgEnvelope @@ -1851,37 +1875,41 @@ bool OutMsg::skip(vm::CellSlice& cs) const { return false; } -bool OutMsg::validate_skip(vm::CellSlice& cs, bool weak) const { +bool OutMsg::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { switch (get_tag(cs)) { case msg_export_ext: - return cs.advance(3) // msg_export_ext$000 - && t_Ref_Message.validate_skip(cs, weak) // msg:^Message - && t_Ref_Transaction.validate_skip(cs, weak); // transaction:^Transaction + return cs.advance(3) // msg_export_ext$000 + && t_Ref_Message.validate_skip(ops, cs, weak) // msg:^Message + && t_Ref_Transaction.validate_skip(ops, cs, weak); // transaction:^Transaction case msg_export_imm: - return cs.advance(3) // msg_export_imm$010 - && t_Ref_MsgEnvelope.validate_skip(cs, weak) // out_msg:^MsgEnvelope - && t_Ref_Transaction.validate_skip(cs, weak) // transaction:^Transaction - && RefTo{}.validate_skip(cs, weak); // reimport:^InMsg + return cs.advance(3) // msg_export_imm$010 + && t_Ref_MsgEnvelope.validate_skip(ops, cs, weak) // out_msg:^MsgEnvelope + && t_Ref_Transaction.validate_skip(ops, cs, weak) // transaction:^Transaction + && RefTo{}.validate_skip(ops, cs, weak); // reimport:^InMsg case msg_export_new: - return cs.advance(3) // msg_export_new$001 - && t_Ref_MsgEnvelope.validate_skip(cs, weak) // out_msg:^MsgEnvelope - && t_Ref_Transaction.validate_skip(cs, weak); // transaction:^Transaction + return cs.advance(3) // msg_export_new$001 + && t_Ref_MsgEnvelope.validate_skip(ops, cs, weak) // out_msg:^MsgEnvelope + && t_Ref_Transaction.validate_skip(ops, cs, weak); // transaction:^Transaction case msg_export_tr: - return cs.advance(3) // msg_export_tr$011 - && t_Ref_MsgEnvelope.validate_skip(cs, weak) // out_msg:^MsgEnvelope - && RefTo{}.validate_skip(cs, weak); // imported:^InMsg + return cs.advance(3) // msg_export_tr$011 + && t_Ref_MsgEnvelope.validate_skip(ops, cs, weak) // out_msg:^MsgEnvelope + && RefTo{}.validate_skip(ops, cs, weak); // imported:^InMsg case msg_export_deq_imm: - return cs.advance(3) // msg_export_deq_imm$100 - && t_Ref_MsgEnvelope.validate_skip(cs, weak) // out_msg:^MsgEnvelope - && RefTo{}.validate_skip(cs, weak); // reimport:^InMsg + return cs.advance(3) // msg_export_deq_imm$100 + && t_Ref_MsgEnvelope.validate_skip(ops, cs, weak) // out_msg:^MsgEnvelope + && RefTo{}.validate_skip(ops, cs, weak); // reimport:^InMsg case msg_export_deq: - return cs.advance(3) // msg_export_deq$110 - && t_Ref_MsgEnvelope.validate_skip(cs, weak) // out_msg:^MsgEnvelope - && cs.advance(64); // import_block_lt:uint64 + return cs.advance(4) // msg_export_deq$1100 + && t_Ref_MsgEnvelope.validate_skip(ops, cs, weak) // out_msg:^MsgEnvelope + && cs.advance(63); // import_block_lt:uint63 + case msg_export_deq_short: + return cs.advance( + 4 + 256 + 32 + 64 + + 64); // msg_export_deq_short$1101 msg_env_hash:bits256 next_workchain:int32 next_addr_pfx:uint64 import_block_lt:uint64 case msg_export_tr_req: - return cs.advance(3) // msg_export_tr_req$111 - && t_Ref_MsgEnvelope.validate_skip(cs, weak) // out_msg:^MsgEnvelope - && RefTo{}.validate_skip(cs, weak); // imported:^InMsg + return cs.advance(3) // msg_export_tr_req$111 + && t_Ref_MsgEnvelope.validate_skip(ops, cs, weak) // out_msg:^MsgEnvelope + && RefTo{}.validate_skip(ops, cs, weak); // imported:^InMsg } return false; } @@ -1898,7 +1926,9 @@ bool OutMsg::get_export_value(vm::CellBuilder& cb, vm::CellSlice& cs) const { case msg_export_deq_imm: // dequeuing record for outbound message delivered in this very block, no value exported return cs.have(3, 2) && t_CurrencyCollection.null_value(cb); case msg_export_deq: // dequeueing record for outbound message, no exported value - return cs.have(3, 1) && t_CurrencyCollection.null_value(cb); + return cs.have(4 + 63, 1) && t_CurrencyCollection.null_value(cb); + case msg_export_deq_short: // dequeueing record for outbound message, no exported value + return cs.have(4 + 256 + 32 + 64 + 64) && t_CurrencyCollection.null_value(cb); case msg_export_new: // newly-generated outbound internal message, queued case msg_export_tr: // transit internal message, queued case msg_export_tr_req: // transit internal message, re-queued from this shardchain @@ -1937,6 +1967,7 @@ bool OutMsg::get_created_lt(vm::CellSlice& cs, unsigned long long& created_lt) c case msg_export_new: case msg_export_tr: case msg_export_deq: + case msg_export_deq_short: case msg_export_deq_imm: case msg_export_tr_req: if (cs.have(3, 1)) { @@ -1954,8 +1985,8 @@ const OutMsg t_OutMsg; const Aug_OutMsgDescr aug_OutMsgDescr; const OutMsgDescr t_OutMsgDescr; -bool EnqueuedMsg::validate_skip(vm::CellSlice& cs, bool weak) const { - return cs.advance(64) && t_Ref_MsgEnvelope.validate_skip(cs, weak); +bool EnqueuedMsg::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return cs.advance(64) && t_Ref_MsgEnvelope.validate_skip(ops, cs, weak); } const EnqueuedMsg t_EnqueuedMsg; @@ -1989,9 +2020,9 @@ bool OutMsgQueueInfo::skip(vm::CellSlice& cs) const { return t_OutMsgQueue.skip(cs) && t_ProcessedInfo.skip(cs) && t_IhrPendingInfo.skip(cs); } -bool OutMsgQueueInfo::validate_skip(vm::CellSlice& cs, bool weak) const { - return t_OutMsgQueue.validate_skip(cs, weak) && t_ProcessedInfo.validate_skip(cs, weak) && - t_IhrPendingInfo.validate_skip(cs, weak); +bool OutMsgQueueInfo::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return t_OutMsgQueue.validate_skip(ops, cs, weak) && t_ProcessedInfo.validate_skip(ops, cs, weak) && + t_IhrPendingInfo.validate_skip(ops, cs, weak); } const OutMsgQueueInfo t_OutMsgQueueInfo; @@ -2047,7 +2078,7 @@ bool ExtBlkRef::pack_to(Ref& cell, const ton::BlockIdExt& blkid, ton:: const ExtBlkRef t_ExtBlkRef; const BlkMasterInfo t_BlkMasterInfo; -bool ShardIdent::validate_skip(vm::CellSlice& cs, bool weak) const { +bool ShardIdent::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { int shard_pfx_len, workchain_id; unsigned long long shard_pfx; if (cs.fetch_ulong(2) == 0 && cs.fetch_uint_to(6, shard_pfx_len) && cs.fetch_int_to(32, workchain_id) && @@ -2112,8 +2143,8 @@ bool ShardIdent::pack(vm::CellBuilder& cb, ton::ShardIdFull data) const { const ShardIdent t_ShardIdent; -bool BlockIdExt::validate_skip(vm::CellSlice& cs, bool weak) const { - return t_ShardIdent.validate_skip(cs, weak) && cs.advance(32 + 256 * 2); +bool BlockIdExt::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return t_ShardIdent.validate_skip(ops, cs, weak) && cs.advance(32 + 256 * 2); } bool BlockIdExt::unpack(vm::CellSlice& cs, ton::BlockIdExt& data) const { @@ -2146,21 +2177,21 @@ bool ShardState::skip(vm::CellSlice& cs) const { && Maybe>{}.skip(cs); // custom:(Maybe ^McStateExtra) } -bool ShardState::validate_skip(vm::CellSlice& cs, bool weak) const { +bool ShardState::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { int seq_no; return get_tag(cs) == shard_state && cs.advance(64) // shard_state#9023afe2 blockchain_id:int32 - && t_ShardIdent.validate_skip(cs, weak) // shard_id:ShardIdent + && t_ShardIdent.validate_skip(ops, cs, weak) // shard_id:ShardIdent && cs.fetch_int_to(32, seq_no) // seq_no:int32 && seq_no >= -1 // { seq_no >= -1 } && cs.advance(32 + 32 + 64 + 32) // vert_seq_no:# gen_utime:uint32 gen_lt:uint64 min_ref_mc_seqno:uint32 - && t_Ref_OutMsgQueueInfo.validate_skip(cs, weak) // out_msg_queue_info:^OutMsgQueueInfo - && cs.advance(1) // before_split:Bool - && t_ShardAccounts.validate_skip_ref(cs, weak) // accounts:^ShardAccounts + && t_Ref_OutMsgQueueInfo.validate_skip(ops, cs, weak) // out_msg_queue_info:^OutMsgQueueInfo + && cs.advance(1) // before_split:Bool + && t_ShardAccounts.validate_skip_ref(ops, cs, weak) // accounts:^ShardAccounts && t_ShardState_aux.validate_skip_ref( - cs, + ops, cs, weak) // ^[ total_balance:CurrencyCollection total_validator_fees:CurrencyCollection libraries:(HashmapE 256 LibDescr) master_ref:(Maybe BlkMasterInfo) ] - && Maybe>{}.validate_skip(cs, weak); // custom:(Maybe ^McStateExtra) + && Maybe>{}.validate_skip(ops, cs, weak); // custom:(Maybe ^McStateExtra) } const ShardState t_ShardState; @@ -2173,12 +2204,12 @@ bool ShardState_aux::skip(vm::CellSlice& cs) const { && Maybe{}.skip(cs); // master_ref:(Maybe BlkMasterInfo) } -bool ShardState_aux::validate_skip(vm::CellSlice& cs, bool weak) const { - return cs.advance(128) // overload_history:uint64 underload_history:uint64 - && t_CurrencyCollection.validate_skip(cs, weak) // total_balance:CurrencyCollection - && t_CurrencyCollection.validate_skip(cs, weak) // total_validator_fees:CurrencyCollection - && HashmapE{256, t_LibDescr}.validate_skip(cs, weak) // libraries:(HashmapE 256 LibDescr) - && Maybe{}.validate_skip(cs, weak); // master_ref:(Maybe BlkMasterInfo) +bool ShardState_aux::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return cs.advance(128) // overload_history:uint64 underload_history:uint64 + && t_CurrencyCollection.validate_skip(ops, cs, weak) // total_balance:CurrencyCollection + && t_CurrencyCollection.validate_skip(ops, cs, weak) // total_validator_fees:CurrencyCollection + && HashmapE{256, t_LibDescr}.validate_skip(ops, cs, weak) // libraries:(HashmapE 256 LibDescr) + && Maybe{}.validate_skip(ops, cs, weak); // master_ref:(Maybe BlkMasterInfo) } const ShardState_aux t_ShardState_aux; @@ -2189,10 +2220,10 @@ bool LibDescr::skip(vm::CellSlice& cs) const { && Hashmap{256, t_True}.skip(cs); // publishers:(Hashmap 256 False) } -bool LibDescr::validate_skip(vm::CellSlice& cs, bool weak) const { - return get_tag(cs) == shared_lib_descr && cs.advance(2) // shared_lib_descr$00 - && cs.fetch_ref().not_null() // lib:^Cell - && Hashmap{256, t_True}.validate_skip(cs, weak); // publishers:(Hashmap 256 False) +bool LibDescr::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return get_tag(cs) == shared_lib_descr && cs.advance(2) // shared_lib_descr$00 + && cs.fetch_ref().not_null() // lib:^Cell + && Hashmap{256, t_True}.validate_skip(ops, cs, weak); // publishers:(Hashmap 256 False) } const LibDescr t_LibDescr; @@ -2202,9 +2233,9 @@ bool BlkPrevInfo::skip(vm::CellSlice& cs) const { && (!merged || t_ExtBlkRef.skip(cs)); // prev_alt:merged?ExtBlkRef } -bool BlkPrevInfo::validate_skip(vm::CellSlice& cs, bool weak) const { - return t_ExtBlkRef.validate_skip(cs, weak) // prev_blk_info$_ {merged:#} prev:ExtBlkRef - && (!merged || t_ExtBlkRef.validate_skip(cs, weak)); // prev_alt:merged?ExtBlkRef +bool BlkPrevInfo::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return t_ExtBlkRef.validate_skip(ops, cs, weak) // prev_blk_info$_ {merged:#} prev:ExtBlkRef + && (!merged || t_ExtBlkRef.validate_skip(ops, cs, weak)); // prev_alt:merged?ExtBlkRef } const BlkPrevInfo t_BlkPrevInfo_0{0}; @@ -2213,8 +2244,8 @@ bool McStateExtra::skip(vm::CellSlice& cs) const { return block::gen::t_McStateExtra.skip(cs); } -bool McStateExtra::validate_skip(vm::CellSlice& cs, bool weak) const { - return block::gen::t_McStateExtra.validate_skip(cs, weak); // ?? +bool McStateExtra::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return block::gen::t_McStateExtra.validate_skip(ops, cs, weak); // ?? } const McStateExtra t_McStateExtra; @@ -2241,8 +2272,8 @@ bool ShardFeeCreated::skip(vm::CellSlice& cs) const { return t_CurrencyCollection.skip(cs) && t_CurrencyCollection.skip(cs); } -bool ShardFeeCreated::validate_skip(vm::CellSlice& cs, bool weak) const { - return t_CurrencyCollection.validate_skip(cs, weak) && t_CurrencyCollection.validate_skip(cs, weak); +bool ShardFeeCreated::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { + return t_CurrencyCollection.validate_skip(ops, cs, weak) && t_CurrencyCollection.validate_skip(ops, cs, weak); } bool ShardFeeCreated::null_value(vm::CellBuilder& cb) const { @@ -2261,5 +2292,37 @@ bool Aug_ShardFees::eval_leaf(vm::CellBuilder& cb, vm::CellSlice& cs) const { const Aug_ShardFees aug_ShardFees; +bool validate_message_libs(const td::Ref &cell) { + gen::Message::Record rec; + if (!type_unpack_cell(cell, gen::t_Message_Any, rec)) { + return false; + } + vm::CellSlice& state_init = rec.init.write(); + if (!state_init.fetch_long(1)) { + return true; + } + if (state_init.fetch_long(1)) { + return gen::t_StateInitWithLibs.validate_ref(state_init.prefetch_ref()); + } else { + return gen::t_StateInitWithLibs.validate_csr(rec.init); + } +} + +bool validate_message_relaxed_libs(const td::Ref &cell) { + gen::MessageRelaxed::Record rec; + if (!type_unpack_cell(cell, gen::t_MessageRelaxed_Any, rec)) { + return false; + } + vm::CellSlice& state_init = rec.init.write(); + if (!state_init.fetch_long(1)) { + return true; + } + if (state_init.fetch_long(1)) { + return gen::t_StateInitWithLibs.validate_ref(state_init.prefetch_ref()); + } else { + return gen::t_StateInitWithLibs.validate_csr(rec.init); + } +} + } // namespace tlb } // namespace block diff --git a/crypto/block/block-parse.h b/crypto/block/block-parse.h index 8ea3325e..c0b11745 100644 --- a/crypto/block/block-parse.h +++ b/crypto/block/block-parse.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/refcnt.hpp" @@ -59,7 +59,7 @@ struct VarUInteger final : TLB_Complex { ln = 32 - td::count_leading_zeroes32(n - 1); } bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; td::RefInt256 as_integer_skip(vm::CellSlice& cs) const override; unsigned long long as_uint(const vm::CellSlice& cs) const override; bool null_value(vm::CellBuilder& cb) const override { @@ -68,23 +68,30 @@ struct VarUInteger final : TLB_Complex { bool store_integer_value(vm::CellBuilder& cb, const td::BigInt256& value) const override; unsigned precompute_integer_size(const td::BigInt256& value) const; unsigned precompute_integer_size(td::RefInt256 value) const; + std::ostream& print_type(std::ostream& os) const override { + return os << "(VarUInteger " << n << ")"; + } }; extern const VarUInteger t_VarUInteger_3, t_VarUInteger_7, t_VarUInteger_16, t_VarUInteger_32; struct VarUIntegerPos final : TLB_Complex { int n, ln; - VarUIntegerPos(int _n) : n(_n) { + bool store_pos_only; + VarUIntegerPos(int _n, bool relaxed = false) : n(_n), store_pos_only(!relaxed) { ln = 32 - td::count_leading_zeroes32(n - 1); } bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; td::RefInt256 as_integer_skip(vm::CellSlice& cs) const override; unsigned long long as_uint(const vm::CellSlice& cs) const override; bool store_integer_value(vm::CellBuilder& cb, const td::BigInt256& value) const override; + std::ostream& print_type(std::ostream& os) const override { + return os << "(VarUIntegerPos " << n << ")"; + } }; -extern const VarUIntegerPos t_VarUIntegerPos_16, t_VarUIntegerPos_32; +extern const VarUIntegerPos t_VarUIntegerPos_16, t_VarUIntegerPos_32, t_VarUIntegerPosRelaxed_32; struct VarInteger final : TLB_Complex { int n, ln; @@ -92,13 +99,16 @@ struct VarInteger final : TLB_Complex { ln = 32 - td::count_leading_zeroes32(n - 1); } bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; td::RefInt256 as_integer_skip(vm::CellSlice& cs) const override; long long as_int(const vm::CellSlice& cs) const override; bool null_value(vm::CellBuilder& cb) const override { return cb.store_zeroes_bool(ln); } bool store_integer_value(vm::CellBuilder& cb, const td::BigInt256& value) const override; + std::ostream& print_type(std::ostream& os) const override { + return os << "(VarInteger " << n << ")"; + } }; struct VarIntegerNz final : TLB_Complex { @@ -107,10 +117,13 @@ struct VarIntegerNz final : TLB_Complex { ln = 32 - td::count_leading_zeroes32(n - 1); } bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; td::RefInt256 as_integer_skip(vm::CellSlice& cs) const override; long long as_int(const vm::CellSlice& cs) const override; bool store_integer_value(vm::CellBuilder& cb, const td::BigInt256& value) const override; + std::ostream& print_type(std::ostream& os) const override { + return os << "(VarIntegerNz " << n << ")"; + } }; struct Unary final : TLB { @@ -123,13 +136,13 @@ struct Unary final : TLB { bool skip(vm::CellSlice& cs, int& n) const { return validate_skip(cs, false, n); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { - return cs.advance(get_size(cs)); + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return skip(cs); } bool skip(vm::CellSlice& cs) const override { - return validate_skip(cs); + return cs.advance(get_size(cs)); } - bool validate(const vm::CellSlice& cs, bool weak = false) const override { + bool validate(int* ops, const vm::CellSlice& cs, bool weak = false) const override { return cs.have(get_size(cs)); } }; @@ -149,7 +162,7 @@ struct HmLabel final : TLB_Complex { int n; return skip(cs, n); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { int n; return validate_skip(cs, weak, n); } @@ -162,7 +175,7 @@ struct Hashmap final : TLB_Complex { Hashmap(int _n, const TLB& _val_type) : value_type(_val_type), n(_n) { } bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; }; struct HashmapNode final : TLB_Complex { @@ -173,7 +186,7 @@ struct HashmapNode final : TLB_Complex { } int get_size(const vm::CellSlice& cs) const override; bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { return n > 0 ? hmn_fork : n; } @@ -185,7 +198,7 @@ struct HashmapE final : TLB { HashmapE(int _n, const TLB& _val_type) : root_type(_n, _val_type) { } int get_size(const vm::CellSlice& cs) const override; - bool validate(const vm::CellSlice& cs, bool weak = false) const override; + bool validate(int* ops, const vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { return (int)cs.prefetch_ulong(1); } @@ -221,7 +234,7 @@ struct HashmapAug final : TLB_Complex { HashmapAug(int _n, const AugmentationCheckData& _aug) : aug(_aug), n(_n) { } bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; bool extract_extra(vm::CellSlice& cs) const; }; @@ -232,7 +245,7 @@ struct HashmapAugNode final : TLB_Complex { HashmapAugNode(int _n, const AugmentationCheckData& _aug) : aug(_aug), n(_n) { } bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { return n > 0 ? ahmn_fork : n; } @@ -244,7 +257,7 @@ struct HashmapAugE final : TLB_Complex { HashmapAugE(int _n, const AugmentationCheckData& _aug) : root_type(_n, std::move(_aug)) { } bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; bool extract_extra(vm::CellSlice& cs) const; int get_tag(const vm::CellSlice& cs) const override { return (int)cs.prefetch_ulong(1); @@ -252,7 +265,7 @@ struct HashmapAugE final : TLB_Complex { }; struct Grams final : TLB_Complex { - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; td::RefInt256 as_integer_skip(vm::CellSlice& cs) const override; bool null_value(vm::CellBuilder& cb) const override; bool store_integer_value(vm::CellBuilder& cb, const td::BigInt256& value) const override; @@ -264,7 +277,7 @@ extern const Grams t_Grams; struct MsgAddressInt final : TLB_Complex { enum { addr_std = 2, addr_var = 3 }; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { return (int)cs.prefetch_ulong(2); } @@ -285,8 +298,13 @@ struct MsgAddressInt final : TLB_Complex { bool rewrite = true) const; bool extract_std_address(vm::CellSlice& cs, ton::WorkchainId& workchain, ton::StdSmcAddress& addr, bool rewrite = true) const; + bool extract_std_address(Ref cs_ref, block::StdAddress& addr, bool rewrite = true) const; + bool extract_std_address(vm::CellSlice& cs, block::StdAddress& addr, bool rewrite = true) const; bool store_std_address(vm::CellBuilder& cb, ton::WorkchainId workchain, const ton::StdSmcAddress& addr) const; Ref pack_std_address(ton::WorkchainId workchain, const ton::StdSmcAddress& addr) const; + + bool store_std_address(vm::CellBuilder& cb, const block::StdAddress& addr) const; + Ref pack_std_address(const block::StdAddress& addr) const; }; extern const MsgAddressInt t_MsgAddressInt; @@ -303,7 +321,7 @@ extern const MsgAddressExt t_MsgAddressExt; struct MsgAddress final : TLB_Complex { enum { addr_none = 0, addr_ext = 1, addr_std = 2, addr_var = 3 }; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { return (int)cs.prefetch_ulong(2); } @@ -312,14 +330,14 @@ struct MsgAddress final : TLB_Complex { extern const MsgAddress t_MsgAddress; struct ExtraCurrencyCollection final : TLB { - HashmapE dict_type; - ExtraCurrencyCollection() : dict_type(32, t_VarUIntegerPos_32) { + HashmapE dict_type, dict_type2; + ExtraCurrencyCollection() : dict_type(32, t_VarUIntegerPos_32), dict_type2(32, t_VarUIntegerPosRelaxed_32) { } int get_size(const vm::CellSlice& cs) const override { return dict_type.get_size(cs); } - bool validate(const vm::CellSlice& cs, bool weak) const override { - return dict_type.validate(cs, weak); + bool validate(int* ops, const vm::CellSlice& cs, bool weak) const override { + return dict_type.validate(ops, cs, weak); } bool null_value(vm::CellBuilder& cb) const override { return cb.store_zeroes_bool(1); @@ -328,13 +346,13 @@ struct ExtraCurrencyCollection final : TLB { return dict_type.add_values(cb, cs1, cs2); } int sub_values(vm::CellBuilder& cb, vm::CellSlice& cs1, vm::CellSlice& cs2) const override { - return dict_type.sub_values(cb, cs1, cs2); + return dict_type2.sub_values(cb, cs1, cs2); } bool add_values_ref(Ref& res, Ref arg1, Ref arg2) const { return dict_type.add_values_ref(res, std::move(arg1), std::move(arg2)); } int sub_values_ref(Ref& res, Ref arg1, Ref arg2) const { - return dict_type.sub_values_ref(res, std::move(arg1), std::move(arg2)); + return dict_type2.sub_values_ref(res, std::move(arg1), std::move(arg2)); } bool store_ref(vm::CellBuilder& cb, Ref arg) const { return dict_type.store_ref(cb, std::move(arg)); @@ -348,7 +366,7 @@ extern const ExtraCurrencyCollection t_ExtraCurrencyCollection; struct CurrencyCollection final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; td::RefInt256 as_integer_skip(vm::CellSlice& cs) const override; bool null_value(vm::CellBuilder& cb) const override { return cb.store_bits_same_bool(1 + 4, false); @@ -371,7 +389,7 @@ extern const CurrencyCollection t_CurrencyCollection; struct CommonMsgInfo final : TLB_Complex { enum { int_msg_info = 0, ext_in_msg_info = 2, ext_out_msg_info = 3 }; bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { int v = (int)cs.prefetch_ulong(2); return v == 1 ? int_msg_info : v; @@ -402,14 +420,14 @@ struct TickTock final : TLB { extern const TickTock t_TickTock; struct StateInit final : TLB_Complex { - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; bool get_ticktock(vm::CellSlice& cs, int& ticktock) const; }; extern const StateInit t_StateInit; struct Message final : TLB_Complex { - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; bool extract_info(vm::CellSlice& cs) const; bool get_created_lt(vm::CellSlice& cs, unsigned long long& created_lt) const; bool is_internal(const vm::CellSlice& cs) const { @@ -425,7 +443,7 @@ struct IntermediateAddress final : TLB_Complex { enum { interm_addr_regular = 0, interm_addr_simple = 2, interm_addr_ext = 3 }; int get_size(const vm::CellSlice& cs) const override; bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; bool fetch_regular(vm::CellSlice& cs, int& use_dst_bits) const { return cs.fetch_uint_to(8, use_dst_bits) && use_dst_bits <= 96; } @@ -439,7 +457,7 @@ extern const IntermediateAddress t_IntermediateAddress; struct MsgEnvelope final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; bool extract_fwd_fees_remaining(vm::CellSlice& cs) const; struct Record { typedef MsgEnvelope type_class; @@ -463,28 +481,28 @@ extern const RefTo t_Ref_MsgEnvelope; struct StorageUsed final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; }; extern const StorageUsed t_StorageUsed; struct StorageUsedShort final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; }; extern const StorageUsedShort t_StorageUsedShort; struct StorageInfo final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; }; extern const StorageInfo t_StorageInfo; struct AccountState final : TLB_Complex { enum { account_uninit = 0, account_frozen = 1, account_active = 2 }; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { int t = (int)cs.prefetch_ulong(2); return t == 3 ? account_active : t; @@ -496,7 +514,7 @@ extern const AccountState t_AccountState; struct AccountStorage final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; bool skip_copy_balance(vm::CellBuilder& cb, vm::CellSlice& cs) const; }; @@ -508,7 +526,7 @@ struct Account final : TLB_Complex { Account(bool _allow_empty = false) : allow_empty(_allow_empty) { } bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; // Ref get_balance(const vm::CellSlice& cs) const; bool skip_copy_balance(vm::CellBuilder& cb, vm::CellSlice& cs) const; bool skip_copy_depth_balance(vm::CellBuilder& cb, vm::CellSlice& cs) const; @@ -518,7 +536,7 @@ struct Account final : TLB_Complex { }; extern const Account t_Account, t_AccountE; -extern const RefTo t_Ref_Account; +extern const RefTo t_Ref_AccountE; struct AccountStatus final : TLB { enum { acc_state_uninit, acc_state_frozen, acc_state_active, acc_state_nonexist }; @@ -553,8 +571,8 @@ struct ShardAccount final : TLB_Complex { bool skip(vm::CellSlice& cs) const override { return cs.advance_ext(0x140, 1); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { - return cs.advance(0x140) && t_Ref_Account.validate_skip(cs, weak); + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return cs.advance(0x140) && t_Ref_AccountE.validate_skip(ops, cs, weak); } static bool unpack(vm::CellSlice& cs, Record& info) { return info.unpack(cs); @@ -569,7 +587,7 @@ extern const ShardAccount t_ShardAccount; struct DepthBalanceInfo final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; bool null_value(vm::CellBuilder& cb) const override; bool add_values(vm::CellBuilder& cb, vm::CellSlice& cs1, vm::CellSlice& cs2) const override; }; @@ -590,8 +608,8 @@ struct ShardAccounts final : TLB_Complex { bool skip(vm::CellSlice& cs) const override { return dict_type.skip(cs); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { - return dict_type.validate_skip(cs, weak); + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return dict_type.validate_skip(ops, cs, weak); } }; @@ -615,7 +633,7 @@ extern const AccStatusChange t_AccStatusChange; struct TrStoragePhase final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; bool get_storage_fees(vm::CellSlice& cs, td::RefInt256& storage_fees) const; bool maybe_get_storage_fees(vm::CellSlice& cs, td::RefInt256& storage_fees) const; }; @@ -624,27 +642,31 @@ extern const TrStoragePhase t_TrStoragePhase; struct TrCreditPhase final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; }; extern const TrCreditPhase t_TrCreditPhase; struct TrComputeInternal1 final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; }; struct ComputeSkipReason final : TLB { - enum { cskip_no_state = 0, cskip_bad_state = 1, cskip_no_gas = 2 }; + enum { cskip_no_state = 0, cskip_bad_state = 1, cskip_no_gas = 2, cskip_suspended = 3 }; int get_size(const vm::CellSlice& cs) const override { - return 2; + return cs.prefetch_ulong(2) == 3 ? 3 : 2; } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { - return get_tag(cs) >= 0 && cs.advance(2); + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { + int tag = get_tag(cs); + return tag >= 0 && cs.advance(tag == 3 ? 3 : 2); } int get_tag(const vm::CellSlice& cs) const override { int t = (int)cs.prefetch_ulong(2); - return t < 3 ? t : -1; + if (t == 3 && cs.prefetch_ulong(3) != 0b110) { + return -1; + } + return t; } }; @@ -653,7 +675,7 @@ extern const ComputeSkipReason t_ComputeSkipReason; struct TrComputePhase final : TLB_Complex { enum { tr_phase_compute_skipped = 0, tr_phase_compute_vm = 1 }; bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { return (int)cs.prefetch_ulong(1); } @@ -663,7 +685,7 @@ extern const TrComputePhase t_TrComputePhase; struct TrActionPhase final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; }; extern const TrActionPhase t_TrActionPhase; @@ -671,7 +693,7 @@ extern const TrActionPhase t_TrActionPhase; struct TrBouncePhase final : TLB_Complex { enum { tr_phase_bounce_negfunds = 0, tr_phase_bounce_nofunds = 1, tr_phase_bounce_ok = 2 }; bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override; }; @@ -679,7 +701,7 @@ extern const TrBouncePhase t_TrBouncePhase; struct SplitMergeInfo final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; }; extern const SplitMergeInfo t_SplitMergeInfo; @@ -695,7 +717,7 @@ struct TransactionDescr final : TLB_Complex { trans_merge_install = 7 }; bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override; bool skip_to_storage_phase(vm::CellSlice& cs, bool& found) const; bool get_storage_fees(Ref cell, td::RefInt256& storage_fees) const; @@ -705,14 +727,14 @@ extern const TransactionDescr t_TransactionDescr; struct Transaction_aux final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; }; extern const Transaction_aux t_Transaction_aux; struct Transaction final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; bool get_total_fees(vm::CellSlice&& cs, block::CurrencyCollection& total_fees) const; bool get_descr(Ref cell, Ref& tdescr) const; bool get_descr(vm::CellSlice& cs, Ref& tdescr) const; @@ -735,7 +757,7 @@ struct HashUpdate final : TLB_Complex { bool skip(vm::CellSlice& cs) const override { return cs.advance(8 + 256 * 2); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { return cs.fetch_ulong(8) == 0x72 && cs.advance(256 * 2); } }; @@ -745,7 +767,7 @@ extern const RefTo t_Ref_HashUpdate; struct AccountBlock final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; bool get_total_fees(vm::CellSlice&& cs, block::CurrencyCollection& total_fees) const; }; @@ -762,7 +784,7 @@ extern const HashmapAugE t_ShardAccountBlocks; // (HashmapAugE 256 AccountBlock struct ImportFees final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; bool null_value(vm::CellBuilder& cb) const override { return cb.store_bits_same_bool(4 + 4 + 1, false); } @@ -782,7 +804,7 @@ struct InMsg final : TLB_Complex { msg_discard_tr = 7 }; bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { return (int)cs.prefetch_ulong(3); } @@ -798,13 +820,15 @@ struct OutMsg final : TLB_Complex { msg_export_imm = 2, msg_export_tr = 3, msg_export_deq_imm = 4, - msg_export_deq = 6, + msg_export_deq = 12, + msg_export_deq_short = 13, msg_export_tr_req = 7 }; bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { - return (int)cs.prefetch_ulong(3); + int t = (int)cs.prefetch_ulong(3); + return t != 6 ? t : (int)cs.prefetch_ulong(4); } bool get_export_value(vm::CellBuilder& cb, vm::CellSlice& cs) const; bool get_created_lt(vm::CellSlice& cs, unsigned long long& created_lt) const; @@ -830,8 +854,8 @@ struct InMsgDescr final : TLB_Complex { bool skip(vm::CellSlice& cs) const override { return dict_type.skip(cs); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { - return dict_type.validate_skip(cs, weak); + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return dict_type.validate_skip(ops, cs, weak); } }; @@ -853,8 +877,8 @@ struct OutMsgDescr final : TLB_Complex { bool skip(vm::CellSlice& cs) const override { return dict_type.skip(cs); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { - return dict_type.validate_skip(cs, weak); + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return dict_type.validate_skip(ops, cs, weak); } }; @@ -867,7 +891,7 @@ struct EnqueuedMsg final : TLB_Complex { bool skip(vm::CellSlice& cs) const override { return cs.advance_ext(0x10040); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; bool unpack(vm::CellSlice& cs, EnqueuedMsgDescr& descr) const { return descr.unpack(cs); } @@ -891,8 +915,8 @@ struct OutMsgQueue final : TLB_Complex { bool skip(vm::CellSlice& cs) const override { return dict_type.skip(cs); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { - return dict_type.validate_skip(cs, weak); + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return dict_type.validate_skip(ops, cs, weak); } }; @@ -910,7 +934,7 @@ extern const HashmapE t_IhrPendingInfo; struct OutMsgQueueInfo final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; }; extern const OutMsgQueueInfo t_OutMsgQueueInfo; @@ -946,7 +970,7 @@ struct ShardIdent final : TLB_Complex { bool skip(vm::CellSlice& cs) const override { return cs.advance(get_size(cs)); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { return 0; } @@ -962,7 +986,7 @@ struct ShardIdent::Record { int shard_pfx_bits; int workchain_id; unsigned long long shard_prefix; - Record() : shard_pfx_bits(-1) { + Record() : shard_pfx_bits(-1), workchain_id(ton::workchainInvalid), shard_prefix(0) { } Record(int _pfxlen, int _wcid, unsigned long long _pfx) : shard_pfx_bits(_pfxlen), workchain_id(_wcid), shard_prefix(_pfx) { @@ -985,7 +1009,7 @@ struct BlockIdExt final : TLB_Complex { bool skip(vm::CellSlice& cs) const override { return cs.advance(get_size(cs)); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; bool unpack(vm::CellSlice& cs, ton::BlockIdExt& data) const; bool pack(vm::CellBuilder& cb, const ton::BlockIdExt& data) const; }; @@ -995,7 +1019,7 @@ extern const BlockIdExt t_BlockIdExt; struct ShardState final : TLB_Complex { enum { shard_state = (int)0x9023afe2, split_state = 0x5f327da5 }; bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { return (int)cs.prefetch_ulong(32) == shard_state ? shard_state : -1; } @@ -1005,7 +1029,7 @@ extern const ShardState t_ShardState; struct ShardState_aux final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { return 0; } @@ -1016,7 +1040,7 @@ extern const ShardState_aux t_ShardState_aux; struct LibDescr final : TLB_Complex { enum { shared_lib_descr = 0 }; bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { return (int)cs.prefetch_ulong(2); } @@ -1029,7 +1053,7 @@ struct BlkPrevInfo final : TLB_Complex { BlkPrevInfo(bool _merged) : merged(_merged) { } bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; }; extern const BlkPrevInfo t_BlkPrevInfo_0; @@ -1037,7 +1061,7 @@ extern const BlkPrevInfo t_BlkPrevInfo_0; struct McStateExtra final : TLB_Complex { enum { masterchain_state_extra = 0xcc26 }; bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; }; extern const McStateExtra t_McStateExtra; @@ -1074,7 +1098,7 @@ extern const Aug_OldMcBlocksInfo aug_OldMcBlocksInfo; struct ShardFeeCreated final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; bool null_value(vm::CellBuilder& cb) const override; bool add_values(vm::CellBuilder& cb, vm::CellSlice& cs1, vm::CellSlice& cs2) const override; }; @@ -1089,5 +1113,9 @@ struct Aug_ShardFees final : AugmentationCheckData { extern const Aug_ShardFees aug_ShardFees; +// Validate dict of libraries in message: used when sending and receiving message +bool validate_message_libs(const td::Ref &cell); +bool validate_message_relaxed_libs(const td::Ref &cell); + } // namespace tlb } // namespace block diff --git a/crypto/block/block.cpp b/crypto/block/block.cpp index b2cad142..a22fd1e5 100644 --- a/crypto/block/block.cpp +++ b/crypto/block/block.cpp @@ -14,18 +14,20 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "td/utils/bits.h" #include "block/block.h" #include "block/block-auto.h" #include "block/block-parse.h" +#include "block/mc-config.h" #include "ton/ton-shard.h" #include "common/bigexp.h" #include "common/util.h" #include "td/utils/crypto.h" #include "td/utils/tl_storers.h" #include "td/utils/misc.h" +#include "td/utils/Random.h" namespace block { using namespace std::literals::string_literals; @@ -368,14 +370,14 @@ std::unique_ptr MsgProcessedUptoCollection::unpack(t return v && v->valid ? std::move(v) : std::unique_ptr{}; } -bool MsgProcessedUpto::contains(const MsgProcessedUpto& other) const & { +bool MsgProcessedUpto::contains(const MsgProcessedUpto& other) const& { return ton::shard_is_ancestor(shard, other.shard) && mc_seqno >= other.mc_seqno && (last_inmsg_lt > other.last_inmsg_lt || (last_inmsg_lt == other.last_inmsg_lt && !(last_inmsg_hash < other.last_inmsg_hash))); } bool MsgProcessedUpto::contains(ton::ShardId other_shard, ton::LogicalTime other_lt, td::ConstBitPtr other_hash, - ton::BlockSeqno other_mc_seqno) const & { + ton::BlockSeqno other_mc_seqno) const& { return ton::shard_is_ancestor(shard, other_shard) && mc_seqno >= other_mc_seqno && (last_inmsg_lt > other_lt || (last_inmsg_lt == other_lt && !(last_inmsg_hash < other_hash))); } @@ -577,6 +579,15 @@ bool MsgProcessedUptoCollection::already_processed(const EnqueuedMsgDescr& msg) return false; } +bool MsgProcessedUptoCollection::can_check_processed() const { + for (const auto& entry : list) { + if (!entry.can_check_processed()) { + return false; + } + } + return true; +} + bool MsgProcessedUptoCollection::for_each_mcseqno(std::function func) const { for (const auto& entry : list) { if (!func(entry.mc_seqno)) { @@ -586,6 +597,36 @@ bool MsgProcessedUptoCollection::for_each_mcseqno(std::function> 3) + sum.cells * 12 + sum.internal_refs * 3 + sum.external_refs * 40 + accounts * 200 + - transactions * 200 + (extra ? 200 : 0); + transactions * 200 + (extra ? 200 : 0) + extra_out_msgs * 300 + public_library_diff * 700; } int BlockLimitStatus::classify() const { @@ -813,11 +854,11 @@ td::Status ShardState::unpack_out_msg_queue_info(Ref out_msg_queue_inf LOG(DEBUG) << "unpacking ProcessedUpto of our previous block " << id_.to_str(); block::gen::t_ProcessedInfo.print(std::cerr, qinfo.proc_info); } - if (!block::gen::t_ProcessedInfo.validate_csr(qinfo.proc_info)) { + if (!block::gen::t_ProcessedInfo.validate_csr(1024, qinfo.proc_info)) { return td::Status::Error( -666, "ProcessedInfo in the state of "s + id_.to_str() + " is invalid according to automated validity checks"); } - if (!block::gen::t_IhrPendingInfo.validate_csr(qinfo.ihr_pending)) { + if (!block::gen::t_IhrPendingInfo.validate_csr(1024, qinfo.ihr_pending)) { return td::Status::Error( -666, "IhrPendingInfo in the state of "s + id_.to_str() + " is invalid according to automated validity checks"); } @@ -968,8 +1009,8 @@ td::Status ShardState::merge_with(ShardState& sib) { return td::Status::OK(); } -td::Result> ShardState::compute_split_out_msg_queue( - ton::ShardIdFull subshard) { +td::Result> ShardState::compute_split_out_msg_queue(ton::ShardIdFull subshard, + td::uint32* queue_size) { auto shard = id_.shard_full(); if (!ton::shard_is_parent(shard, subshard)) { return td::Status::Error(-666, "cannot split subshard "s + subshard.to_str() + " from state of " + id_.to_str() + @@ -977,7 +1018,7 @@ td::Result> ShardState::compute_split_o } CHECK(out_msg_queue_); auto subqueue = std::make_unique(*out_msg_queue_); - int res = block::filter_out_msg_queue(*subqueue, shard, subshard); + int res = block::filter_out_msg_queue(*subqueue, shard, subshard, queue_size); if (res < 0) { return td::Status::Error(-666, "error splitting OutMsgQueue of "s + id_.to_str()); } @@ -999,7 +1040,7 @@ td::Result> ShardState::compu return std::move(sub_processed_upto); } -td::Status ShardState::split(ton::ShardIdFull subshard) { +td::Status ShardState::split(ton::ShardIdFull subshard, td::uint32* queue_size) { if (!ton::shard_is_parent(id_.shard_full(), subshard)) { return td::Status::Error(-666, "cannot split subshard "s + subshard.to_str() + " from state of " + id_.to_str() + " because it is not a parent"); @@ -1017,7 +1058,7 @@ td::Status ShardState::split(ton::ShardIdFull subshard) { auto shard1 = id_.shard_full(); CHECK(ton::shard_is_parent(shard1, subshard)); CHECK(out_msg_queue_); - int res1 = block::filter_out_msg_queue(*out_msg_queue_, shard1, subshard); + int res1 = block::filter_out_msg_queue(*out_msg_queue_, shard1, subshard, queue_size); if (res1 < 0) { return td::Status::Error(-666, "error splitting OutMsgQueue of "s + id_.to_str()); } @@ -1036,7 +1077,7 @@ td::Status ShardState::split(ton::ShardIdFull subshard) { LOG(DEBUG) << "splitting total_balance"; auto old_total_balance = total_balance_; auto accounts_extra = account_dict_->get_root_extra(); - if (!(accounts_extra.write().advance(5) && total_balance_.validate_unpack(accounts_extra))) { + if (!(accounts_extra.write().advance(5) && total_balance_.validate_unpack(accounts_extra, 1024))) { LOG(ERROR) << "cannot unpack CurrencyCollection from the root of newly-split accounts dictionary"; return td::Status::Error( -666, "error splitting total balance in account dictionary of shardchain state "s + id_.to_str()); @@ -1057,8 +1098,12 @@ td::Status ShardState::split(ton::ShardIdFull subshard) { return td::Status::OK(); } -int filter_out_msg_queue(vm::AugmentedDictionary& out_queue, ton::ShardIdFull old_shard, ton::ShardIdFull subshard) { - return out_queue.filter([subshard, old_shard](vm::CellSlice& cs, td::ConstBitPtr key, int key_len) -> int { +int filter_out_msg_queue(vm::AugmentedDictionary& out_queue, ton::ShardIdFull old_shard, ton::ShardIdFull subshard, + td::uint32* queue_size) { + if (queue_size) { + *queue_size = 0; + } + return out_queue.filter([=](vm::CellSlice& cs, td::ConstBitPtr key, int key_len) -> int { CHECK(key_len == 352); LOG(DEBUG) << "scanning OutMsgQueue entry with key " << key.to_hex(key_len); block::tlb::MsgEnvelope::Record_std env; @@ -1081,20 +1126,24 @@ int filter_out_msg_queue(vm::AugmentedDictionary& out_queue, ton::ShardIdFull ol << " does not contain current address belonging to shard " << old_shard.to_str(); return -1; } - return ton::shard_contains(subshard, cur_prefix); + bool res = ton::shard_contains(subshard, cur_prefix); + if (res && queue_size) { + ++*queue_size; + } + return res; }); } -bool CurrencyCollection::validate() const { - return is_valid() && td::sgn(grams) >= 0 && validate_extra(); +bool CurrencyCollection::validate(int max_cells) const { + return is_valid() && td::sgn(grams) >= 0 && validate_extra(max_cells); } -bool CurrencyCollection::validate_extra() const { +bool CurrencyCollection::validate_extra(int max_cells) const { if (extra.is_null()) { return true; } vm::CellBuilder cb; - return cb.store_maybe_ref(extra) && block::tlb::t_ExtraCurrencyCollection.validate_ref(cb.finalize()); + return cb.store_maybe_ref(extra) && block::tlb::t_ExtraCurrencyCollection.validate_ref(max_cells, cb.finalize()); } bool CurrencyCollection::add(const CurrencyCollection& a, const CurrencyCollection& b, CurrencyCollection& c) { @@ -1265,8 +1314,8 @@ bool CurrencyCollection::unpack(Ref csr) { return unpack_CurrencyCollection(std::move(csr), grams, extra) || invalidate(); } -bool CurrencyCollection::validate_unpack(Ref csr) { - return (csr.not_null() && block::tlb::t_CurrencyCollection.validate(*csr) && +bool CurrencyCollection::validate_unpack(Ref csr, int max_cells) { + return (csr.not_null() && block::tlb::t_CurrencyCollection.validate_upto(max_cells, *csr) && unpack_CurrencyCollection(std::move(csr), grams, extra)) || invalidate(); } @@ -1323,42 +1372,61 @@ std::ostream& operator<<(std::ostream& os, const CurrencyCollection& cc) { bool ValueFlow::set_zero() { return from_prev_blk.set_zero() && to_next_blk.set_zero() && imported.set_zero() && exported.set_zero() && fees_collected.set_zero() && fees_imported.set_zero() && recovered.set_zero() && created.set_zero() && - minted.set_zero(); + minted.set_zero() && burned.set_zero(); } bool ValueFlow::validate() const { return is_valid() && from_prev_blk + imported + fees_imported + created + minted + recovered == - to_next_blk + exported + fees_collected; + to_next_blk + exported + fees_collected + burned; } bool ValueFlow::store(vm::CellBuilder& cb) const { vm::CellBuilder cb2; - return cb.store_long_bool(block::gen::ValueFlow::cons_tag[0], 32) // value_flow ^[ - && from_prev_blk.store(cb2) // from_prev_blk:CurrencyCollection - && to_next_blk.store(cb2) // to_next_blk:CurrencyCollection - && imported.store(cb2) // imported:CurrencyCollection - && exported.store(cb2) // exported:CurrencyCollection - && cb.store_ref_bool(cb2.finalize()) // ] - && fees_collected.store(cb) // fees_collected:CurrencyCollection - && fees_imported.store(cb2) // ^[ fees_imported:CurrencyCollection - && recovered.store(cb2) // recovered:CurrencyCollection - && created.store(cb2) // created:CurrencyCollection - && minted.store(cb2) // minted:CurrencyCollection - && cb.store_ref_bool(cb2.finalize()); // ] = ValueFlow; + auto type = burned.is_zero() ? block::gen::ValueFlow::value_flow : block::gen::ValueFlow::value_flow_v2; + return cb.store_long_bool(block::gen::ValueFlow::cons_tag[type], 32) // ^[ + && from_prev_blk.store(cb2) // from_prev_blk:CurrencyCollection + && to_next_blk.store(cb2) // to_next_blk:CurrencyCollection + && imported.store(cb2) // imported:CurrencyCollection + && exported.store(cb2) // exported:CurrencyCollection + && cb.store_ref_bool(cb2.finalize()) // ] + && fees_collected.store(cb) // fees_collected:CurrencyCollection + && (burned.is_zero() || burned.store(cb)) // fees_burned:CurrencyCollection + && fees_imported.store(cb2) // ^[ fees_imported:CurrencyCollection + && recovered.store(cb2) // recovered:CurrencyCollection + && created.store(cb2) // created:CurrencyCollection + && minted.store(cb2) // minted:CurrencyCollection + && cb.store_ref_bool(cb2.finalize()); // ] = ValueFlow; } bool ValueFlow::fetch(vm::CellSlice& cs) { - block::gen::ValueFlow::Record f; - if (!(tlb::unpack(cs, f) && from_prev_blk.validate_unpack(std::move(f.r1.from_prev_blk)) && - to_next_blk.validate_unpack(std::move(f.r1.to_next_blk)) && - imported.validate_unpack(std::move(f.r1.imported)) && exported.validate_unpack(std::move(f.r1.exported)) && - fees_collected.validate_unpack(std::move(f.fees_collected)) && - fees_imported.validate_unpack(std::move(f.r2.fees_imported)) && - recovered.validate_unpack(std::move(f.r2.recovered)) && created.validate_unpack(std::move(f.r2.created)) && - minted.validate_unpack(std::move(f.r2.minted)))) { + if (cs.size() < 32) { return invalidate(); } - return true; + auto tag = cs.prefetch_ulong(32); + block::gen::ValueFlow::Record_value_flow f1; + if (tag == block::gen::ValueFlow::cons_tag[block::gen::ValueFlow::value_flow] && tlb::unpack(cs, f1) && + from_prev_blk.validate_unpack(std::move(f1.r1.from_prev_blk)) && + to_next_blk.validate_unpack(std::move(f1.r1.to_next_blk)) && + imported.validate_unpack(std::move(f1.r1.imported)) && exported.validate_unpack(std::move(f1.r1.exported)) && + fees_collected.validate_unpack(std::move(f1.fees_collected)) && burned.set_zero() && + fees_imported.validate_unpack(std::move(f1.r2.fees_imported)) && + recovered.validate_unpack(std::move(f1.r2.recovered)) && created.validate_unpack(std::move(f1.r2.created)) && + minted.validate_unpack(std::move(f1.r2.minted))) { + return true; + } + block::gen::ValueFlow::Record_value_flow_v2 f2; + if (tag == block::gen::ValueFlow::cons_tag[block::gen::ValueFlow::value_flow_v2] && tlb::unpack(cs, f2) && + from_prev_blk.validate_unpack(std::move(f2.r1.from_prev_blk)) && + to_next_blk.validate_unpack(std::move(f2.r1.to_next_blk)) && + imported.validate_unpack(std::move(f2.r1.imported)) && exported.validate_unpack(std::move(f2.r1.exported)) && + fees_collected.validate_unpack(std::move(f2.fees_collected)) && + burned.validate_unpack(std::move(f2.burned)) && + fees_imported.validate_unpack(std::move(f2.r2.fees_imported)) && + recovered.validate_unpack(std::move(f2.r2.recovered)) && created.validate_unpack(std::move(f2.r2.created)) && + minted.validate_unpack(std::move(f2.r2.minted))) { + return true; + } + return invalidate(); } bool ValueFlow::unpack(Ref csr) { @@ -1383,7 +1451,8 @@ bool ValueFlow::show(std::ostream& os) const { show_one(os, " to_next_blk:", to_next_blk) && show_one(os, " imported:", imported) && show_one(os, " exported:", exported) && show_one(os, " fees_collected:", fees_collected) && show_one(os, " fees_imported:", fees_imported) && show_one(os, " recovered:", recovered) && - show_one(os, " created:", created) && show_one(os, " minted:", minted) && say(os, ")")) || + show_one(os, " created:", created) && show_one(os, " minted:", minted) && + (burned.is_zero() || show_one(os, " burned:", burned)) && say(os, ")")) || (say(os, "...)") && false); } @@ -1506,6 +1575,89 @@ bool unpack_CreatorStats(Ref cs, DiscountedCounter& mc_cnt, Disco } } +/* + * + * Monte Carlo simulator for computing the share of shardchain blocks generated by each validator + * + */ + +bool MtCarloComputeShare::compute() { + ok = false; + if (W.size() >= (1U << 31) || W.empty()) { + return false; + } + K = std::min(K, N); + if (K <= 0 || iterations <= 0) { + return false; + } + double tot_weight = 0., acc = 0.; + for (int i = 0; i < N; i++) { + if (W[i] <= 0.) { + return false; + } + tot_weight += W[i]; + } + CW.resize(N); + RW.resize(N); + for (int i = 0; i < N; i++) { + CW[i] = acc; + acc += W[i] /= tot_weight; + RW[i] = 0.; + } + R0 = 0.; + H.resize(N); + A.resize(K); + for (long long it = 0; it < iterations; ++it) { + gen_vset(); + } + for (int i = 0; i < N; i++) { + RW[i] = W[i] * (RW[i] + R0) / (double)iterations; + } + return ok = true; +} + +void MtCarloComputeShare::gen_vset() { + double total_wt = 1.; + int hc = 0; + for (int i = 0; i < K; i++) { + CHECK(total_wt > 0); + double inv_wt = 1. / total_wt; + R0 += inv_wt; + for (int j = 0; j < i; j++) { + RW[A[j]] -= inv_wt; + } + // double p = drand48() * total_wt; + double p = (double)td::Random::fast_uint64() * total_wt / (1. * (1LL << 32) * (1LL << 32)); + for (int h = 0; h < hc; h++) { + if (p < H[h].first) { + break; + } + p += H[h].second; + } + int a = -1, b = N, c; + while (b - a > 1) { + c = ((a + b) >> 1); + if (CW[c] <= p) { + a = c; + } else { + b = c; + } + } + CHECK(a >= 0 && a < N); + CHECK(total_wt >= W[a]); + total_wt -= W[a]; + double x = CW[a]; + c = hc++; + while (c > 0 && H[c - 1].first > x) { + H[c] = H[c - 1]; + --c; + } + H[c].first = x; + H[c].second = W[a]; + A[i] = a; + } +} + /* * * Other block-related functions @@ -1593,7 +1745,7 @@ bool check_one_config_param(Ref cs_ref, td::ConstBitPtr key, td:: } else if (idx < 0) { return true; } - bool ok = block::gen::ConfigParam{idx}.validate_ref(std::move(cell)); + bool ok = block::gen::ConfigParam{idx}.validate_ref(1024, std::move(cell)); if (!ok) { LOG(ERROR) << "configuration parameter #" << idx << " is invalid"; } @@ -1602,33 +1754,50 @@ bool check_one_config_param(Ref cs_ref, td::ConstBitPtr key, td:: const int mandatory_config_params[] = {18, 20, 21, 22, 23, 24, 25, 28, 34}; -bool valid_config_data(Ref cell, const td::BitArray<256>& addr, bool catch_errors, bool relax_par0) { +bool valid_config_data(Ref cell, const td::BitArray<256>& addr, bool catch_errors, bool relax_par0, + Ref old_mparams) { using namespace std::placeholders; if (cell.is_null()) { return false; } - if (!catch_errors) { - vm::Dictionary dict{std::move(cell), 32}; - for (int x : mandatory_config_params) { - if (!dict.int_key_exists(x)) { - LOG(ERROR) << "mandatory configuration parameter #" << x << " is missing"; - return false; - } + if (catch_errors) { + try { + return valid_config_data(std::move(cell), addr, false, relax_par0, std::move(old_mparams)); + } catch (vm::VmError&) { + return false; } - return dict.check_for_each(std::bind(check_one_config_param, _1, _2, addr.cbits(), relax_par0)); } - try { - vm::Dictionary dict{std::move(cell), 32}; - for (int x : mandatory_config_params) { - if (!dict.int_key_exists(x)) { - LOG(ERROR) << "mandatory configuration parameter #" << x << " is missing"; - return false; - } - } - return dict.check_for_each(std::bind(check_one_config_param, _1, _2, addr.cbits(), relax_par0)); - } catch (vm::VmError&) { + vm::Dictionary dict{std::move(cell), 32}; + if (!dict.check_for_each(std::bind(check_one_config_param, _1, _2, addr.cbits(), relax_par0))) { return false; } + for (int x : mandatory_config_params) { + if (!dict.int_key_exists(x)) { + LOG(ERROR) << "mandatory configuration parameter #" << x << " is missing"; + return false; + } + } + return config_params_present(dict, dict.lookup_ref(td::BitArray<32>{9})) && + config_params_present(dict, std::move(old_mparams)); +} + +bool config_params_present(vm::Dictionary& dict, Ref param_dict_root) { + auto res = block::Config::unpack_param_dict(std::move(param_dict_root)); + if (res.is_error()) { + LOG(ERROR) + << "invalid mandatory parameters dictionary while checking existence of all mandatory configuration parameters"; + return false; + } + for (int x : res.move_as_ok()) { + // LOG(DEBUG) << "checking whether mandatory configuration parameter #" << x << " exists"; + if (!dict.int_key_exists(x)) { + LOG(ERROR) << "configuration parameter #" << x + << " (declared as mandatory in configuration parameter #9) is missing"; + return false; + } + } + // LOG(DEBUG) << "all mandatory configuration parameters present"; + return true; } bool add_extra_currency(Ref extra1, Ref extra2, Ref& res) { @@ -1651,7 +1820,7 @@ bool sub_extra_currency(Ref extra1, Ref extra2, Ref= 0; } } @@ -1666,7 +1835,7 @@ ton::AccountIdPrefixFull interpolate_addr(const ton::AccountIdPrefixFull& src, c unsigned long long mask = (std::numeric_limits::max() >> (d - 32)); return ton::AccountIdPrefixFull{dest.workchain, (dest.account_id_prefix & ~mask) | (src.account_id_prefix & mask)}; } else { - int mask = (-1 >> d); + int mask = (int)(~0U >> d); return ton::AccountIdPrefixFull{(dest.workchain & ~mask) | (src.workchain & mask), src.account_id_prefix}; } } diff --git a/crypto/block/block.h b/crypto/block/block.h index 0cfbe24a..c54949f4 100644 --- a/crypto/block/block.h +++ b/crypto/block/block.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/refcnt.hpp" @@ -163,16 +163,25 @@ struct MsgProcessedUpto { MsgProcessedUpto(ton::ShardId _shard, ton::BlockSeqno _mcseqno, ton::LogicalTime _lt, td::ConstBitPtr _hash) : shard(_shard), mc_seqno(_mcseqno), last_inmsg_lt(_lt), last_inmsg_hash(_hash) { } - bool operator<(const MsgProcessedUpto& other) const & { + bool operator<(const MsgProcessedUpto& other) const& { return shard < other.shard || (shard == other.shard && mc_seqno < other.mc_seqno); } - bool contains(const MsgProcessedUpto& other) const &; + bool contains(const MsgProcessedUpto& other) const&; bool contains(ton::ShardId other_shard, ton::LogicalTime other_lt, td::ConstBitPtr other_hash, - ton::BlockSeqno other_mc_seqno) const &; + ton::BlockSeqno other_mc_seqno) const&; // NB: this is for checking whether we have already imported an internal message bool already_processed(const EnqueuedMsgDescr& msg) const; + bool can_check_processed() const { + return (bool)compute_shard_end_lt; + } + std::ostream& print(std::ostream& os) const; + std::string to_str() const; }; +static inline std::ostream& operator<<(std::ostream& os, const MsgProcessedUpto& proc) { + return proc.print(os); +} + struct MsgProcessedUptoCollection { ton::ShardIdFull owner; bool valid{false}; @@ -197,9 +206,16 @@ struct MsgProcessedUptoCollection { bool combine_with(const MsgProcessedUptoCollection& other); // NB: this is for checking whether we have already imported an internal message bool already_processed(const EnqueuedMsgDescr& msg) const; + bool can_check_processed() const; bool for_each_mcseqno(std::function) const; + std::ostream& print(std::ostream& os) const; + std::string to_str() const; }; +static inline std::ostream& operator<<(std::ostream& os, const MsgProcessedUptoCollection& proc_coll) { + return proc_coll.print(os); +} + struct ParamLimits { enum { limits_cnt = 4 }; enum { cl_underload = 0, cl_normal = 1, cl_soft = 2, cl_medium = 3, cl_hard = 4 }; @@ -245,7 +261,8 @@ struct BlockLimitStatus { ton::LogicalTime cur_lt; td::uint64 gas_used{}; vm::NewCellStorageStat st_stat; - unsigned accounts{}, transactions{}; + unsigned accounts{}, transactions{}, extra_out_msgs{}; + unsigned public_library_diff{}; BlockLimitStatus(const BlockLimits& limits_, ton::LogicalTime lt = 0) : limits(limits_), cur_lt(std::max(limits_.start_lt, lt)) { } @@ -254,6 +271,8 @@ struct BlockLimitStatus { st_stat.set_zero(); transactions = accounts = 0; gas_used = 0; + extra_out_msgs = 0; + public_library_diff = 0; } td::uint64 estimate_block_size(const vm::NewCellStorageStat::Stat* extra = nullptr) const; int classify() const; @@ -323,8 +342,8 @@ struct CurrencyCollection { grams.clear(); return false; } - bool validate() const; - bool validate_extra() const; + bool validate(int max_cells = 1024) const; + bool validate_extra(int max_cells = 1024) const; bool operator==(const CurrencyCollection& other) const; bool operator!=(const CurrencyCollection& other) const { return !operator==(other); @@ -360,7 +379,7 @@ struct CurrencyCollection { bool fetch(vm::CellSlice& cs); bool fetch_exact(vm::CellSlice& cs); bool unpack(Ref csr); - bool validate_unpack(Ref csr); + bool validate_unpack(Ref csr, int max_cells = 1024); Ref pack() const; bool pack_to(Ref& csr) const { return (csr = pack()).not_null(); @@ -414,10 +433,11 @@ struct ShardState { ton::BlockSeqno prev_mc_block_seqno, bool after_split, bool clear_history, std::function for_each_mcseqno); td::Status merge_with(ShardState& sib); - td::Result> compute_split_out_msg_queue(ton::ShardIdFull subshard); + td::Result> compute_split_out_msg_queue(ton::ShardIdFull subshard, + td::uint32* queue_size = nullptr); td::Result> compute_split_processed_upto( ton::ShardIdFull subshard); - td::Status split(ton::ShardIdFull subshard); + td::Status split(ton::ShardIdFull subshard, td::uint32* queue_size = nullptr); td::Status unpack_out_msg_queue_info(Ref out_msg_queue_info); bool clear_load_history() { overload_history_ = underload_history_ = 0; @@ -439,7 +459,7 @@ struct ShardState { struct ValueFlow { struct SetZero {}; CurrencyCollection from_prev_blk, to_next_blk, imported, exported, fees_collected, fees_imported, recovered, created, - minted; + minted, burned; ValueFlow() = default; ValueFlow(SetZero) : from_prev_blk{0} @@ -450,7 +470,8 @@ struct ValueFlow { , fees_imported{0} , recovered{0} , created{0} - , minted{0} { + , minted{0} + , burned{0} { } bool is_valid() const { return from_prev_blk.is_valid() && minted.is_valid(); @@ -580,7 +601,64 @@ struct BlockProofChain { td::Status validate(td::CancellationToken cancellation_token = {}); }; -int filter_out_msg_queue(vm::AugmentedDictionary& out_queue, ton::ShardIdFull old_shard, ton::ShardIdFull subshard); +// compute the share of shardchain blocks generated by each validator using Monte Carlo method +class MtCarloComputeShare { + int K, N; + long long iterations; + std::vector W; + std::vector CW, RW; + std::vector> H; + std::vector A; + double R0; + bool ok; + + public: + MtCarloComputeShare(int subset_size, const std::vector& weights, long long iteration_count = 1000000) + : K(subset_size), N((int)weights.size()), iterations(iteration_count), W(weights), ok(false) { + compute(); + } + MtCarloComputeShare(int subset_size, int set_size, const double* weights, long long iteration_count = 1000000) + : K(subset_size), N(set_size), iterations(iteration_count), W(weights, weights + set_size), ok(false) { + compute(); + } + bool is_ok() const { + return ok; + } + const double* share_array() const { + return ok ? RW.data() : nullptr; + } + const double* weights_array() const { + return ok ? W.data() : nullptr; + } + double operator[](int i) const { + return ok ? RW.at(i) : -1.; + } + double share(int i) const { + return ok ? RW.at(i) : -1.; + } + double weight(int i) const { + return ok ? W.at(i) : -1.; + } + int size() const { + return N; + } + int subset_size() const { + return K; + } + long long performed_iterations() const { + return iterations; + } + + private: + bool set_error() { + return ok = false; + } + bool compute(); + void gen_vset(); +}; + +int filter_out_msg_queue(vm::AugmentedDictionary& out_queue, ton::ShardIdFull old_shard, ton::ShardIdFull subshard, + td::uint32* queue_size = nullptr); std::ostream& operator<<(std::ostream& os, const ShardId& shard_id); @@ -606,7 +684,8 @@ bool unpack_CurrencyCollection(Ref csr, td::RefInt256& value, Ref bool valid_library_collection(Ref cell, bool catch_errors = true); bool valid_config_data(Ref cell, const td::BitArray<256>& addr, bool catch_errors = true, - bool relax_par0 = false); + bool relax_par0 = false, Ref old_mparams = {}); +bool config_params_present(vm::Dictionary& dict, Ref param_dict_root); bool add_extra_currency(Ref extra1, Ref extra2, Ref& res); bool sub_extra_currency(Ref extra1, Ref extra2, Ref& res); diff --git a/crypto/block/block.tlb b/crypto/block/block.tlb index a30cd89a..3ae54239 100644 --- a/crypto/block/block.tlb +++ b/crypto/block/block.tlb @@ -113,7 +113,10 @@ var_uint$_ {n:#} len:(#< n) value:(uint (len * 8)) = VarUInteger n; var_int$_ {n:#} len:(#< n) value:(int (len * 8)) = VarInteger n; -nanograms$_ amount:(VarUInteger 16) = Grams; +nanograms$_ amount:(VarUInteger 16) = Grams; + +_ grams:Grams = Coins; + // extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32)) = ExtraCurrencyCollection; @@ -140,8 +143,13 @@ tick_tock$_ tick:Bool tock:Bool = TickTock; _ split_depth:(Maybe (## 5)) special:(Maybe TickTock) code:(Maybe ^Cell) data:(Maybe ^Cell) - library:(HashmapE 256 SimpleLib) = StateInit; - + library:(Maybe ^Cell) = StateInit; + +// StateInitWithLibs is used to validate sent and received messages +_ split_depth:(Maybe (## 5)) special:(Maybe TickTock) + code:(Maybe ^Cell) data:(Maybe ^Cell) + library:(HashmapE 256 SimpleLib) = StateInitWithLibs; + simple_lib$_ public:Bool root:^Cell = SimpleLib; message$_ {X:Type} info:CommonMsgInfo @@ -151,6 +159,9 @@ message$_ {X:Type} info:CommonMsgInfo message$_ {X:Type} info:CommonMsgInfoRelaxed init:(Maybe (Either StateInit ^StateInit)) body:(Either X ^X) = MessageRelaxed X; + +_ (Message Any) = MessageAny; + // interm_addr_regular$0 use_dest_bits:(#<= 96) = IntermediateAddress; @@ -190,7 +201,10 @@ msg_export_new$001 out_msg:^MsgEnvelope transaction:^Transaction = OutMsg; msg_export_tr$011 out_msg:^MsgEnvelope imported:^InMsg = OutMsg; -msg_export_deq$110 out_msg:^MsgEnvelope // out_msg_hash:bits256 ? +msg_export_deq$1100 out_msg:^MsgEnvelope + import_block_lt:uint63 = OutMsg; +msg_export_deq_short$1101 msg_env_hash:bits256 + next_workchain:int32 next_addr_pfx:uint64 import_block_lt:uint64 = OutMsg; msg_export_tr_req$111 out_msg:^MsgEnvelope imported:^InMsg = OutMsg; @@ -266,6 +280,7 @@ transaction$0111 account_addr:bits256 lt:uint64 old:^X new:^X = MERKLE_UPDATE X; update_hashes#72 {X:Type} old_hash:bits256 new_hash:bits256 = HASH_UPDATE X; +!merkle_proof#03 {X:Type} virtual_hash:bits256 depth:uint16 virtual_root:^X = MERKLE_PROOF X; acc_trans#5 account_addr:bits256 transactions:(HashmapAug 64 ^Transaction CurrencyCollection) @@ -299,6 +314,7 @@ tr_phase_compute_vm$1 success:Bool msg_state_used:Bool cskip_no_state$00 = ComputeSkipReason; cskip_bad_state$01 = ComputeSkipReason; cskip_no_gas$10 = ComputeSkipReason; +cskip_suspended$110 = ComputeSkipReason; tr_phase_action$_ success:Bool valid:Bool no_funds:Bool status_change:AccStatusChange @@ -355,7 +371,7 @@ trans_merge_install$0111 split_info:SplitMergeInfo smc_info#076ef1ea actions:uint16 msgs_sent:uint16 unixtime:uint32 block_lt:uint64 trans_lt:uint64 rand_seed:bits256 balance_remaining:CurrencyCollection - myself:MsgAddressInt = SmartContractInfo; + myself:MsgAddressInt global_config:(Maybe Cell) = SmartContractInfo; // // out_list_empty$_ = OutList 0; @@ -368,7 +384,7 @@ action_reserve_currency#36e6b809 mode:(## 8) currency:CurrencyCollection = OutAction; libref_hash$0 lib_hash:bits256 = LibRef; libref_ref$1 library:^Cell = LibRef; -action_change_library#26fa1dd4 mode:(## 7) { mode <= 2 } +action_change_library#26fa1dd4 mode:(## 7) libref:LibRef = OutAction; out_list_node$_ prev:^Cell action:OutAction = OutListNode; @@ -414,7 +430,7 @@ block_info#9bc7a987 version:uint32 after_split:(## 1) want_split:Bool want_merge:Bool key_block:Bool vert_seqno_incr:(## 1) - flags:(## 8) + flags:(## 8) { flags <= 1 } seq_no:# vert_seq_no:# { vert_seq_no >= vert_seqno_incr } { prev_seq_no:# } { ~prev_seq_no + 1 = seq_no } shard:ShardIdent gen_utime:uint32 @@ -423,6 +439,7 @@ block_info#9bc7a987 version:uint32 gen_catchain_seqno:uint32 min_ref_mc_seqno:uint32 prev_key_block_seqno:uint32 + gen_software:flags . 0?GlobalVersion master_ref:not_master?^BlkMasterInfo prev_ref:^(BlkPrevInfo after_merge) prev_vert_ref:vert_seqno_incr?^(BlkPrevInfo 0) @@ -443,7 +460,7 @@ block_extra in_msg_descr:^InMsgDescr created_by:bits256 custom:(Maybe ^McBlockExtra) = BlockExtra; // -value_flow ^[ from_prev_blk:CurrencyCollection +value_flow#b8e48dfb ^[ from_prev_blk:CurrencyCollection to_next_blk:CurrencyCollection imported:CurrencyCollection exported:CurrencyCollection ] @@ -455,6 +472,19 @@ value_flow ^[ from_prev_blk:CurrencyCollection minted:CurrencyCollection ] = ValueFlow; +value_flow_v2#3ebf98b7 ^[ from_prev_blk:CurrencyCollection + to_next_blk:CurrencyCollection + imported:CurrencyCollection + exported:CurrencyCollection ] + fees_collected:CurrencyCollection + burned:CurrencyCollection + ^[ + fees_imported:CurrencyCollection + recovered:CurrencyCollection + created:CurrencyCollection + minted:CurrencyCollection + ] = ValueFlow; + // // bt_leaf$0 {X:Type} leaf:X = BinTree X; @@ -477,6 +507,18 @@ shard_descr#b seq_no:uint32 reg_mc_seqno:uint32 fees_collected:CurrencyCollection funds_created:CurrencyCollection = ShardDescr; +shard_descr_new#a seq_no:uint32 reg_mc_seqno:uint32 + start_lt:uint64 end_lt:uint64 + root_hash:bits256 file_hash:bits256 + before_split:Bool before_merge:Bool + want_split:Bool want_merge:Bool + nx_cc_updated:Bool flags:(## 3) { flags = 0 } + next_catchain_seqno:uint32 next_validator_shard:uint64 + min_ref_mc_seqno:uint32 gen_utime:uint32 + split_merge_at:FutureSplitMerge + ^[ fees_collected:CurrencyCollection + funds_created:CurrencyCollection ] = ShardDescr; + _ (HashmapE 32 ^(BinTree ShardDescr)) = ShardHashes; bta_leaf$0 {X:Type} {Y:Type} extra:Y leaf:X = BinTreeAug X Y; @@ -566,12 +608,28 @@ _ minter_addr:bits256 = ConfigParam 2; // ConfigParam 0 is used if absent _ fee_collector_addr:bits256 = ConfigParam 3; // ConfigParam 1 is used if absent _ dns_root_addr:bits256 = ConfigParam 4; // root TON DNS resolver +burning_config#01 + blackhole_addr:(Maybe bits256) + fee_burn_num:# fee_burn_denom:# { fee_burn_num <= fee_burn_denom } { fee_burn_denom >= 1 } = BurningConfig; +_ BurningConfig = ConfigParam 5; + _ mint_new_price:Grams mint_add_price:Grams = ConfigParam 6; _ to_mint:ExtraCurrencyCollection = ConfigParam 7; capabilities#c4 version:uint32 capabilities:uint64 = GlobalVersion; _ GlobalVersion = ConfigParam 8; // all zero if absent _ mandatory_params:(Hashmap 32 True) = ConfigParam 9; +_ critical_params:(Hashmap 32 True) = ConfigParam 10; + +cfg_vote_cfg#36 min_tot_rounds:uint8 max_tot_rounds:uint8 min_wins:uint8 max_losses:uint8 min_store_sec:uint32 max_store_sec:uint32 bit_price:uint32 cell_price:uint32 = ConfigProposalSetup; +cfg_vote_setup#91 normal_params:^ConfigProposalSetup critical_params:^ConfigProposalSetup = ConfigVotingSetup; +_ ConfigVotingSetup = ConfigParam 11; + +cfg_proposal#f3 param_id:int32 param_value:(Maybe ^Cell) if_hash_equal:(Maybe uint256) + = ConfigProposal; +cfg_proposal_status#ce expires:uint32 proposal:^ConfigProposal is_critical:Bool + voters:(HashmapE 16 True) remaining_weight:int64 validator_set_id:uint256 + rounds_remaining:uint8 wins:uint8 losses:uint8 = ConfigProposalStatus; wfmt_basic#1 vm_version:int32 vm_mode:uint64 = WorkchainFormat 1; wfmt_ext#0 min_addr_len:(## 12) max_addr_len:(## 12) addr_len_step:(## 12) @@ -580,17 +638,34 @@ wfmt_ext#0 min_addr_len:(## 12) max_addr_len:(## 12) addr_len_step:(## 12) workchain_type_id:(## 32) { workchain_type_id >= 1 } = WorkchainFormat 0; -workchain#a6 enabled_since:uint32 actual_min_split:(## 8) - min_split:(## 8) max_split:(## 8) { actual_min_split <= min_split } +wc_split_merge_timings#0 + split_merge_delay:uint32 split_merge_interval:uint32 + min_split_merge_interval:uint32 max_split_merge_delay:uint32 + = WcSplitMergeTimings; + //workchain#a5 enabled_since:uint32 min_split:(## 8) max_split:(## 8) // { min_split <= max_split } { max_split <= 60 } + +workchain#a6 enabled_since:uint32 actual_min_split:(## 8) + min_split:(## 8) max_split:(## 8) { actual_min_split <= min_split } basic:(## 1) active:Bool accept_msgs:Bool flags:(## 13) { flags = 0 } zerostate_root_hash:bits256 zerostate_file_hash:bits256 version:uint32 format:(WorkchainFormat basic) = WorkchainDescr; +workchain_v2#a7 enabled_since:uint32 actual_min_split:(## 8) + min_split:(## 8) max_split:(## 8) { actual_min_split <= min_split } + basic:(## 1) active:Bool accept_msgs:Bool flags:(## 13) { flags = 0 } + zerostate_root_hash:bits256 zerostate_file_hash:bits256 + version:uint32 format:(WorkchainFormat basic) + split_merge_timings:WcSplitMergeTimings + = WorkchainDescr; + _ workchains:(HashmapE 32 WorkchainDescr) = ConfigParam 12; +complaint_prices#1a deposit:Grams bit_price:Grams cell_price:Grams = ComplaintPricing; +_ ComplaintPricing = ConfigParam 13; + block_grams_created#6b masterchain_block_fee:Grams basechain_block_fee:Grams = BlockCreateFees; _ BlockCreateFees = ConfigParam 14; @@ -611,6 +686,8 @@ _#cc utime_since:uint32 bit_price_ps:uint64 cell_price_ps:uint64 mc_bit_price_ps:uint64 mc_cell_price_ps:uint64 = StoragePrices; _ (Hashmap 32 StoragePrices) = ConfigParam 18; +_ global_id:int32 = ConfigParam 19; + gas_prices#dd gas_price:uint64 gas_limit:uint64 gas_credit:uint64 block_gas_limit:uint64 freeze_due_limit:uint64 delete_due_limit:uint64 = GasLimitsPrices; @@ -646,11 +723,36 @@ config_fwd_prices#_ MsgForwardPrices = ConfigParam 25; catchain_config#c1 mc_catchain_lifetime:uint32 shard_catchain_lifetime:uint32 shard_validators_lifetime:uint32 shard_validators_num:uint32 = CatchainConfig; + +catchain_config_new#c2 flags:(## 7) { flags = 0 } shuffle_mc_validators:Bool + mc_catchain_lifetime:uint32 shard_catchain_lifetime:uint32 + shard_validators_lifetime:uint32 shard_validators_num:uint32 = CatchainConfig; + consensus_config#d6 round_candidates:# { round_candidates >= 1 } next_candidate_delay_ms:uint32 consensus_timeout_ms:uint32 fast_attempts:uint32 attempt_duration:uint32 catchain_max_deps:uint32 max_block_bytes:uint32 max_collated_bytes:uint32 = ConsensusConfig; +consensus_config_new#d7 flags:(## 7) { flags = 0 } new_catchain_ids:Bool + round_candidates:(## 8) { round_candidates >= 1 } + next_candidate_delay_ms:uint32 consensus_timeout_ms:uint32 + fast_attempts:uint32 attempt_duration:uint32 catchain_max_deps:uint32 + max_block_bytes:uint32 max_collated_bytes:uint32 = ConsensusConfig; + +consensus_config_v3#d8 flags:(## 7) { flags = 0 } new_catchain_ids:Bool + round_candidates:(## 8) { round_candidates >= 1 } + next_candidate_delay_ms:uint32 consensus_timeout_ms:uint32 + fast_attempts:uint32 attempt_duration:uint32 catchain_max_deps:uint32 + max_block_bytes:uint32 max_collated_bytes:uint32 + proto_version:uint16 = ConsensusConfig; + +consensus_config_v4#d9 flags:(## 7) { flags = 0 } new_catchain_ids:Bool + round_candidates:(## 8) { round_candidates >= 1 } + next_candidate_delay_ms:uint32 consensus_timeout_ms:uint32 + fast_attempts:uint32 attempt_duration:uint32 catchain_max_deps:uint32 + max_block_bytes:uint32 max_collated_bytes:uint32 + proto_version:uint16 catchain_max_blocks_coeff:uint32 = ConsensusConfig; + _ CatchainConfig = ConfigParam 28; _ ConsensusConfig = ConfigParam 29; @@ -666,6 +768,50 @@ validator_temp_key#3 adnl_addr:bits256 temp_public_key:SigPubKey seqno:# valid_u signed_temp_key#4 key:^ValidatorTempKey signature:CryptoSignature = ValidatorSignedTempKey; _ (HashmapE 256 ValidatorSignedTempKey) = ConfigParam 39; +misbehaviour_punishment_config_v1#01 + default_flat_fine:Grams default_proportional_fine:uint32 + severity_flat_mult:uint16 severity_proportional_mult:uint16 + unpunishable_interval:uint16 + long_interval:uint16 long_flat_mult:uint16 long_proportional_mult:uint16 + medium_interval:uint16 medium_flat_mult:uint16 medium_proportional_mult:uint16 + = MisbehaviourPunishmentConfig; +_ MisbehaviourPunishmentConfig = ConfigParam 40; + +size_limits_config#01 max_msg_bits:uint32 max_msg_cells:uint32 max_library_cells:uint32 max_vm_data_depth:uint16 + max_ext_msg_size:uint32 max_ext_msg_depth:uint16 = SizeLimitsConfig; +size_limits_config_v2#02 max_msg_bits:uint32 max_msg_cells:uint32 max_library_cells:uint32 max_vm_data_depth:uint16 + max_ext_msg_size:uint32 max_ext_msg_depth:uint16 max_acc_state_cells:uint32 max_acc_state_bits:uint32 + max_acc_public_libraries:uint32 = SizeLimitsConfig; +_ SizeLimitsConfig = ConfigParam 43; + +// key is [ wc:int32 addr:uint256 ] +suspended_address_list#00 addresses:(HashmapE 288 Unit) suspended_until:uint32 = SuspendedAddressList; +_ SuspendedAddressList = ConfigParam 44; + +precompiled_smc#b0 gas_usage:uint64 = PrecompiledSmc; +precompiled_contracts_config#c0 list:(HashmapE 256 PrecompiledSmc) = PrecompiledContractsConfig; +_ PrecompiledContractsConfig = ConfigParam 45; + +oracle_bridge_params#_ bridge_address:bits256 oracle_mutlisig_address:bits256 oracles:(HashmapE 256 uint256) external_chain_address:bits256 = OracleBridgeParams; +_ OracleBridgeParams = ConfigParam 71; // Ethereum bridge +_ OracleBridgeParams = ConfigParam 72; // Binance Smart Chain bridge +_ OracleBridgeParams = ConfigParam 73; // Polygon bridge + +// Note that chains in which bridge, minter and jetton-wallet operate are fixated +jetton_bridge_prices#_ bridge_burn_fee:Coins bridge_mint_fee:Coins + wallet_min_tons_for_storage:Coins + wallet_gas_consumption:Coins + minter_min_tons_for_storage:Coins + discover_gas_consumption:Coins = JettonBridgePrices; + +jetton_bridge_params_v0#00 bridge_address:bits256 oracles_address:bits256 oracles:(HashmapE 256 uint256) state_flags:uint8 burn_bridge_fee:Coins = JettonBridgeParams; +jetton_bridge_params_v1#01 bridge_address:bits256 oracles_address:bits256 oracles:(HashmapE 256 uint256) state_flags:uint8 prices:^JettonBridgePrices external_chain_address:bits256 = JettonBridgeParams; + +_ JettonBridgeParams = ConfigParam 79; // ETH->TON token bridge +_ JettonBridgeParams = ConfigParam 81; // BNB->TON token bridge +_ JettonBridgeParams = ConfigParam 82; // Polygon->TON token bridge + + // // PROOFS // @@ -684,6 +830,16 @@ top_block_descr#d5 proof_for:BlockIdExt signatures:(Maybe ^BlockSignatures) // top_block_descr_set#4ac789f3 collection:(HashmapE 96 ^TopBlockDescr) = TopBlockDescrSet; +// +// VALIDATOR MISBEHAVIOR COMPLAINTS +// +prod_info#34 utime:uint32 mc_blk_ref:ExtBlkRef state_proof:^(MERKLE_PROOF Block) + prod_proof:^(MERKLE_PROOF ShardState) = ProducerInfo; +no_blk_gen from_utime:uint32 prod_info:^ProducerInfo = ComplaintDescr; +no_blk_gen_diff prod_info_old:^ProducerInfo prod_info_new:^ProducerInfo = ComplaintDescr; +validator_complaint#bc validator_pubkey:bits256 description:^ComplaintDescr created_at:uint32 severity:uint8 reward_addr:uint256 paid:Grams suggested_fine:Grams suggested_fine_part:uint32 = ValidatorComplaint; +complaint_status#2d complaint:^ValidatorComplaint voters:(HashmapE 16 True) vset_id:uint256 weight_remaining:int64 = ValidatorComplaintStatus; + // // TVM REFLECTION // @@ -731,7 +887,7 @@ vmc_pushint$1111 value:int32 next:^VmCont = VmCont; // // DNS RECORDS // -_ (HashmapE 16 ^DNSRecord) = DNS_RecordSet; +_ (HashmapE 256 ^DNSRecord) = DNS_RecordSet; chunk_ref$_ {n:#} ref:^(TextChunks (n + 1)) = TextChunkRef (n + 1); chunk_ref_empty$_ = TextChunkRef 0; @@ -757,3 +913,30 @@ cap_method_pubkey#71f4 = SmcCapability; cap_is_wallet#2177 = SmcCapability; cap_name#ff name:Text = SmcCapability; +dns_storage_address#7473 bag_id:bits256 = DNSRecord; + +// +// PAYMENT CHANNELS +// + +chan_config$_ init_timeout:uint32 close_timeout:uint32 a_key:bits256 b_key:bits256 + a_addr:^MsgAddressInt b_addr:^MsgAddressInt channel_id:uint64 min_A_extra:Grams = ChanConfig; + +chan_state_init$000 signed_A:Bool signed_B:Bool min_A:Grams min_B:Grams expire_at:uint32 A:Grams B:Grams = ChanState; +chan_state_close$001 signed_A:Bool signed_B:Bool promise_A:Grams promise_B:Grams expire_at:uint32 A:Grams B:Grams = ChanState; +chan_state_payout$010 A:Grams B:Grams = ChanState; + +chan_promise$_ channel_id:uint64 promise_A:Grams promise_B:Grams = ChanPromise; +chan_signed_promise#_ sig:(Maybe ^bits512) promise:ChanPromise = ChanSignedPromise; + +chan_msg_init#27317822 inc_A:Grams inc_B:Grams min_A:Grams min_B:Grams channel_id:uint64 = ChanMsg; +chan_msg_close#f28ae183 extra_A:Grams extra_B:Grams promise:ChanSignedPromise = ChanMsg; +chan_msg_timeout#43278a28 = ChanMsg; +chan_msg_payout#37fe7810 = ChanMsg; + +chan_signed_msg$_ sig_A:(Maybe ^bits512) sig_B:(Maybe ^bits512) msg:ChanMsg = ChanSignedMsg; + +chan_op_cmd#912838d1 msg:ChanSignedMsg = ChanOp; + + +chan_data$_ config:^ChanConfig state:^ChanState = ChanData; diff --git a/crypto/block/check-proof.cpp b/crypto/block/check-proof.cpp index 7db7a97c..431a03fe 100644 --- a/crypto/block/check-proof.cpp +++ b/crypto/block/check-proof.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "check-proof.h" #include "block/block.h" @@ -25,13 +25,13 @@ #include "ton/ton-shard.h" #include "vm/cells/MerkleProof.h" -#include "openssl/digest.h" +#include "openssl/digest.hpp" #include "Ed25519.h" namespace block { using namespace std::literals::string_literals; -td::Status check_block_header_proof(td::Ref root, ton::BlockIdExt blkid, ton::Bits256* store_shard_hash_to, +td::Status check_block_header_proof(td::Ref root, ton::BlockIdExt blkid, ton::Bits256* store_state_hash_to, bool check_state_hash, td::uint32* save_utime, ton::LogicalTime* save_lt) { ton::RootHash vhash{root->get_hash().bits()}; if (vhash != blkid.root_hash) { @@ -53,7 +53,7 @@ td::Status check_block_header_proof(td::Ref root, ton::BlockIdExt blki if (save_lt) { *save_lt = info.end_lt; } - if (store_shard_hash_to) { + if (store_state_hash_to) { vm::CellSlice upd_cs{vm::NoVmSpec(), blk.state_update}; if (!(upd_cs.is_special() && upd_cs.prefetch_long(8) == 4 // merkle update && upd_cs.size_ext() == 0x20228)) { @@ -61,11 +61,11 @@ td::Status check_block_header_proof(td::Ref root, ton::BlockIdExt blki } auto upd_hash = upd_cs.prefetch_ref(1)->get_hash(0); if (!check_state_hash) { - *store_shard_hash_to = upd_hash.bits(); - } else if (store_shard_hash_to->compare(upd_hash.bits())) { + *store_state_hash_to = upd_hash.bits(); + } else if (store_state_hash_to->compare(upd_hash.bits())) { return td::Status::Error(PSTRING() << "state hash mismatch in block header of " << blkid.to_str() << " : header declares " << upd_hash.bits().to_hex(256) << " expected " - << store_shard_hash_to->to_hex()); + << store_state_hash_to->to_hex()); } } return td::Status::OK(); @@ -219,7 +219,17 @@ td::Status check_account_proof(td::Slice proof, ton::BlockIdExt shard_blk, const } td::Result AccountState::validate(ton::BlockIdExt ref_blk, block::StdAddress addr) const { - TRY_RESULT_PREFIX(root, vm::std_boc_deserialize(state.as_slice(), true), "cannot deserialize account state"); + TRY_RESULT_PREFIX(true_root, vm::std_boc_deserialize(state.as_slice(), true), "cannot deserialize account state"); + Ref root; + + if (is_virtualized && true_root.not_null()) { + root = vm::MerkleProof::virtualize(true_root, 1); + if (root.is_null()) { + return td::Status::Error("account state proof is invalid"); + } + } else { + root = true_root; + } if (blk != ref_blk && ref_blk.id.seqno != ~0U) { return td::Status::Error(PSLICE() << "obtained getAccountState() for a different reference block " << blk.to_str() @@ -241,6 +251,7 @@ td::Result AccountState::validate(ton::BlockIdExt ref_blk, b TRY_STATUS(block::check_account_proof(proof.as_slice(), shard_blk, addr, root, &res.last_trans_lt, &res.last_trans_hash, &res.gen_utime, &res.gen_lt)); res.root = std::move(root); + res.true_root = std::move(true_root); return res; } @@ -304,6 +315,113 @@ td::Result TransactionList::validate() const { return std::move(res); } +td::Result BlockTransaction::validate(bool check_proof) const { + if (root.is_null()) { + return td::Status::Error("transactions are expected to be non-empty"); + } + if (check_proof && proof->get_hash().bits().compare(root->get_hash().bits(), 256)) { + return td::Status::Error(PSLICE() << "transaction hash mismatch: Merkle proof expects " + << proof->get_hash().bits().to_hex(256) + << " but received data has " << root->get_hash().bits().to_hex(256)); + } + block::gen::Transaction::Record trans; + if (!tlb::unpack_cell(root, trans)) { + return td::Status::Error("cannot unpack transaction cell"); + } + Info res; + res.blkid = blkid; + res.now = trans.now; + res.lt = trans.lt; + res.hash = root->get_hash().bits(); + res.transaction = root; + return std::move(res); +} + +td::Result BlockTransactionList::validate(bool check_proof) const { + constexpr int max_answer_transactions = 256; + + TRY_RESULT_PREFIX(list, vm::std_boc_deserialize_multi(std::move(transactions_boc)), "cannot deserialize transactions boc: "); + std::vector> tx_proofs(list.size()); + + if (check_proof) { + try { + TRY_RESULT(proof_cell, vm::std_boc_deserialize(std::move(proof_boc))); + auto virt_root = vm::MerkleProof::virtualize(proof_cell, 1); + + if (blkid.root_hash != virt_root->get_hash().bits()) { + return td::Status::Error("Invalid block proof root hash"); + } + block::gen::Block::Record blk; + block::gen::BlockExtra::Record extra; + if (!(tlb::unpack_cell(virt_root, blk) && tlb::unpack_cell(std::move(blk.extra), extra))) { + return td::Status::Error("Error unpacking proof cell"); + } + vm::AugmentedDictionary acc_dict{vm::load_cell_slice_ref(extra.account_blocks), 256, + block::tlb::aug_ShardAccountBlocks}; + + bool eof = false; + ton::LogicalTime reverse = reverse_mode ? ~0ULL : 0; + ton::LogicalTime trans_lt = static_cast(start_lt); + td::Bits256 cur_addr = start_addr; + bool allow_same = true; + int count = 0; + while (!eof && count < req_count && count < max_answer_transactions) { + auto value = acc_dict.extract_value( + acc_dict.vm::DictionaryFixed::lookup_nearest_key(cur_addr.bits(), 256, !reverse, allow_same)); + if (value.is_null()) { + eof = true; + break; + } + allow_same = false; + if (cur_addr != start_addr) { + trans_lt = reverse; + } + + block::gen::AccountBlock::Record acc_blk; + if (!tlb::csr_unpack(std::move(value), acc_blk) || acc_blk.account_addr != cur_addr) { + return td::Status::Error("Error unpacking proof account block"); + } + vm::AugmentedDictionary trans_dict{vm::DictNonEmpty(), std::move(acc_blk.transactions), 64, + block::tlb::aug_AccountTransactions}; + td::BitArray<64> cur_trans{(long long)trans_lt}; + while (count < req_count && count < max_answer_transactions) { + auto tvalue = trans_dict.extract_value_ref( + trans_dict.vm::DictionaryFixed::lookup_nearest_key(cur_trans.bits(), 64, !reverse)); + if (tvalue.is_null()) { + trans_lt = reverse; + break; + } + if (static_cast(count) < tx_proofs.size()) { + tx_proofs[count] = std::move(tvalue); + } + count++; + } + } + if (static_cast(count) != list.size()) { + return td::Status::Error(PSLICE() << "Txs count mismatch in proof (" << count << ") and response (" << list.size() << ")"); + } + } catch (vm::VmError& err) { + return err.as_status("Couldn't verify proof: "); + } catch (vm::VmVirtError& err) { + return err.as_status("Couldn't verify proof: "); + } catch (...) { + return td::Status::Error("Unknown exception raised while verifying proof"); + } + } + + Info res; + for (int i = 0; i < static_cast(list.size()); i++) { + auto& root = list[i]; + BlockTransaction transaction; + transaction.root = root; + transaction.blkid = blkid; + transaction.proof = tx_proofs[i]; + TRY_RESULT(info, transaction.validate(check_proof)); + res.transactions.push_back(std::move(info)); + } + return std::move(res); +} + td::Status BlockProofLink::validate(td::uint32* save_utime) const { if (save_utime) { *save_utime = 0; @@ -351,7 +469,7 @@ td::Status BlockProofLink::validate(td::uint32* save_utime) const { if (to.seqno()) { TRY_STATUS(check_block_header(vd_root, to)); if (!(tlb::unpack_cell(vd_root, blk) && tlb::unpack_cell(blk.info, info))) { - return td::Status::Error("cannot unpack header for block "s + from.to_str()); + return td::Status::Error("cannot unpack header for block "s + to.to_str()); } if (info.key_block != is_key) { return td::Status::Error(PSTRING() << "incorrect is_key_block value " << is_key << " for destination block " diff --git a/crypto/block/check-proof.h b/crypto/block/check-proof.h index 173ae9e4..497a4eba 100644 --- a/crypto/block/check-proof.h +++ b/crypto/block/check-proof.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -25,7 +25,7 @@ namespace block { using td::Ref; td::Status check_block_header_proof(td::Ref root, ton::BlockIdExt blkid, - ton::Bits256* store_shard_hash_to = nullptr, bool check_state_hash = false, + ton::Bits256* store_state_hash_to = nullptr, bool check_state_hash = false, td::uint32* save_utime = nullptr, ton::LogicalTime* save_lt = nullptr); td::Status check_shard_proof(ton::BlockIdExt blk, ton::BlockIdExt shard_blk, td::Slice shard_proof); td::Status check_account_proof(td::Slice proof, ton::BlockIdExt shard_blk, const block::StdAddress& addr, @@ -44,10 +44,11 @@ struct AccountState { td::BufferSlice shard_proof; td::BufferSlice proof; td::BufferSlice state; + bool is_virtualized{false}; struct Info { - td::Ref root; - ton::LogicalTime last_trans_lt = 0; + td::Ref root, true_root; + ton::LogicalTime last_trans_lt{0}; ton::Bits256 last_trans_hash; ton::LogicalTime gen_lt{0}; td::uint32 gen_utime{0}; @@ -87,4 +88,36 @@ struct TransactionList { td::Result validate() const; }; +struct BlockTransaction { + ton::BlockIdExt blkid; + td::Ref root; + td::Ref proof; + + struct Info { + ton::BlockIdExt blkid; + td::uint32 now; + ton::LogicalTime lt; + ton::Bits256 hash; + td::Ref transaction; + }; + td::Result validate(bool check_proof) const; +}; + +struct BlockTransactionList { + ton::BlockIdExt blkid; + td::BufferSlice transactions_boc; + td::BufferSlice proof_boc; + ton::LogicalTime start_lt; + td::Bits256 start_addr; + bool reverse_mode; + int req_count; + + struct Info { + ton::BlockIdExt blkid; + std::vector transactions; + }; + + td::Result validate(bool check_proof) const; +}; + } // namespace block diff --git a/crypto/block/create-state.cpp b/crypto/block/create-state.cpp index 02896e7b..183da0a7 100644 --- a/crypto/block/create-state.cpp +++ b/crypto/block/create-state.cpp @@ -1,4 +1,4 @@ -/* +/* This file is part of TON Blockchain source code. TON Blockchain is free software; you can redistribute it and/or @@ -14,16 +14,16 @@ You should have received a copy of the GNU General Public License along with TON Blockchain. If not, see . - In addition, as a special exception, the copyright holders give permission - to link the code of portions of this program with the OpenSSL library. - You must obey the GNU General Public License in all respects for all - of the code used other than OpenSSL. If you modify file(s) with this - exception, you may extend this exception to your version of the file(s), - but you are not obligated to do so. If you do not wish to do so, delete this - exception statement from your version. If you delete this exception statement + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include @@ -47,6 +47,7 @@ #include "fift/Fift.h" #include "fift/Dictionary.h" #include "fift/SourceLookup.h" +#include "fift/IntCtx.h" #include "fift/words.h" #include "td/utils/logging.h" @@ -59,6 +60,12 @@ #include "block-parse.h" #include "block-auto.h" #include "mc-config.h" +#include "git.h" + +#if defined(_INTERNAL_COMPILE) || defined(_TONLIB_COMPILE) +#define WITH_TONLIB +#include "tonlib/keys/Mnemonic.h" +#endif #define PDO(__op) \ if (!(__op)) { \ @@ -266,6 +273,10 @@ td::RefInt256 create_smartcontract(td::RefInt256 smc_addr, Ref code, R PDO(sgn(balance) >= 0); THRERR("balance cannot be negative"); if (!mode) { + if (verbosity > 2) { + std::cerr << "StateInit used for computing address: "; + block::gen::t_StateInit.print_ref(std::cerr, state_init); + } return smc_addr; // compute address only } auto it = smart_contracts.find(addr); @@ -298,7 +309,7 @@ td::RefInt256 create_smartcontract(td::RefInt256 smc_addr, Ref code, R THRERR("cannot create smart-contract AccountStorage"); Ref storage = cb.finalize(); vm::CellStorageStat stats; - PDO(stats.compute_used_storage(Ref(storage))); + PDO(stats.compute_used_storage(Ref(storage)).is_ok()); if (verbosity > 2) { std::cerr << "storage is:\n"; vm::load_cell_slice(storage).print_rec(std::cerr); @@ -594,6 +605,18 @@ void interpret_set_config_param(vm::Stack& stack) { } } +void interpret_check_config_param(vm::Stack& stack) { + int x = stack.pop_smallint_range(0x7fffffff, 0x80000000); + Ref value = stack.pop_cell(); + if (verbosity > 2 && x >= 0) { + std::cerr << "checking validity as configuration parameter #" << x << " of "; + // vm::load_cell_slice(value).print_rec(std::cerr); + block::gen::ConfigParam{x}.print_ref(std::cerr, value); + std::cerr << std::endl; + } + stack.push_bool(x < 0 || block::gen::ConfigParam{x}.validate_ref(value)); +} + void interpret_is_shard_state(vm::Stack& stack) { Ref cell = stack.pop_cell(); if (verbosity > 4) { @@ -616,7 +639,49 @@ void interpret_is_workchain_descr(vm::Stack& stack) { stack.push_bool(block::gen::t_WorkchainDescr.validate_ref(std::move(cell))); } +void interpret_add_extra_currencies(vm::Stack& stack) { + Ref y = stack.pop_maybe_cell(), x = stack.pop_maybe_cell(), res; + bool ok = block::add_extra_currency(std::move(x), std::move(y), res); + if (ok) { + stack.push_maybe_cell(std::move(res)); + } + stack.push_bool(ok); +} + +void interpret_sub_extra_currencies(vm::Stack& stack) { + Ref y = stack.pop_maybe_cell(), x = stack.pop_maybe_cell(), res; + bool ok = block::sub_extra_currency(std::move(x), std::move(y), res); + if (ok) { + stack.push_maybe_cell(std::move(res)); + } + stack.push_bool(ok); +} + +void interpret_allocated_balance(vm::Stack& stack) { + stack.push_int(total_smc_balance); +} + +#ifdef WITH_TONLIB +void interpret_mnemonic_to_privkey(vm::Stack& stack, int mode) { + td::SecureString str{td::Slice{stack.pop_string()}}; + auto res = tonlib::Mnemonic::create(std::move(str), td::SecureString()); + if (res.is_error()) { + throw fift::IntError{res.move_as_error().to_string()}; + } + auto privkey = res.move_as_ok().to_private_key(); + td::SecureString key; + if (mode & 1) { + auto pub = privkey.get_public_key(); + key = pub.move_as_ok().as_octet_string(); + } else { + key = privkey.as_octet_string(); + } + stack.push_bytes(key.as_slice()); +} +#endif + void init_words_custom(fift::Dictionary& d) { + using namespace std::placeholders; d.def_stack_word("verb@ ", interpret_get_verbosity); d.def_stack_word("verb! ", interpret_set_verbosity); d.def_stack_word("wcid@ ", interpret_get_workchain); @@ -625,12 +690,20 @@ void init_words_custom(fift::Dictionary& d) { d.def_stack_word("globalid! ", interpret_set_global_id); d.def_stack_word("config@ ", interpret_get_config_param); d.def_stack_word("config! ", interpret_set_config_param); + d.def_stack_word("config-valid? ", interpret_check_config_param); d.def_stack_word("(configdict) ", interpret_get_config_dict); d.def_stack_word("register_smc ", interpret_register_smartcontract); d.def_stack_word("set_config_smc ", interpret_set_config_smartcontract); d.def_stack_word("create_state ", interpret_create_state); d.def_stack_word("isShardState? ", interpret_is_shard_state); d.def_stack_word("isWorkchainDescr? ", interpret_is_workchain_descr); + d.def_stack_word("CC+? ", interpret_add_extra_currencies); + d.def_stack_word("CC-? ", interpret_sub_extra_currencies); + d.def_stack_word("allocated-balance ", interpret_allocated_balance); +#ifdef WITH_TONLIB + d.def_stack_word("mnemo>priv ", std::bind(interpret_mnemonic_to_privkey, _1, 0)); + d.def_stack_word("mnemo>pub ", std::bind(interpret_mnemonic_to_privkey, _1, 1)); +#endif } tlb::TypenameLookup tlb_dict; @@ -697,14 +770,19 @@ void interpret_tlb_skip(vm::Stack& stack) { void interpret_tlb_validate_skip(vm::Stack& stack) { auto tp = pop_tlb_type(stack); auto cs = stack.pop_cellslice(); - bool ok = (*tp)->validate_skip(cs.write()); + bool ok = (*tp)->validate_skip_upto(1048576, cs.write()); if (ok) { stack.push(std::move(cs)); } stack.push_bool(ok); } +void interpret_tlb_type_const(vm::Stack& stack, const tlb::TLB* ptr) { + stack.push_make_object(ptr); +} + void init_words_tlb(fift::Dictionary& d) { + using namespace std::placeholders; tlb_dict.register_types(block::gen::register_simple_types); d.def_stack_word("tlb-type-lookup ", interpret_tlb_type_lookup); d.def_stack_word("tlb-type-name ", interpret_tlb_type_name); @@ -713,6 +791,8 @@ void init_words_tlb(fift::Dictionary& d) { d.def_stack_word("(tlb-dump-str?) ", interpret_tlb_dump_to_str); d.def_stack_word("tlb-skip ", interpret_tlb_skip); d.def_stack_word("tlb-validate-skip ", interpret_tlb_validate_skip); + d.def_stack_word("ExtraCurrencyCollection", + std::bind(interpret_tlb_type_const, _1, &block::tlb::t_ExtraCurrencyCollection)); } void usage(const char* progname) { @@ -726,7 +806,8 @@ void usage(const char* progname) { "\t-I\tSets colon-separated library source include path. If not indicated, " "$FIFTPATH is used instead.\n" "\t-L\tPre-loads a library source file\n" - "\t-v\tSet verbosity level\n"; + "\t-v\tSet verbosity level\n" + "\t-V\tShow create-state build information\n"; std::exit(2); } @@ -764,7 +845,7 @@ int main(int argc, char* const argv[]) { int i; int new_verbosity_level = VERBOSITY_NAME(INFO); - while (!script_mode && (i = getopt(argc, argv, "hinsI:L:v:")) != -1) { + while (!script_mode && (i = getopt(argc, argv, "hinsI:L:v:V")) != -1) { switch (i) { case 'i': interactive = true; @@ -786,6 +867,11 @@ int main(int argc, char* const argv[]) { case 'v': new_verbosity_level = VERBOSITY_NAME(FATAL) + (verbosity = td::to_integer(td::Slice(optarg))); break; + case 'V': + std::cout << "create-state build information: [ Commit: " << GitMetadata::CommitSHA1() + << ", Date: " << GitMetadata::CommitDate() << "]\n"; + std::exit(0); + break; case 'h': default: usage(argv[0]); diff --git a/crypto/block/dump-block.cpp b/crypto/block/dump-block.cpp index 25eb0eed..e7a19677 100644 --- a/crypto/block/dump-block.cpp +++ b/crypto/block/dump-block.cpp @@ -1,4 +1,4 @@ -/* +/* This file is part of TON Blockchain source code. TON Blockchain is free software; you can redistribute it and/or @@ -14,16 +14,16 @@ You should have received a copy of the GNU General Public License along with TON Blockchain. If not, see . - In addition, as a special exception, the copyright holders give permission - to link the code of portions of this program with the OpenSSL library. - You must obey the GNU General Public License in all respects for all - of the code used other than OpenSSL. If you modify file(s) with this - exception, you may extend this exception to your version of the file(s), - but you are not obligated to do so. If you do not wish to do so, delete this - exception statement from your version. If you delete this exception statement + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "block/block.h" #include "vm/boc.h" @@ -31,10 +31,13 @@ #include "block-db.h" #include "block-auto.h" #include "block-parse.h" +#include "mc-config.h" #include "vm/cp0.h" #include +#include "git.h" using td::Ref; +using namespace std::literals::string_literals; int verbosity; @@ -46,6 +49,12 @@ struct IntError { } }; +void throw_err(td::Status err) { + if (err.is_error()) { + throw IntError{err.to_string()}; + } +} + td::Ref load_boc(std::string filename) { std::cerr << "loading bag-of-cell file " << filename << std::endl; auto bytes_res = block::load_binary_file(filename); @@ -63,6 +72,8 @@ td::Ref load_boc(std::string filename) { return boc.get_root_cell(); } +std::vector> loaded_boc; + void test1() { block::ShardId id{ton::masterchainId}, id2{ton::basechainId, 0x11efULL << 48}; std::cout << '[' << id << "][" << id2 << ']' << std::endl; @@ -98,7 +109,7 @@ void test1() { block::tlb::ShardIdent::Record shard_id; for (int i = 0; i < 3; i++) { - std::cout << "ShardIdent.validate() = " << block::tlb::t_ShardIdent.validate(csl) << std::endl; + std::cout << "ShardIdent.validate() = " << block::tlb::t_ShardIdent.validate_upto(1024, csl) << std::endl; csl.print_rec(std::cerr); csl.dump(std::cerr, 7); std::cout << "ShardIdent.unpack() = " << block::tlb::t_ShardIdent.unpack(csl, shard_id) << std::endl; @@ -107,9 +118,9 @@ void test1() { << " shard_prefix:" << shard_id.shard_prefix << std::endl; } } - std::cout << "ShardIdent.skip_validate() = " << block::tlb::t_ShardIdent.validate_skip(csl) << std::endl; - std::cout << "ShardIdent.skip_validate() = " << block::tlb::t_ShardIdent.validate_skip(csl) << std::endl; - std::cout << "ShardIdent.skip_validate() = " << block::tlb::t_ShardIdent.validate_skip(csl) << std::endl; + std::cout << "ShardIdent.skip_validate() = " << block::tlb::t_ShardIdent.validate_skip_upto(1024, csl) << std::endl; + std::cout << "ShardIdent.skip_validate() = " << block::tlb::t_ShardIdent.validate_skip_upto(1024, csl) << std::endl; + std::cout << "ShardIdent.skip_validate() = " << block::tlb::t_ShardIdent.validate_skip_upto(1024, csl) << std::endl; using namespace td::literals; std::cout << "Grams.store_intval(239) = " << block::tlb::t_Grams.store_integer_value(cb, "239"_i256) << std::endl; std::cout << "Grams.store_intval(17239) = " << block::tlb::t_Grams.store_integer_value(cb, "17239"_i256) << std::endl; @@ -120,13 +131,13 @@ void test1() { std::cout << "Grams.store_intval(666) = " << block::tlb::t_Grams.store_integer_value(cb, "666"_i256) << std::endl; std::cout << cb << std::endl; cs2 = td::Ref{true, cb.finalize()}; - std::cout << "Grams.validate(cs) = " << block::tlb::t_Grams.validate(*cs) << std::endl; - std::cout << "Grams.validate(cs2) = " << block::tlb::t_Grams.validate(*cs2) << std::endl; + std::cout << "Grams.validate(cs) = " << block::tlb::t_Grams.validate_upto(1024, *cs) << std::endl; + std::cout << "Grams.validate(cs2) = " << block::tlb::t_Grams.validate_upto(1024, *cs2) << std::endl; // block::gen::SplitMergeInfo::Record data; block::gen::Grams::Record data2; - std::cout << "block::gen::Grams.validate(cs) = " << block::gen::t_Grams.validate(*cs) << std::endl; - std::cout << "block::gen::Grams.validate(cs2) = " << block::gen::t_Grams.validate(*cs2) << std::endl; + std::cout << "block::gen::Grams.validate(cs) = " << block::gen::t_Grams.validate_upto(1024, *cs) << std::endl; + std::cout << "block::gen::Grams.validate(cs2) = " << block::gen::t_Grams.validate_upto(1024, *cs2) << std::endl; std::cout << "[cs = " << cs << "]" << std::endl; bool ok = tlb::csr_unpack_inexact(cs, data); std::cout << "block::gen::SplitMergeInfo.unpack(cs, data) = " << ok << std::endl; @@ -149,7 +160,7 @@ void test1() { std::cout << " cb = " << cb.finalize() << std::endl; } } - /* + /* { vm::CellBuilder cb; td::BitArray<256> hash; @@ -182,18 +193,62 @@ void test1() { } void test2(vm::CellSlice& cs) { - std::cout << "Bool.validate() = " << block::tlb::t_Bool.validate(cs) << std::endl; - std::cout << "UInt16.validate() = " << block::tlb::t_uint16.validate(cs) << std::endl; - std::cout << "HashmapE(32,UInt16).validate() = " << block::tlb::HashmapE(32, block::tlb::t_uint16).validate(cs) - << std::endl; + std::cout << "Bool.validate() = " << block::tlb::t_Bool.validate_upto(1024, cs) << std::endl; + std::cout << "UInt16.validate() = " << block::tlb::t_uint16.validate_upto(1024, cs) << std::endl; + std::cout << "HashmapE(32,UInt16).validate() = " + << block::tlb::HashmapE(32, block::tlb::t_uint16).validate_upto(1024, cs) << std::endl; std::cout << "block::gen::HashmapE(32,UInt16).validate() = " - << block::gen::HashmapE{32, block::gen::t_uint16}.validate(cs) << std::endl; + << block::gen::HashmapE{32, block::gen::t_uint16}.validate_upto(1024, cs) << std::endl; +} + +td::Status test_vset() { + if (loaded_boc.size() != 2) { + return td::Status::Error( + "must have exactly two boc files (with a masterchain Block and with ConfigParams) for vset compute test"); + } + std::cerr << "running test_vset()\n"; + TRY_RESULT(config, block::Config::unpack_config(vm::load_cell_slice_ref(loaded_boc[1]))); + std::cerr << "config unpacked\n"; + auto cv_root = config->get_config_param(34); + if (cv_root.is_null()) { + return td::Status::Error("no config parameter 34"); + } + std::cerr << "config param #34 obtained\n"; + TRY_RESULT(cur_validators, block::Config::unpack_validator_set(std::move(cv_root))); + // auto vconf = config->get_catchain_validators_config(); + std::cerr << "validator set unpacked\n"; + std::cerr << "unpacking ShardHashes\n"; + block::ShardConfig shards; + if (!shards.unpack(vm::load_cell_slice_ref(loaded_boc[0]))) { + return td::Status::Error("cannot unpack ShardConfig"); + } + std::cerr << "ShardHashes initialized\n"; + ton::ShardIdFull shard{0, 0x6e80000000000000}; + ton::CatchainSeqno cc_seqno = std::max(48763, 48763) + 1 + 1; + ton::UnixTime now = 1586169666; + cc_seqno = shards.get_shard_cc_seqno(shard); + std::cerr << "shard=" << shard.to_str() << " cc_seqno=" << cc_seqno << " time=" << now << std::endl; + if (cc_seqno == ~0U) { + return td::Status::Error("cannot compute cc_seqno for shard "s + shard.to_str()); + } + auto nodes = config->compute_validator_set(shard, *cur_validators, now, cc_seqno); + if (nodes.empty()) { + return td::Status::Error(PSTRING() << "compute_validator_set() for " << shard.to_str() << "," << now << "," + << cc_seqno << " returned empty list"); + } + for (auto& x : nodes) { + std::cout << "weight=" << x.weight << " key=" << x.key.as_bits256().to_hex() << " addr=" << x.addr.to_hex() + << std::endl; + } + // ... + return td::Status::OK(); } void usage() { std::cout << "usage: dump-block [-t][-S][]\n\tor dump-block -h\n\tDumps specified blockchain " "block or state " - "from , or runs some tests\n\t-S\tDump a blockchain state instead of a block\n"; + "from , or runs some tests\n\t-S\tDump a blockchain state instead of a block\n" + "\t-V\tShow fift build information\n"; std::exit(2); } @@ -202,8 +257,11 @@ int main(int argc, char* const argv[]) { int new_verbosity_level = VERBOSITY_NAME(INFO); const char* tname = nullptr; const tlb::TLB* type = &block::gen::t_Block; + bool vset_compute_test = false; + bool store_loaded = false; + int dump = 3; auto zerostate = std::make_unique(); - while ((i = getopt(argc, argv, "CSt:hv:")) != -1) { + while ((i = getopt(argc, argv, "CSt:hqv:V")) != -1) { switch (i) { case 'C': type = &block::gen::t_VmCont; @@ -218,6 +276,16 @@ int main(int argc, char* const argv[]) { case 'v': new_verbosity_level = VERBOSITY_NAME(FATAL) + (verbosity = td::to_integer(td::Slice(optarg))); break; + case 'q': + type = &block::gen::t_ShardHashes; + vset_compute_test = true; + store_loaded = true; + dump = 0; + break; + case 'V': + std::cout << "dump-block build information: [ Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate() << "]\n"; + std::exit(0); + break; case 'h': usage(); std::exit(2); @@ -228,17 +296,22 @@ int main(int argc, char* const argv[]) { } SET_VERBOSITY_LEVEL(new_verbosity_level); try { - bool done = false; + int loaded = 0; while (optind < argc) { auto boc = load_boc(argv[optind++]); if (boc.is_null()) { - std::cerr << "(invalid boc)" << std::endl; + std::cerr << "(invalid boc in file" << argv[optind - 1] << ")" << std::endl; std::exit(2); } else { - done = true; - vm::CellSlice cs{vm::NoVm(), boc}; - cs.print_rec(std::cout); - std::cout << std::endl; + if (store_loaded) { + loaded_boc.push_back(boc); + } + ++loaded; + if (dump & 1) { + vm::CellSlice cs{vm::NoVm(), boc}; + cs.print_rec(std::cout); + std::cout << std::endl; + } if (!type) { tlb::TypenameLookup dict(block::gen::register_simple_types); type = dict.lookup(tname); @@ -247,20 +320,31 @@ int main(int argc, char* const argv[]) { std::exit(3); } } - type->print_ref(std::cout, boc); - std::cout << std::endl; - bool ok = type->validate_ref(boc); + if (dump & 2) { + type->print_ref(std::cout, boc); + std::cout << std::endl; + } + bool ok = type->validate_ref(1048576, boc); std::cout << "(" << (ok ? "" : "in") << "valid " << *type << ")" << std::endl; + if (vset_compute_test) { + if (!ok || loaded > 2) { + std::cerr << "fatal: validity check failed\n"; + exit(3); + } + type = &block::gen::t_ConfigParams; + } } } - if (!done) { + if (vset_compute_test) { + throw_err(test_vset()); + } else if (!loaded) { test1(); } } catch (IntError& err) { - std::cerr << "caught internal error " << err.err_msg << std::endl; + std::cerr << "internal error: " << err.err_msg << std::endl; return 1; } catch (vm::VmError& err) { - std::cerr << "caught vm error " << err.get_msg() << std::endl; + std::cerr << "vm error: " << err.get_msg() << std::endl; return 1; } return 0; diff --git a/crypto/block/mc-config.cpp b/crypto/block/mc-config.cpp index c686e200..1dbfeaed 100644 --- a/crypto/block/mc-config.cpp +++ b/crypto/block/mc-config.cpp @@ -23,7 +23,7 @@ exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "mc-config.h" #include "block/block.h" @@ -35,13 +35,24 @@ #include "td/utils/uint128.h" #include "ton/ton-types.h" #include "ton/ton-shard.h" -#include "crypto/openssl/digest.h" +#include "openssl/digest.hpp" #include #include namespace block { +using namespace std::literals::string_literals; using td::Ref; +#define DBG(__n) dbg(__n)&& +#define DSTART int __dcnt = 0; +#define DEB DBG(++__dcnt) + +static inline bool dbg(int c) TD_UNUSED; +static inline bool dbg(int c) { + std::cerr << '[' << (char)('0' + c / 10) << (char)('0' + c % 10) << ']'; + return true; +} + Config::Config(Ref config_root, const td::Bits256& config_addr, int _mode) : mode(_mode), config_addr(config_addr), config_root(std::move(config_root)) { } @@ -299,16 +310,52 @@ td::Status Config::visit_validator_params() const { ton::ValidatorSessionConfig Config::get_consensus_config() const { auto cc = get_config_param(29); ton::ValidatorSessionConfig c; - block::gen::ConsensusConfig::Record r; - if (cc.not_null() && tlb::unpack_cell(cc, r)) { - c.catchain_idle_timeout = r.consensus_timeout_ms * 0.001; - c.catchain_max_deps = r.catchain_max_deps; + auto set_v1 = [&](auto& r) { + c.catchain_opts.idle_timeout = r.consensus_timeout_ms * 0.001; + c.catchain_opts.max_deps = r.catchain_max_deps; c.round_candidates = r.round_candidates; c.next_candidate_delay = r.next_candidate_delay_ms * 0.001; c.round_attempt_duration = r.attempt_duration; c.max_round_attempts = r.fast_attempts; c.max_block_size = r.max_block_bytes; c.max_collated_data_size = r.max_collated_bytes; + }; + auto set_v2 = [&] (auto& r) { + set_v1(r); + c.new_catchain_ids = r.new_catchain_ids; + }; + auto set_v3 = [&](auto& r) { + set_v2(r); + c.proto_version = r.proto_version; + }; + auto set_v4 = [&](auto& r) { + set_v3(r); + td::uint64 max_blocks_coeff = r.catchain_max_blocks_coeff; + if (max_blocks_coeff == 0) { + c.catchain_opts.max_block_height_coeff = 0; + } else { + auto catchain_config = get_catchain_validators_config(); + td::uint64 catchain_lifetime = std::max(catchain_config.mc_cc_lifetime, catchain_config.shard_cc_lifetime); + c.catchain_opts.max_block_height_coeff = catchain_lifetime * max_blocks_coeff; + } + }; + if (cc.not_null()) { + block::gen::ConsensusConfig::Record_consensus_config_v4 r4; + block::gen::ConsensusConfig::Record_consensus_config_v3 r3; + block::gen::ConsensusConfig::Record_consensus_config_new r2; + block::gen::ConsensusConfig::Record_consensus_config r1; + if (tlb::unpack_cell(cc, r4)) { + set_v4(r4); + } else if (tlb::unpack_cell(cc, r3)) { + set_v3(r3); + } else if (tlb::unpack_cell(cc, r2)) { + set_v2(r2); + } else if (tlb::unpack_cell(cc, r1)) { + set_v1(r1); + } + } + if (c.proto_version >= ton::ValidatorSessionConfig::BLOCK_HASH_COVERS_DATA_FROM_VERSION) { + c.catchain_opts.block_hash_covers_data = true; } return c; } @@ -335,6 +382,59 @@ std::unique_ptr ShardConfig::extract_shard_hashes_dict(Ref> Config::unpack_param_dict(vm::Dictionary& dict) { + try { + std::vector vect; + if (dict.check_for_each( + [&vect](Ref value, td::ConstBitPtr key, int key_len) { + bool ok = (key_len == 32 && value->empty_ext()); + if (ok) { + vect.push_back((int)key.get_int(32)); + } + return ok; + }, + true)) { + return std::move(vect); + } else { + return td::Status::Error("invalid parameter list dictionary"); + } + } catch (vm::VmError& vme) { + return td::Status::Error("error unpacking parameter list dictionary: "s + vme.get_msg()); + } +} + +td::Result> Config::unpack_param_dict(Ref dict_root) { + vm::Dictionary dict{std::move(dict_root), 32}; + return unpack_param_dict(dict); +} + +std::unique_ptr Config::get_param_dict(int idx) const { + return std::make_unique(get_config_param(idx), 32); +} + +td::Result> Config::unpack_param_list(int idx) const { + return unpack_param_dict(*get_param_dict(idx)); +} + +bool Config::all_mandatory_params_defined(int* bad_idx_ptr) const { + auto res = get_mandatory_param_list(); + if (res.is_error()) { + if (bad_idx_ptr) { + *bad_idx_ptr = -1; + } + return false; + } + for (int x : res.move_as_ok()) { + if (get_config_param(x).is_null()) { + if (bad_idx_ptr) { + *bad_idx_ptr = x; + } + return false; + } + } + return true; +} + std::unique_ptr ConfigInfo::create_accounts_dict() const { if (mode & needAccountsRoot) { return std::make_unique(accounts_root, 256, block::tlb::aug_ShardAccounts); @@ -521,12 +621,14 @@ td::Result> Config::get_storage_prices() const { } vm::Dictionary dict{std::move(cell), 32}; if (!dict.check_for_each([&res](Ref cs_ref, td::ConstBitPtr key, int n) -> bool { - block::gen::StoragePrices::Record data; - if (!tlb::csr_unpack(std::move(cs_ref), data) || data.utime_since != key.get_uint(n)) { + auto r_prices = do_get_one_storage_prices(*cs_ref); + if (r_prices.is_error()) { + return false; + } + res.push_back(r_prices.move_as_ok()); + if (res.back().valid_since != key.get_uint(n)) { return false; } - res.emplace_back(data.utime_since, data.bit_price_ps, data.cell_price_ps, data.mc_bit_price_ps, - data.mc_cell_price_ps); return true; })) { return td::Status::Error("invalid storage prices dictionary in configuration parameter 18"); @@ -534,16 +636,25 @@ td::Result> Config::get_storage_prices() const { return std::move(res); } -td::Result Config::do_get_gas_limits_prices(td::Ref cell, int id) { +td::Result Config::do_get_one_storage_prices(vm::CellSlice cs) { + block::gen::StoragePrices::Record data; + if (!tlb::unpack(cs, data)) { + return td::Status::Error("invalid storage prices dictionary in configuration parameter 18"); + } + return StoragePrices{data.utime_since, data.bit_price_ps, data.cell_price_ps, data.mc_bit_price_ps, + data.mc_cell_price_ps}; +} + +td::Result Config::do_get_gas_limits_prices(vm::CellSlice cs, int id) { GasLimitsPrices res; - auto cs = vm::load_cell_slice(cell); + vm::CellSlice cs0 = cs; block::gen::GasLimitsPrices::Record_gas_flat_pfx flat; if (tlb::unpack(cs, flat)) { cs = *flat.other; res.flat_gas_limit = flat.flat_gas_limit; res.flat_gas_price = flat.flat_gas_price; } else { - cs = vm::load_cell_slice(cell); + cs = cs0; } auto f = [&](const auto& r, td::uint64 spec_limit) { res.gas_limit = r.gas_limit; @@ -558,7 +669,7 @@ td::Result Config::do_get_gas_limits_prices(td::Ref c f(rec, rec.special_gas_limit); } else { block::gen::GasLimitsPrices::Record_gas_prices rec0; - if (tlb::unpack(cs, rec0)) { + if (tlb::unpack(cs = cs0, rec0)) { f(rec0, rec0.gas_limit); } else { return td::Status::Error(PSLICE() << "configuration parameter " << id @@ -567,13 +678,28 @@ td::Result Config::do_get_gas_limits_prices(td::Ref c } return res; } + +td::Result Config::get_dns_root_addr() const { + auto cell = get_config_param(4); + if (cell.is_null()) { + return td::Status::Error(PSLICE() << "configuration parameter " << 4 << " with dns root address is absent"); + } + auto cs = vm::load_cell_slice(std::move(cell)); + if (cs.size() != 0x100) { + return td::Status::Error(PSLICE() << "configuration parameter " << 4 << " with dns root address has wrong size"); + } + ton::StdSmcAddress res; + CHECK(cs.fetch_bits_to(res)); + return res; +} + td::Result Config::get_gas_limits_prices(bool is_masterchain) const { auto id = is_masterchain ? 20 : 21; auto cell = get_config_param(id); if (cell.is_null()) { return td::Status::Error(PSLICE() << "configuration parameter " << id << " with gas prices is absent"); } - return do_get_gas_limits_prices(std::move(cell), id); + return do_get_gas_limits_prices(vm::load_cell_slice(cell), id); } td::Result Config::get_msg_prices(bool is_masterchain) const { @@ -582,7 +708,10 @@ td::Result Config::get_msg_prices(bool is_masterchain) const { if (cell.is_null()) { return td::Status::Error(PSLICE() << "configuration parameter " << id << " with msg prices is absent"); } - auto cs = vm::load_cell_slice(std::move(cell)); + return do_get_msg_prices(vm::load_cell_slice(cell), id); +} + +td::Result Config::do_get_msg_prices(vm::CellSlice cs, int id) { block::gen::MsgForwardPrices::Record rec; if (!tlb::unpack(cs, rec)) { return td::Status::Error(PSLICE() << "configuration parameter " << id @@ -592,14 +721,20 @@ td::Result Config::get_msg_prices(bool is_masterchain) const { } CatchainValidatorsConfig Config::unpack_catchain_validators_config(Ref cell) { - block::gen::CatchainConfig::Record cfg; - if (cell.is_null() || !tlb::unpack_cell(std::move(cell), cfg)) { - return {default_mc_catchain_lifetime, default_shard_catchain_lifetime, default_shard_validators_lifetime, - default_shard_validators_num}; - } else { - return {cfg.mc_catchain_lifetime, cfg.shard_catchain_lifetime, cfg.shard_validators_lifetime, - cfg.shard_validators_num}; + if (cell.not_null()) { + block::gen::CatchainConfig::Record_catchain_config cfg; + if (tlb::unpack_cell(cell, cfg)) { + return {cfg.mc_catchain_lifetime, cfg.shard_catchain_lifetime, cfg.shard_validators_lifetime, + cfg.shard_validators_num}; + } + block::gen::CatchainConfig::Record_catchain_config_new cfg2; + if (tlb::unpack_cell(std::move(cell), cfg2)) { + return {cfg2.mc_catchain_lifetime, cfg2.shard_catchain_lifetime, cfg2.shard_validators_lifetime, + cfg2.shard_validators_num, cfg2.shuffle_mc_validators}; + } } + return {default_mc_catchain_lifetime, default_shard_catchain_lifetime, default_shard_validators_lifetime, + default_shard_validators_num}; } CatchainValidatorsConfig Config::get_catchain_validators_config() const { @@ -622,29 +757,51 @@ void McShardHash::set_fsm(FsmState fsm, ton::UnixTime fsm_utime, ton::UnixTime f } Ref McShardHash::unpack(vm::CellSlice& cs, ton::ShardIdFull id) { - gen::ShardDescr::Record descr; - CurrencyCollection fees_collected, funds_created; - if (!(tlb::unpack_exact(cs, descr) && fees_collected.unpack(descr.fees_collected) && - funds_created.unpack(descr.funds_created))) { - return {}; // throw ? + int tag = gen::t_ShardDescr.get_tag(cs); + if (tag < 0) { + return {}; + } + auto create = [&id](auto& descr, Ref fees, Ref funds) { + CurrencyCollection fees_collected, funds_created; + if (!(fees_collected.unpack(std::move(fees)) && funds_created.unpack(std::move(funds)))) { + return Ref{}; + } + return td::make_ref(ton::BlockId{id, (unsigned)descr.seq_no}, descr.start_lt, descr.end_lt, + descr.gen_utime, descr.root_hash, descr.file_hash, fees_collected, funds_created, + descr.reg_mc_seqno, descr.min_ref_mc_seqno, descr.next_catchain_seqno, + descr.next_validator_shard, /* descr.nx_cc_updated */ false, descr.before_split, + descr.before_merge, descr.want_split, descr.want_merge); + }; + Ref res; + Ref fsm_cs; + if (tag == gen::ShardDescr::shard_descr) { + gen::ShardDescr::Record_shard_descr descr; + if (tlb::unpack_exact(cs, descr)) { + fsm_cs = std::move(descr.split_merge_at); + res = create(descr, std::move(descr.fees_collected), std::move(descr.funds_created)); + } + } else { + gen::ShardDescr::Record_shard_descr_new descr; + if (tlb::unpack_exact(cs, descr)) { + fsm_cs = std::move(descr.split_merge_at); + res = create(descr, std::move(descr.r1.fees_collected), std::move(descr.r1.funds_created)); + } + } + if (res.is_null()) { + return res; } - auto res = Ref(true, ton::BlockId{id, (unsigned)descr.seq_no}, descr.start_lt, descr.end_lt, - descr.gen_utime, descr.root_hash, descr.file_hash, fees_collected, funds_created, - descr.reg_mc_seqno, descr.min_ref_mc_seqno, descr.next_catchain_seqno, - descr.next_validator_shard, /* descr.nx_cc_updated */ false, descr.before_split, - descr.before_merge, descr.want_split, descr.want_merge); McShardHash& sh = res.unique_write(); - switch (gen::t_FutureSplitMerge.get_tag(*(descr.split_merge_at))) { + switch (gen::t_FutureSplitMerge.get_tag(*fsm_cs)) { case gen::FutureSplitMerge::fsm_none: return res; case gen::FutureSplitMerge::fsm_split: - if (gen::t_FutureSplitMerge.unpack_fsm_split(descr.split_merge_at.write(), sh.fsm_utime_, sh.fsm_interval_)) { + if (gen::t_FutureSplitMerge.unpack_fsm_split(fsm_cs.write(), sh.fsm_utime_, sh.fsm_interval_)) { sh.fsm_ = FsmState::fsm_split; return res; } break; case gen::FutureSplitMerge::fsm_merge: - if (gen::t_FutureSplitMerge.unpack_fsm_merge(descr.split_merge_at.write(), sh.fsm_utime_, sh.fsm_interval_)) { + if (gen::t_FutureSplitMerge.unpack_fsm_merge(fsm_cs.write(), sh.fsm_utime_, sh.fsm_interval_)) { sh.fsm_ = FsmState::fsm_merge; return res; } @@ -657,7 +814,7 @@ Ref McShardHash::unpack(vm::CellSlice& cs, ton::ShardIdFull id) { bool McShardHash::pack(vm::CellBuilder& cb) const { if (!(is_valid() // (validate) - && cb.store_long_bool(11, 4) // shard_descr#b + && cb.store_long_bool(10, 4) // shard_descr_new#a && cb.store_long_bool(blk_.id.seqno, 32) // seq_no:uint32 && cb.store_long_bool(reg_mc_seqno_, 32) // reg_mc_seqno:uint32 && cb.store_long_bool(start_lt_, 64) // start_lt:uint64 @@ -691,9 +848,11 @@ bool McShardHash::pack(vm::CellBuilder& cb) const { default: return false; } - return ok // split_merge_at:FutureSplitMerge - && fees_collected_.store_or_zero(cb) // fees_collected:CurrencyCollection - && funds_created_.store_or_zero(cb); // funds_created:CurrencyCollection = ShardDescr; + vm::CellBuilder cb2; + return ok // split_merge_at:FutureSplitMerge + && fees_collected_.store_or_zero(cb2) // ^[ fees_collected:CurrencyCollection + && funds_created_.store_or_zero(cb2) // funds_created:CurrencyCollection ] + && cb.store_builder_ref_bool(std::move(cb2)); // = ShardDescr; } Ref McShardHash::from_block(Ref block_root, const ton::FileHash& fhash, bool init_fees) { @@ -710,11 +869,12 @@ Ref McShardHash::from_block(Ref block_root, const ton::Fi ton::RootHash rhash = block_root->get_hash().bits(); CurrencyCollection fees_collected, funds_created; if (init_fees) { - block::gen::ValueFlow::Record flow; - if (!(tlb::unpack_cell(rec.value_flow, flow) && fees_collected.unpack(flow.fees_collected) && - funds_created.unpack(flow.r2.created))) { + block::ValueFlow flow; + if (!flow.unpack(vm::load_cell_slice_ref(rec.value_flow))) { return {}; } + fees_collected = flow.fees_collected; + funds_created = flow.created; } return Ref(true, ton::BlockId{ton::ShardIdFull(shard), (unsigned)info.seq_no}, info.start_lt, info.end_lt, info.gen_utime, rhash, fhash, fees_collected, funds_created, ~0U, @@ -752,8 +912,8 @@ Ref McShardDescr::from_block(Ref block_root, Ref McShardDescr::from_block(Ref block_root, Refget_hash().bits(); CurrencyCollection fees_collected, funds_created; if (init_fees) { - block::gen::ValueFlow::Record flow; - if (!(tlb::unpack_cell(rec.value_flow, flow) && fees_collected.unpack(flow.fees_collected) && - funds_created.unpack(flow.r2.created))) { + block::ValueFlow flow; + if (!flow.unpack(vm::load_cell_slice_ref(rec.value_flow))) { return {}; } + fees_collected = flow.fees_collected; + funds_created = flow.created; } auto res = Ref(true, ton::BlockId{ton::ShardIdFull(shard), (unsigned)info.seq_no}, info.start_lt, info.end_lt, info.gen_utime, rhash, fhash, fees_collected, funds_created, ~0U, @@ -870,7 +1031,7 @@ bool ShardConfig::get_shard_hash_raw_from(vm::Dictionary& dict, vm::CellSlice& c unsigned long long z = id.shard, m = std::numeric_limits::max(); int len = id.pfx_len(); while (true) { - cs.load(vm::NoVmOrd{}, leaf ? root : std::move(root)); + cs.load(vm::NoVmOrd(), leaf ? root : std::move(root)); int t = (int)cs.fetch_ulong(1); if (t < 0) { return false; // throw DictError ? @@ -913,27 +1074,45 @@ Ref ShardConfig::get_shard_hash(ton::ShardIdFull id, bool exact) co } } +bool McShardHash::extract_cc_seqno(vm::CellSlice& cs, ton::CatchainSeqno* cc) { + auto get = [&cs, cc](auto& rec) { + if (tlb::unpack_exact(cs, rec)) { + *cc = rec.next_catchain_seqno; + return true; + } else { + *cc = std::numeric_limits::max(); + return false; + } + }; + if (block::gen::t_ShardDescr.get_tag(cs) == block::gen::ShardDescr::shard_descr) { + gen::ShardDescr::Record_shard_descr rec; + return get(rec); + } else { + gen::ShardDescr::Record_shard_descr_new rec; + return get(rec); + } +} + ton::CatchainSeqno ShardConfig::get_shard_cc_seqno(ton::ShardIdFull shard) const { if (shard.is_masterchain() || !shard.is_valid()) { return std::numeric_limits::max(); } ton::ShardIdFull true_id; - gen::ShardDescr::Record info; + ton::CatchainSeqno cc_seqno, cc_seqno2; vm::CellSlice cs; if (!(get_shard_hash_raw(cs, shard - 1, true_id, false) && (ton::shard_is_ancestor(true_id, shard) || ton::shard_is_parent(shard, true_id)) && - tlb::unpack_exact(cs, info))) { + McShardHash::extract_cc_seqno(cs, &cc_seqno))) { return std::numeric_limits::max(); } - ton::CatchainSeqno cc_seqno = info.next_catchain_seqno; if (ton::shard_is_ancestor(true_id, shard)) { return cc_seqno; } if (!(get_shard_hash_raw(cs, shard + 1, true_id, false) && ton::shard_is_parent(shard, true_id) && - tlb::unpack_exact(cs, info))) { + McShardHash::extract_cc_seqno(cs, &cc_seqno2))) { return std::numeric_limits::max(); } - return std::max(cc_seqno, info.next_catchain_seqno) + 1; + return std::max(cc_seqno, cc_seqno2) + 1; } ton::LogicalTime ShardConfig::get_shard_end_lt_ext(ton::AccountIdPrefixFull acc, ton::ShardIdFull& actual_shard) const { @@ -1078,9 +1257,10 @@ bool ShardConfig::process_sibling_shard_hashes(std::function root; ok = ok && (n == 32) && csr->size_ext() == 0x10000 && std::move(csr)->prefetch_ref_to(root) && process_workchain_sibling_shard_hashes(root, Ref{}, ton::ShardIdFull{(int)key.get_int(32)}, func) >= - 0 && - cb.store_ref_bool(std::move(root)); - return true; + 0; + bool f = cb.store_ref_bool(std::move(root)); + ok &= f; + return f; }); return ok; } @@ -1093,47 +1273,47 @@ std::vector ShardConfig::get_shard_hash_ids( std::vector res; bool mcout = mc_shard_hash_.is_null() || !mc_shard_hash_->seqno(); // include masterchain as a shard if seqno > 0 bool ok = shard_hashes_dict_->check_for_each( - [&res, &mcout, mc_shard_hash_ = mc_shard_hash_, &filter ](Ref cs_ref, td::ConstBitPtr key, int n) - ->bool { - int workchain = (int)key.get_int(n); - if (workchain >= 0 && !mcout) { - if (filter(ton::ShardIdFull{ton::masterchainId}, true)) { - res.emplace_back(mc_shard_hash_->blk_.id); - } - mcout = true; - } - if (!cs_ref->have_refs()) { + [&res, &mcout, mc_shard_hash_ = mc_shard_hash_, &filter](Ref cs_ref, td::ConstBitPtr key, + int n) -> bool { + int workchain = (int)key.get_int(n); + if (workchain >= 0 && !mcout) { + if (filter(ton::ShardIdFull{ton::masterchainId}, true)) { + res.emplace_back(mc_shard_hash_->blk_.id); + } + mcout = true; + } + if (!cs_ref->have_refs()) { + return false; + } + std::stack, unsigned long long>> stack; + stack.emplace(cs_ref->prefetch_ref(), ton::shardIdAll); + while (!stack.empty()) { + vm::CellSlice cs{vm::NoVmOrd(), std::move(stack.top().first)}; + unsigned long long shard = stack.top().second; + stack.pop(); + int t = (int)cs.fetch_ulong(1); + if (t < 0) { + return false; + } + if (!filter(ton::ShardIdFull{workchain, shard}, !t)) { + continue; + } + if (!t) { + if (!(cs.advance(4) && cs.have(32))) { return false; } - std::stack, unsigned long long>> stack; - stack.emplace(cs_ref->prefetch_ref(), ton::shardIdAll); - while (!stack.empty()) { - vm::CellSlice cs{vm::NoVm{}, std::move(stack.top().first)}; - unsigned long long shard = stack.top().second; - stack.pop(); - int t = (int)cs.fetch_ulong(1); - if (t < 0) { - return false; - } - if (!filter(ton::ShardIdFull{workchain, shard}, !t)) { - continue; - } - if (!t) { - if (!(cs.advance(4) && cs.have(32))) { - return false; - } - res.emplace_back(workchain, shard, (int)cs.prefetch_ulong(32)); - continue; - } - unsigned long long delta = (td::lower_bit64(shard) >> 1); - if (!delta || cs.size_ext() != 0x20000) { - return false; - } - stack.emplace(cs.prefetch_ref(1), shard + delta); - stack.emplace(cs.prefetch_ref(0), shard - delta); - } - return true; - }, + res.emplace_back(workchain, shard, (int)cs.prefetch_ulong(32)); + continue; + } + unsigned long long delta = (td::lower_bit64(shard) >> 1); + if (!delta || cs.size_ext() != 0x20000) { + return false; + } + stack.emplace(cs.prefetch_ref(1), shard + delta); + stack.emplace(cs.prefetch_ref(0), shard - delta); + } + return true; + }, true); if (!ok) { return {}; @@ -1224,7 +1404,7 @@ bool ShardConfig::new_workchain(ton::WorkchainId workchain, ton::BlockSeqno reg_ cb.store_zeroes_bool( 1 + 5 + 5) // split_merge_at:FutureSplitMerge fees_collected:CurrencyCollection funds_created:CurrencyCollection - && cb.finalize_to(cell) && block::gen::t_BinTree_ShardDescr.validate_ref(cell) && + && cb.finalize_to(cell) && block::gen::t_BinTree_ShardDescr.validate_ref(1024, cell) && shard_hashes_dict_->set_ref(td::BitArray<32>{workchain}, std::move(cell), vm::Dictionary::SetMode::Add); } @@ -1454,7 +1634,7 @@ static bool btree_set(Ref& root, ton::ShardId shard, Ref val } bool ShardConfig::set_shard_info(ton::ShardIdFull shard, Ref value) { - if (!gen::t_BinTree_ShardDescr.validate_ref(value)) { + if (!gen::t_BinTree_ShardDescr.validate_ref(1024, value)) { LOG(ERROR) << "attempting to store an invalid (BinTree ShardDescr) at shard configuration position " << shard.to_str(); gen::t_BinTree_ShardDescr.print_ref(std::cerr, value); @@ -1489,8 +1669,8 @@ td::Result> Config::get_special_smartcontracts(b return td::Status::Error(-666, "configuration loaded without fundamental smart contract list"); } std::vector res; - if (!special_smc_dict->check_for_each([&res, &without_config, conf_addr = config_addr.bits() ]( - Ref cs_ref, td::ConstBitPtr key, int n) { + if (!special_smc_dict->check_for_each([&res, &without_config, conf_addr = config_addr.bits()]( + Ref cs_ref, td::ConstBitPtr key, int n) { if (cs_ref->size_ext() || n != 256) { return false; } @@ -1651,6 +1831,31 @@ std::vector ValidatorSet::export_validator_set() const { return l; } +std::map ValidatorSet::compute_validator_map() const { + std::map res; + for (int i = 0; i < (int)list.size(); i++) { + res.emplace(list[i].pubkey.as_bits256(), i); + } + return res; +} + +std::vector ValidatorSet::export_scaled_validator_weights() const { + std::vector res; + for (const auto& node : list) { + res.push_back((double)node.weight / (double)total_weight); + } + return res; +} + +int ValidatorSet::lookup_public_key(td::ConstBitPtr pubkey) const { + for (int i = 0; i < (int)list.size(); i++) { + if (list[i].pubkey.as_bits256() == pubkey) { + return i; + } + } + return -1; +} + std::vector Config::do_compute_validator_set(const block::CatchainValidatorsConfig& ccv_conf, ton::ShardIdFull shard, const block::ValidatorSet& vset, ton::UnixTime time, @@ -1664,18 +1869,32 @@ std::vector Config::do_compute_validator_set(const block::C return {}; // no validators? } nodes.reserve(count); + ValidatorSetPRNG gen{shard, cc_seqno}; // use zero seed (might use a non-trivial seed from ccv_conf in the future) if (is_mc) { - // simply take needed number of validators from the head of the list - // TODO: write a more clever validator list selection algorithm - // (if we really need one for the masterchain) - for (unsigned i = 0; i < count; i++) { - const auto& v = vset.list[i]; - nodes.emplace_back(v.pubkey, v.weight, v.adnl_addr); + if (ccv_conf.shuffle_mc_val) { + // shuffle mc validators from the head of the list + std::vector idx(count); + CHECK(idx.size() == count); + for (unsigned i = 0; i < count; i++) { + unsigned j = (unsigned)gen.next_ranged(i + 1); // number 0 .. i + CHECK(j <= i); + idx[i] = idx[j]; + idx[j] = i; + } + for (unsigned i = 0; i < count; i++) { + const auto& v = vset.list[idx[i]]; + nodes.emplace_back(v.pubkey, v.weight, v.adnl_addr); + } + } else { + // simply take needed number of validators from the head of the list + for (unsigned i = 0; i < count; i++) { + const auto& v = vset.list[i]; + nodes.emplace_back(v.pubkey, v.weight, v.adnl_addr); + } } return nodes; } // this is the "true" algorithm for generating shardchain validator subgroups - ValidatorSetPRNG gen{shard, cc_seqno}; // use zero seed (might use a non-trivial seed from ccv_conf in the future) std::vector> holes; holes.reserve(count); td::uint64 total_wt = vset.total_weight; @@ -1710,6 +1929,120 @@ std::vector Config::compute_total_validator_set(int next) c return res.move_as_ok()->export_validator_set(); } +td::Result Config::get_size_limits_config() const { + td::Ref param = get_config_param(43); + if (param.is_null()) { + return do_get_size_limits_config({}); + } + return do_get_size_limits_config(vm::load_cell_slice_ref(param)); +} + +td::Result Config::do_get_size_limits_config(td::Ref cs) { + SizeLimitsConfig limits; + if (cs.is_null()) { + return limits; // default values + } + auto unpack_v1 = [&](auto& rec) { + limits.max_msg_bits = rec.max_msg_bits; + limits.max_msg_cells = rec.max_msg_cells; + limits.max_library_cells = rec.max_library_cells; + limits.max_vm_data_depth = static_cast(rec.max_vm_data_depth); + limits.ext_msg_limits.max_size = rec.max_ext_msg_size; + limits.ext_msg_limits.max_depth = static_cast(rec.max_ext_msg_depth); + }; + + auto unpack_v2 = [&](auto& rec) { + unpack_v1(rec); + limits.max_acc_state_bits = rec.max_acc_state_bits; + limits.max_acc_state_cells = rec.max_acc_state_cells; + limits.max_acc_public_libraries = rec.max_acc_public_libraries; + }; + gen::SizeLimitsConfig::Record_size_limits_config rec_v1; + gen::SizeLimitsConfig::Record_size_limits_config_v2 rec_v2; + if (tlb::csr_unpack(cs, rec_v1)) { + unpack_v1(rec_v1); + } else if (tlb::csr_unpack(cs, rec_v2)) { + unpack_v2(rec_v2); + } else { + return td::Status::Error("configuration parameter 43 is invalid"); + } + return limits; +} + +std::unique_ptr Config::get_suspended_addresses(ton::UnixTime now) const { + td::Ref param = get_config_param(44); + gen::SuspendedAddressList::Record rec; + if (param.is_null() || !tlb::unpack_cell(param, rec) || rec.suspended_until <= now) { + return {}; + } + return std::make_unique(rec.addresses->prefetch_ref(), 288); +} + +BurningConfig Config::get_burning_config() const { + td::Ref param = get_config_param(5); + gen::BurningConfig::Record rec; + if (param.is_null() || !tlb::unpack_cell(param, rec)) { + return {}; + } + BurningConfig c; + c.fee_burn_num = rec.fee_burn_num; + c.fee_burn_denom = rec.fee_burn_denom; + vm::CellSlice& addr = rec.blackhole_addr.write(); + if (addr.fetch_long(1)) { + td::Bits256 x; + addr.fetch_bits_to(x.bits(), 256); + c.blackhole_addr = x; + } + return c; +} + +td::Ref Config::get_unpacked_config_tuple(ton::UnixTime now) const { + auto get_param = [&](td::int32 idx) -> vm::StackEntry { + auto cell = get_config_param(idx); + if (cell.is_null()) { + return {}; + } + return vm::load_cell_slice_ref(cell); + }; + auto get_current_storage_prices = [&]() -> vm::StackEntry { + auto cell = get_config_param(18); + if (cell.is_null()) { + return {}; + } + vm::StackEntry res; + vm::Dictionary dict{std::move(cell), 32}; + dict.check_for_each([&](Ref cs_ref, td::ConstBitPtr key, int n) -> bool { + auto utime_since = key.get_uint(n); + if (now >= utime_since) { + res = std::move(cs_ref); + return true; + } + return false; + }); + return res; + }; + std::vector tuple; + tuple.push_back(get_current_storage_prices()); // storage_prices + tuple.push_back(get_param(19)); // global_id + tuple.push_back(get_param(20)); // config_mc_gas_prices + tuple.push_back(get_param(21)); // config_gas_prices + tuple.push_back(get_param(24)); // config_mc_fwd_prices + tuple.push_back(get_param(25)); // config_fwd_prices + tuple.push_back(get_param(43)); // size_limits_config + return td::make_cnt_ref>(std::move(tuple)); +} + +PrecompiledContractsConfig Config::get_precompiled_contracts_config() const { + PrecompiledContractsConfig c; + td::Ref param = get_config_param(45); + gen::PrecompiledContractsConfig::Record rec; + if (param.is_null() || !tlb::unpack_cell(param, rec)) { + return c; + } + c.list = vm::Dictionary{rec.list->prefetch_ref(), 256}; + return c; +} + td::Result> Config::unpack_validator_set_start_stop(Ref vset_root) { if (vset_root.is_null()) { return td::Status::Error("validator set absent"); @@ -1739,31 +2072,58 @@ bool WorkchainInfo::unpack(ton::WorkchainId wc, vm::CellSlice& cs) { if (wc == ton::workchainInvalid) { return false; } - block::gen::WorkchainDescr::Record info; - if (!tlb::unpack(cs, info)) { - return false; - } - enabled_since = info.enabled_since; - actual_min_split = info.actual_min_split; - min_split = info.min_split; - max_split = info.max_split; - basic = info.basic; - active = info.active; - accept_msgs = info.accept_msgs; - flags = info.flags; - zerostate_root_hash = info.zerostate_root_hash; - zerostate_file_hash = info.zerostate_file_hash; - version = info.version; - if (basic) { - min_addr_len = max_addr_len = addr_len_step = 256; - } else { - block::gen::WorkchainFormat::Record_wfmt_ext ext; - if (!tlb::type_unpack(cs, block::gen::WorkchainFormat{basic}, ext)) { + auto unpack_v1 = [this](auto& info) { + enabled_since = info.enabled_since; + actual_min_split = info.actual_min_split; + min_split = info.min_split; + max_split = info.max_split; + basic = info.basic; + active = info.active; + accept_msgs = info.accept_msgs; + flags = info.flags; + zerostate_root_hash = info.zerostate_root_hash; + zerostate_file_hash = info.zerostate_file_hash; + version = info.version; + if (basic) { + min_addr_len = max_addr_len = addr_len_step = 256; + } else { + block::gen::WorkchainFormat::Record_wfmt_ext ext; + if (!tlb::csr_type_unpack(info.format, block::gen::WorkchainFormat{basic}, ext)) { + return false; + } + min_addr_len = ext.min_addr_len; + max_addr_len = ext.max_addr_len; + addr_len_step = ext.addr_len_step; + } + return true; + }; + auto unpack_v2 = [&, this](auto& info) { + if (!unpack_v1(info)) { return false; } - min_addr_len = ext.min_addr_len; - max_addr_len = ext.max_addr_len; - addr_len_step = ext.addr_len_step; + block::gen::WcSplitMergeTimings::Record rec; + if (!tlb::csr_unpack(info.split_merge_timings, rec)) { + return false; + } + split_merge_delay = rec.split_merge_delay; + split_merge_interval = rec.split_merge_interval; + min_split_merge_interval = rec.min_split_merge_interval; + max_split_merge_delay = rec.max_split_merge_delay; + return true; + }; + block::gen::WorkchainDescr::Record_workchain info_v1; + block::gen::WorkchainDescr::Record_workchain_v2 info_v2; + vm::CellSlice cs0 = cs; + if (tlb::unpack(cs, info_v1)) { + if (!unpack_v1(info_v1)) { + return false; + } + } else if (tlb::unpack(cs = cs0, info_v2)) { + if (!unpack_v2(info_v2)) { + return false; + } + } else { + return false; } workchain = wc; LOG(DEBUG) << "unpacked info for workchain " << wc << ": basic=" << basic << ", active=" << active @@ -1917,7 +2277,7 @@ Ref ConfigInfo::lookup_library(td::ConstBitPtr root_hash) const { return {}; } auto csr = libraries_dict_->lookup(root_hash, 256); - if (csr.is_null() || csr->prefetch_ulong(8) != 0 || !csr->have_refs()) { // shared_lib_descr$00 lib:^Cell + if (csr.is_null() || csr->prefetch_ulong(2) != 0 || !csr->have_refs()) { // shared_lib_descr$00 lib:^Cell return {}; } auto lib = csr->prefetch_ref(); @@ -1929,4 +2289,58 @@ Ref ConfigInfo::lookup_library(td::ConstBitPtr root_hash) const { return lib; } +td::Result> ConfigInfo::get_prev_blocks_info() const { + // [ wc:Integer shard:Integer seqno:Integer root_hash:Integer file_hash:Integer] = BlockId; + // [ last_mc_blocks:[BlockId...] + // prev_key_block:BlockId ] : PrevBlocksInfo + auto block_id_to_tuple = [](const ton::BlockIdExt& block_id) -> vm::Ref { + td::RefInt256 shard = td::make_refint(block_id.id.shard); + if (shard->sgn() < 0) { + shard &= ((td::make_refint(1) << 64) - 1); + } + return vm::make_tuple_ref( + td::make_refint(block_id.id.workchain), + std::move(shard), + td::make_refint(block_id.id.seqno), + td::bits_to_refint(block_id.root_hash.bits(), 256), + td::bits_to_refint(block_id.file_hash.bits(), 256)); + }; + std::vector last_mc_blocks; + + last_mc_blocks.push_back(block_id_to_tuple(block_id)); + for (ton::BlockSeqno seqno = block_id.id.seqno; seqno > 0 && last_mc_blocks.size() < 16; ) { + --seqno; + ton::BlockIdExt block_id; + if (!get_old_mc_block_id(seqno, block_id)) { + return td::Status::Error("cannot fetch old mc block"); + } + last_mc_blocks.push_back(block_id_to_tuple(block_id)); + } + + ton::BlockIdExt last_key_block; + ton::LogicalTime last_key_block_lt; + if (!get_last_key_block(last_key_block, last_key_block_lt)) { + return td::Status::Error("cannot fetch last key block"); + } + return vm::make_tuple_ref( + td::make_cnt_ref>(std::move(last_mc_blocks)), + block_id_to_tuple(last_key_block)); +} + +td::optional PrecompiledContractsConfig::get_contract( + td::Bits256 code_hash) const { + auto list_copy = list; + auto cs = list_copy.lookup(code_hash); + if (cs.is_null()) { + return {}; + } + gen::PrecompiledSmc::Record rec; + if (!tlb::csr_unpack(cs, rec)) { + return {}; + } + Contract c; + c.gas_usage = rec.gas_usage; + return c; +} + } // namespace block diff --git a/crypto/block/mc-config.h b/crypto/block/mc-config.h index 55bf1122..2ca89399 100644 --- a/crypto/block/mc-config.h +++ b/crypto/block/mc-config.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with TON Blockchain. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/refcnt.hpp" @@ -50,7 +50,7 @@ struct ValidatorDescr { : pubkey(_pubkey), weight(_weight), cum_weight(_cum_weight) { adnl_addr.set_zero(); } - bool operator<(td::uint64 wt_pos) const & { + bool operator<(td::uint64 wt_pos) const& { return cum_weight < wt_pos; } }; @@ -71,6 +71,12 @@ struct ValidatorSet { } const ValidatorDescr& at_weight(td::uint64 weight_pos) const; std::vector export_validator_set() const; + std::map compute_validator_map() const; + std::vector export_scaled_validator_weights() const; + int lookup_public_key(td::ConstBitPtr pubkey) const; + int lookup_public_key(const td::Bits256& pubkey) const { + return lookup_public_key(pubkey.bits()); + } }; #pragma pack(push, 1) @@ -273,6 +279,7 @@ struct McShardHash : public McShardHashI { bool pack(vm::CellBuilder& cb) const; static Ref unpack(vm::CellSlice& cs, ton::ShardIdFull id); static Ref from_block(Ref block_root, const ton::FileHash& _fhash, bool init_fees = false); + static bool extract_cc_seqno(vm::CellSlice& cs, ton::CatchainSeqno* cc); McShardHash* make_copy() const override { return new McShardHash(*this); } @@ -343,7 +350,11 @@ struct GasLimitsPrices { td::uint64 freeze_due_limit{0}; td::uint64 delete_due_limit{0}; - td::RefInt256 compute_gas_price(td::uint64 gas_used) const; + td::RefInt256 compute_gas_price(td::uint64 gas_used) const { + return gas_used <= flat_gas_limit + ? td::make_refint(flat_gas_price) + : td::rshift(td::make_refint(gas_price) * (gas_used - flat_gas_limit), 16, 1) + flat_gas_price; + } }; // msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms @@ -358,6 +369,7 @@ struct MsgPrices { td::uint32 first_frac; td::uint32 next_frac; td::uint64 compute_fwd_fees(td::uint64 cells, td::uint64 bits) const; + td::RefInt256 compute_fwd_fees256(td::uint64 cells, td::uint64 bits) const; std::pair compute_fwd_ihr_fees(td::uint64 cells, td::uint64 bits, bool ihr_disabled = false) const; MsgPrices() = default; @@ -369,13 +381,32 @@ struct MsgPrices { td::RefInt256 get_next_part(td::RefInt256 total) const; }; +struct SizeLimitsConfig { + // Default values are used when not present in global config + struct ExtMsgLimits { + td::uint32 max_size = 65535; + td::uint16 max_depth = 512; + }; + td::uint32 max_msg_bits = 1 << 21; + td::uint32 max_msg_cells = 1 << 13; + td::uint32 max_library_cells = 1000; + td::uint16 max_vm_data_depth = 512; + ExtMsgLimits ext_msg_limits; + td::uint32 max_acc_state_cells = 1 << 16; + td::uint32 max_acc_state_bits = (1 << 16) * 1023; + td::uint32 max_acc_public_libraries = 256; +}; + struct CatchainValidatorsConfig { td::uint32 mc_cc_lifetime, shard_cc_lifetime, shard_val_lifetime, shard_val_num; - CatchainValidatorsConfig(td::uint32 mc_cc_lt_, td::uint32 sh_cc_lt_, td::uint32 sh_val_lt_, td::uint32 sh_val_num_) + bool shuffle_mc_val; + CatchainValidatorsConfig(td::uint32 mc_cc_lt_, td::uint32 sh_cc_lt_, td::uint32 sh_val_lt_, td::uint32 sh_val_num_, + bool shuffle_mc = false) : mc_cc_lifetime(mc_cc_lt_) , shard_cc_lifetime(sh_cc_lt_) , shard_val_lifetime(sh_val_lt_) - , shard_val_num(sh_val_num_) { + , shard_val_num(sh_val_num_) + , shuffle_mc_val(shuffle_mc) { } }; @@ -392,6 +423,13 @@ struct WorkchainInfo : public td::CntObject { ton::RootHash zerostate_root_hash; ton::FileHash zerostate_file_hash; int min_addr_len, max_addr_len, addr_len_step; + + // Default values are used when split_merge_timings is not set in config + unsigned split_merge_delay = 100; // prepare (delay) split/merge for 100 seconds + unsigned split_merge_interval = 100; // split/merge is enabled during 60 second interval + unsigned min_split_merge_interval = 30; // split/merge interval must be at least 30 seconds + unsigned max_split_merge_delay = 1000; // end of split/merge interval must be at most 1000 seconds in the future + bool is_valid() const { return workchain != ton::workchainInvalid; } @@ -472,6 +510,31 @@ class ShardConfig { bool set_shard_info(ton::ShardIdFull shard, Ref value); }; +struct BurningConfig { + td::optional blackhole_addr; + td::uint32 fee_burn_num = 0, fee_burn_denom = 1; + + td::RefInt256 calculate_burned_fees(const td::RefInt256& x) const { + if (x.is_null()) { + return x; + } + return x * fee_burn_num / td::make_refint(fee_burn_denom); + } + + CurrencyCollection calculate_burned_fees(const CurrencyCollection& x) const { + return CurrencyCollection{calculate_burned_fees(x.grams)}; + } +}; + +struct PrecompiledContractsConfig { + struct Contract { + td::uint64 gas_usage; + }; + vm::Dictionary list{256}; + + td::optional get_contract(td::Bits256 code_hash) const; +}; + class Config { enum { default_mc_catchain_lifetime = 200, @@ -534,14 +597,32 @@ class Config { bool create_stats_enabled() const { return has_capability(ton::capCreateStatsEnabled); } + std::unique_ptr get_param_dict(int idx) const; + td::Result> unpack_param_list(int idx) const; + std::unique_ptr get_mandatory_param_dict() const { + return get_param_dict(9); + } + std::unique_ptr get_critical_param_dict() const { + return get_param_dict(10); + } + td::Result> get_mandatory_param_list() const { + return unpack_param_list(9); + } + td::Result> get_critical_param_list() const { + return unpack_param_list(10); + } + bool all_mandatory_params_defined(int* bad_idx_ptr = nullptr) const; + td::Result get_dns_root_addr() const; bool set_block_id_ext(const ton::BlockIdExt& block_id_ext); td::Result> get_special_smartcontracts(bool without_config = false) const; bool is_special_smartcontract(const ton::StdSmcAddress& addr) const; static td::Result> unpack_validator_set(Ref valset_root); td::Result> get_storage_prices() const; + static td::Result do_get_one_storage_prices(vm::CellSlice cs); td::Result get_gas_limits_prices(bool is_masterchain = false) const; - static td::Result do_get_gas_limits_prices(td::Ref cell, int id); + static td::Result do_get_gas_limits_prices(vm::CellSlice cs, int id); td::Result get_msg_prices(bool is_masterchain = false) const; + static td::Result do_get_msg_prices(vm::CellSlice cs, int id); static CatchainValidatorsConfig unpack_catchain_validators_config(Ref cell); CatchainValidatorsConfig get_catchain_validators_config() const; td::Status visit_validator_params() const; @@ -567,6 +648,12 @@ class Config { std::vector compute_validator_set(ton::ShardIdFull shard, ton::UnixTime time, ton::CatchainSeqno cc_seqno) const; std::vector compute_total_validator_set(int next) const; + td::Result get_size_limits_config() const; + static td::Result do_get_size_limits_config(td::Ref cs); + std::unique_ptr get_suspended_addresses(ton::UnixTime now) const; + BurningConfig get_burning_config() const; + td::Ref get_unpacked_config_tuple(ton::UnixTime now) const; + PrecompiledContractsConfig get_precompiled_contracts_config() const; static std::vector do_compute_validator_set(const block::CatchainValidatorsConfig& ccv_conf, ton::ShardIdFull shard, const block::ValidatorSet& vset, ton::UnixTime time, @@ -579,8 +666,9 @@ class Config { static td::Result> extract_from_state(Ref mc_state_root, int mode = 0); static td::Result> extract_from_key_block(Ref key_block_root, int mode = 0); static td::Result> unpack_validator_set_start_stop(Ref root); + static td::Result> unpack_param_dict(vm::Dictionary& dict); + static td::Result> unpack_param_dict(Ref dict_root); - protected: Config(int _mode) : mode(_mode) { config_addr.set_zero(); } @@ -670,6 +758,7 @@ class ConfigInfo : public Config, public ShardConfig { ton::CatchainSeqno* cc_seqno_delta = nullptr) const; std::vector compute_validator_set_cc(ton::ShardIdFull shard, ton::UnixTime time, ton::CatchainSeqno* cc_seqno_delta = nullptr) const; + td::Result> get_prev_blocks_info() const; static td::Result> extract_config(std::shared_ptr static_boc, int mode = 0); static td::Result> extract_config(Ref mc_state_root, int mode = 0); diff --git a/crypto/block/output-queue-merger.cpp b/crypto/block/output-queue-merger.cpp index 981f1f95..aa425f6b 100644 --- a/crypto/block/output-queue-merger.cpp +++ b/crypto/block/output-queue-merger.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "output-queue-merger.h" @@ -146,22 +146,30 @@ bool OutputQueueMerger::add_root(int src, Ref outmsg_root) { return true; } -OutputQueueMerger::OutputQueueMerger(ton::ShardIdFull _queue_for, std::vector _neighbors) +OutputQueueMerger::OutputQueueMerger(ton::ShardIdFull _queue_for, std::vector _neighbors) : queue_for(_queue_for), neighbors(std::move(_neighbors)), eof(false), failed(false) { init(); } +OutputQueueMerger::OutputQueueMerger(ton::ShardIdFull _queue_for, std::vector _neighbors) + : queue_for(_queue_for), eof(false), failed(false) { + for (auto& nb : _neighbors) { + neighbors.emplace_back(nb.top_block_id(), nb.outmsg_root, nb.is_disabled()); + } + init(); +} + void OutputQueueMerger::init() { common_pfx.bits().store_int(queue_for.workchain, 32); int l = queue_for.pfx_len(); td::bitstring::bits_store_long_top(common_pfx.bits() + 32, queue_for.shard, l); common_pfx_len = 32 + l; int i = 0; - for (block::McShardDescr& neighbor : neighbors) { - if (!neighbor.is_disabled()) { - LOG(DEBUG) << "adding " << (neighbor.outmsg_root.is_null() ? "" : "non-") << "empty output queue for neighbor #" - << i << " (" << neighbor.blk_.to_str() << ")"; - add_root(i++, neighbor.outmsg_root); + for (Neighbor& neighbor : neighbors) { + if (!neighbor.disabled_) { + LOG(DEBUG) << "adding " << (neighbor.outmsg_root_.is_null() ? "" : "non-") << "empty output queue for neighbor #" + << i << " (" << neighbor.block_id_.to_str() << ")"; + add_root(i++, neighbor.outmsg_root_); } else { LOG(DEBUG) << "skipping output queue for disabled neighbor #" << i; i++; diff --git a/crypto/block/output-queue-merger.h b/crypto/block/output-queue-merger.h index 942ca7c8..07533f24 100644 --- a/crypto/block/output-queue-merger.h +++ b/crypto/block/output-queue-merger.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "ton/ton-types.h" @@ -51,12 +51,22 @@ struct OutputQueueMerger { bool unpack_node(td::ConstBitPtr key_pfx, int key_pfx_len, Ref node); bool split(MsgKeyValue& second); }; + struct Neighbor { + ton::BlockIdExt block_id_; + td::Ref outmsg_root_; + bool disabled_; + Neighbor() = default; + Neighbor(ton::BlockIdExt block_id, td::Ref outmsg_root, bool disabled = false) + : block_id_(block_id), outmsg_root_(std::move(outmsg_root)), disabled_(disabled) { + } + }; // ton::ShardIdFull queue_for; std::vector> msg_list; - std::vector neighbors; + std::vector neighbors; public: + OutputQueueMerger(ton::ShardIdFull _queue_for, std::vector _neighbors); OutputQueueMerger(ton::ShardIdFull _queue_for, std::vector _neighbors); bool is_eof() const { return eof; diff --git a/crypto/block/precompiled-smc/PrecompiledSmartContract.cpp b/crypto/block/precompiled-smc/PrecompiledSmartContract.cpp new file mode 100644 index 00000000..6797216d --- /dev/null +++ b/crypto/block/precompiled-smc/PrecompiledSmartContract.cpp @@ -0,0 +1,170 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#include "common.h" +#include +#include "vm/memo.h" + +namespace block::precompiled { + +using namespace vm; + +Result PrecompiledSmartContract::run(td::Ref my_address, ton::UnixTime now, ton::LogicalTime cur_lt, + CurrencyCollection balance, td::Ref c4, vm::CellSlice msg_body, + td::Ref msg, CurrencyCollection msg_balance, bool is_external, + std::vector> libraries, int global_version, + td::uint16 max_data_depth, td::Ref my_code, + td::Ref unpacked_config, td::RefInt256 due_payment, + td::uint64 precompiled_gas_usage) { + my_address_ = std::move(my_address); + now_ = now; + cur_lt_ = cur_lt; + balance_ = std::move(balance); + c4_ = (c4.not_null() ? std::move(c4) : CellBuilder().finalize()); + in_msg_body_ = std::move(msg_body); + in_msg_ = std::move(msg); + in_msg_balance_ = std::move(msg_balance); + is_external_ = is_external; + my_code_ = std::move(my_code); + unpacked_config_ = std::move(unpacked_config); + due_payment_ = std::move(due_payment); + precompiled_gas_usage_ = precompiled_gas_usage; + + vm::DummyVmState vm_state{std::move(libraries), global_version}; + vm::VmStateInterface::Guard guard{&vm_state}; + + Result result; + try { + result = do_run(); + } catch (vm::VmError &e) { + result = Result::error(e.get_errno(), e.get_arg()); + } catch (Result &r) { + result = std::move(r); + } + + if (result.exit_code != 0 && result.exit_code != 1) { + // see VmState::try_commit() + if (c4_.is_null() || c4_->get_depth() > max_data_depth || c4_->get_level() != 0 || c5_.is_null() || + c5_->get_depth() > max_data_depth || c5_->get_level() != 0) { + result = Result::error(Excno::cell_ov, 0); + } + } + return result; +} + +void PrecompiledSmartContract::send_raw_message(const td::Ref &msg, int mode) { + CellBuilder cb; + if (!(cb.store_ref_bool(c5_) // out_list$_ {n:#} prev:^(OutList n) + && cb.store_long_bool(0x0ec3c86d, 32) // action_send_msg#0ec3c86d + && cb.store_long_bool(mode, 8) // mode:(## 8) + && cb.store_ref_bool(msg))) { + throw VmError{Excno::cell_ov, "cannot serialize raw output message into an output action cell"}; + } + c5_ = cb.finalize_novm(); +} + +void PrecompiledSmartContract::raw_reserve(const td::RefInt256 &amount, int mode) { + if (amount->sgn() < 0) { + throw VmError{Excno::range_chk, "amount of nanograms must be non-negative"}; + } + CellBuilder cb; + if (!(cb.store_ref_bool(c5_) // out_list$_ {n:#} prev:^(OutList n) + && cb.store_long_bool(0x36e6b809, 32) // action_reserve_currency#36e6b809 + && cb.store_long_bool(mode, 8) // mode:(## 8) + && util::store_coins(cb, std::move(amount), true) // + && cb.store_maybe_ref({}))) { + throw VmError{Excno::cell_ov, "cannot serialize raw reserved currency amount into an output action cell"}; + } + c5_ = cb.finalize_novm(); +} + +td::RefInt256 PrecompiledSmartContract::get_compute_fee(ton::WorkchainId wc, td::uint64 gas_used) { + if (gas_used >= (1ULL << 63)) { + throw VmError{Excno::range_chk}; + } + block::GasLimitsPrices prices = util::get_gas_prices(unpacked_config_, wc); + return util::check_finite(prices.compute_gas_price(gas_used)); +} + +td::RefInt256 PrecompiledSmartContract::get_forward_fee(ton::WorkchainId wc, td::uint64 bits, td::uint64 cells) { + if (bits >= (1ULL << 63) || cells >= (1ULL << 63)) { + throw VmError{Excno::range_chk}; + } + block::MsgPrices prices = util::get_msg_prices(unpacked_config_, wc); + return util::check_finite(prices.compute_fwd_fees256(cells, bits)); +} + +td::RefInt256 PrecompiledSmartContract::get_storage_fee(ton::WorkchainId wc, td::uint64 duration, td::uint64 bits, + td::uint64 cells) { + if (bits >= (1ULL << 63) || cells >= (1ULL << 63) || duration >= (1ULL << 63)) { + throw VmError{Excno::range_chk}; + } + td::optional maybe_prices = util::get_storage_prices(unpacked_config_); + return util::check_finite(util::calculate_storage_fee(maybe_prices, wc, duration, bits, cells)); +} + +td::RefInt256 PrecompiledSmartContract::get_simple_compute_fee(ton::WorkchainId wc, td::uint64 gas_used) { + if (gas_used >= (1ULL << 63)) { + throw VmError{Excno::range_chk}; + } + block::GasLimitsPrices prices = util::get_gas_prices(unpacked_config_, wc); + return util::check_finite(td::rshift(td::make_refint(prices.gas_price) * gas_used, 16, 1)); +} + +td::RefInt256 PrecompiledSmartContract::get_simple_forward_fee(ton::WorkchainId wc, td::uint64 bits, td::uint64 cells) { + if (bits >= (1ULL << 63) || cells >= (1ULL << 63)) { + throw VmError{Excno::range_chk}; + } + block::MsgPrices prices = util::get_msg_prices(unpacked_config_, wc); + return util::check_finite( + td::rshift(td::make_refint(prices.bit_price) * bits + td::make_refint(prices.cell_price) * cells, 16, 1)); +} + +td::RefInt256 PrecompiledSmartContract::get_original_fwd_fee(ton::WorkchainId wc, const td::RefInt256 &x) { + if (x->sgn() < 0) { + throw VmError{Excno::range_chk, "fwd_fee is negative"}; + } + block::MsgPrices prices = util::get_msg_prices(unpacked_config_, wc); + return util::check_finite(td::muldiv(x, td::make_refint(1 << 16), td::make_refint((1 << 16) - prices.first_frac))); +} + +static std::atomic_bool precompiled_execution_enabled{false}; + +std::unique_ptr get_implementation(td::Bits256 code_hash) { + if (!precompiled_execution_enabled) { + return nullptr; + } + static std::map (*)()> map = []() { + auto from_hex = [](td::Slice s) -> td::Bits256 { + td::Bits256 x; + CHECK(x.from_hex(s) == 256); + return x; + }; + std::map (*)()> map; +#define CONTRACT(hash, cls) \ + map[from_hex(hash)] = []() -> std::unique_ptr { return std::make_unique(); }; + // CONTRACT("CODE_HASH_HEX", ClassName); + return map; + }(); + auto it = map.find(code_hash); + return it == map.end() ? nullptr : it->second(); +} + +void set_precompiled_execution_enabled(bool value) { + precompiled_execution_enabled = value; +} + +} // namespace block::precompiled diff --git a/crypto/block/precompiled-smc/PrecompiledSmartContract.h b/crypto/block/precompiled-smc/PrecompiledSmartContract.h new file mode 100644 index 00000000..509ebf79 --- /dev/null +++ b/crypto/block/precompiled-smc/PrecompiledSmartContract.h @@ -0,0 +1,122 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#pragma once +#include "common/refcnt.hpp" +#include "common/refint.h" +#include "vm/cells.h" +#include "vm/cellslice.h" +#include "vm/dict.h" +#include "vm/boc.h" +#include +#include "tl/tlblib.hpp" +#include "td/utils/bits.h" +#include "ton/ton-types.h" +#include "block/block.h" +#include "block/mc-config.h" + +namespace block::precompiled { + +struct Result { + int exit_code = 0; + td::optional exit_arg; + bool accepted = true; + bool committed = false; + + static Result error(int code, long long arg = 0) { + Result res; + res.exit_code = code; + res.exit_arg = arg; + return res; + } + + static Result error(vm::Excno code, long long arg = 0) { + Result res; + res.exit_code = (int)code; + res.exit_arg = arg; + return res; + } + + static Result not_accepted(int code = 0) { + Result res; + res.exit_code = code; + res.accepted = false; + return res; + } + + static Result success() { + Result res; + res.committed = true; + return res; + } +}; + +class PrecompiledSmartContract { + public: + virtual ~PrecompiledSmartContract() = default; + + virtual std::string get_name() const = 0; + + virtual int required_version() const { + return 6; + } + + Result run(td::Ref my_address, ton::UnixTime now, ton::LogicalTime cur_lt, CurrencyCollection balance, + td::Ref c4, vm::CellSlice msg_body, td::Ref msg, CurrencyCollection msg_balance, + bool is_external, std::vector> libraries, int global_version, td::uint16 max_data_depth, + td::Ref my_code, td::Ref unpacked_config, td::RefInt256 due_payment, td::uint64 precompiled_gas_usage); + + td::Ref get_c4() const { + return c4_; + } + td::Ref get_c5() const { + return c5_; + } + + protected: + td::Ref my_address_; + ton::UnixTime now_; + ton::LogicalTime cur_lt_; + CurrencyCollection balance_; + vm::CellSlice in_msg_body_; + td::Ref in_msg_; + CurrencyCollection in_msg_balance_; + bool is_external_; + td::Ref my_code_; + td::Ref unpacked_config_; + td::RefInt256 due_payment_; + td::uint64 precompiled_gas_usage_; + + td::Ref c4_; + td::Ref c5_ = vm::CellBuilder().finalize_novm(); + + void send_raw_message(const td::Ref& msg, int mode); + void raw_reserve(const td::RefInt256& amount, int mode); + + td::RefInt256 get_compute_fee(ton::WorkchainId wc, td::uint64 gas_used); + td::RefInt256 get_forward_fee(ton::WorkchainId wc, td::uint64 bits, td::uint64 cells); + td::RefInt256 get_storage_fee(ton::WorkchainId wc, td::uint64 duration, td::uint64 bits, td::uint64 cells); + td::RefInt256 get_simple_compute_fee(ton::WorkchainId wc, td::uint64 gas_used); + td::RefInt256 get_simple_forward_fee(ton::WorkchainId wc, td::uint64 bits, td::uint64 cells); + td::RefInt256 get_original_fwd_fee(ton::WorkchainId wc, const td::RefInt256& x); + + virtual Result do_run() = 0; +}; + +std::unique_ptr get_implementation(td::Bits256 code_hash); +void set_precompiled_execution_enabled(bool value); // disabled by default + +} // namespace block::precompiled \ No newline at end of file diff --git a/crypto/block/precompiled-smc/common.h b/crypto/block/precompiled-smc/common.h new file mode 100644 index 00000000..0406d7de --- /dev/null +++ b/crypto/block/precompiled-smc/common.h @@ -0,0 +1,21 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#pragma once +#include "PrecompiledSmartContract.h" +#include "vm/arithops.h" +#include "vm/cellops.h" +#include "vm/tonops.h" \ No newline at end of file diff --git a/crypto/block/test-weight-distr.cpp b/crypto/block/test-weight-distr.cpp new file mode 100644 index 00000000..a94b791c --- /dev/null +++ b/crypto/block/test-weight-distr.cpp @@ -0,0 +1,199 @@ +/* + This file is part of TON Blockchain source code. + + TON Blockchain is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + TON Blockchain 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TON Blockchain. If not, see . + + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement + from all source files in the program, then also delete it here. + + Copyright 2020 Telegram Systems LLP +*/ +#include +#include "td/utils/Random.h" +#include "td/utils/misc.h" +#include "block/block.h" +#include + +const int MAX_N = 1000, MAX_K = 100, DEFAULT_K = 7; + +int verbosity; +int N, K = DEFAULT_K; +long long iterations = 1000000; + +td::uint64 TWL, WL[MAX_N]; +double W[MAX_N], CW[MAX_N + 1], RW[MAX_N], R0; +int A[MAX_N], C[MAX_N]; +long long TC; + +void gen_vset() { + static std::pair H[MAX_N]; + double total_wt = 1.; + int hc = 0; + for (int i = 0; i < K; i++) { + CHECK(total_wt > 0); + double inv_wt = 1. / total_wt; + R0 += inv_wt; // advanced mtcarlo stats + for (int j = 0; j < i; j++) { + RW[A[j]] -= inv_wt; // advanced mtcarlo stats + } + // double p = drand48() * total_wt; + double p = (double)td::Random::fast_uint64() * total_wt / (1. * (1LL << 32) * (1LL << 32)); + for (int h = 0; h < hc; h++) { + if (p < H[h].first) { + break; + } + p += H[h].second; + } + int a = -1, b = N, c; + while (b - a > 1) { + c = ((a + b) >> 1); + if (CW[c] <= p) { + a = c; + } else { + b = c; + } + } + CHECK(a >= 0 && a < N); + CHECK(total_wt >= W[a]); + total_wt -= W[a]; + double x = CW[a]; + c = hc++; + while (c > 0 && H[c - 1].first > x) { + H[c] = H[c - 1]; + --c; + } + H[c].first = x; + H[c].second = W[a]; + A[i] = a; + C[a]++; // simple mtcarlo stats + // std::cout << a << ' '; + } + // std::cout << std::endl; + ++TC; // simple mtcarlo stats +} + +void mt_carlo() { + for (int i = 0; i < N; i++) { + C[i] = 0; + RW[i] = 0.; + } + TC = 0; + R0 = 0.; + std::cout << "running " << iterations << " steps of Monte Carlo simulation\n"; + for (long long it = 0; it < iterations; ++it) { + gen_vset(); + } + for (int i = 0; i < N; i++) { + RW[i] = W[i] * (RW[i] + R0) / (double)iterations; + } +} + +double B[MAX_N]; + +void compute_bad_approx() { + static double S[MAX_K + 1]; + S[0] = 1.; + for (int i = 1; i <= K; i++) { + S[i] = 0.; + } + for (int i = 0; i < N; i++) { + double p = W[i]; + for (int j = K; j > 0; j--) { + S[j] += p * S[j - 1]; + } + } + double Sk = S[K]; + for (int i = 0; i < N; i++) { + double t = 1., p = W[i]; + for (int j = 1; j <= K; j++) { + t = S[j] - p * t; + } + B[i] = 1. - t / Sk; + } +} + +void usage() { + std::cout + << "usage: test-weight-distr [-k][-m][-s]\nReads the set of validator " + "weights from stdin and emulates validator shard distribution load\n\t-k \tSets the number of " + "validators generating each shard\n\t-m \tMonte Carlo simulation steps\n"; + std::exit(2); +} + +int main(int argc, char* const argv[]) { + int i; + int new_verbosity_level = VERBOSITY_NAME(INFO); + // long seed = 0; + while ((i = getopt(argc, argv, "hs:k:m:v:")) != -1) { + switch (i) { + case 'k': + K = td::to_integer(td::Slice(optarg)); + CHECK(K > 0 && K <= 100); + break; + case 'm': + iterations = td::to_integer(td::Slice(optarg)); + CHECK(iterations > 0); + break; + case 's': + // seed = td::to_integer(td::Slice(optarg)); + // srand48(seed); + break; + case 'v': + new_verbosity_level = VERBOSITY_NAME(FATAL) + (verbosity = td::to_integer(td::Slice(optarg))); + break; + case 'h': + usage(); + std::exit(2); + default: + usage(); + std::exit(2); + } + } + SET_VERBOSITY_LEVEL(new_verbosity_level); + for (N = 0; N < MAX_N && (std::cin >> WL[N]); N++) { + CHECK(WL[N] > 0); + TWL += WL[N]; + } + CHECK(std::cin.eof()); + CHECK(N > 0 && TWL > 0 && N <= MAX_N); + K = std::min(K, N); + CHECK(K > 0 && K <= MAX_K); + double acc = 0.; + for (i = 0; i < N; i++) { + CW[i] = acc; + acc += W[i] = (double)WL[i] / (double)TWL; + std::cout << "#" << i << ":\t" << W[i] << std::endl; + } + compute_bad_approx(); + mt_carlo(); + std::cout << "result of Monte Carlo simulation (" << iterations << " iterations):" << std::endl; + std::cout << "idx\tweight\tmtcarlo1\tmtcarlo2\tapprox\n"; + for (i = 0; i < N; i++) { + std::cout << "#" << i << ":\t" << W[i] << '\t' << (double)C[i] / (double)iterations << '\t' << RW[i] << '\t' << B[i] + << std::endl; + } + // same computation, but using a MtCarloComputeShare object + block::MtCarloComputeShare MT(K, N, W, iterations); + std::cout << "-----------------------\n"; + for (i = 0; i < N; i++) { + std::cout << '#' << i << ":\t" << MT.weight(i) << '\t' << MT.share(i) << std::endl; + } + return 0; +} diff --git a/crypto/block/transaction.cpp b/crypto/block/transaction.cpp index df4cf883..24013a40 100644 --- a/crypto/block/transaction.cpp +++ b/crypto/block/transaction.cpp @@ -14,20 +14,82 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "block/transaction.h" #include "block/block.h" #include "block/block-parse.h" #include "block/block-auto.h" +#include "crypto/openssl/rand.hpp" #include "td/utils/bits.h" #include "td/utils/uint128.h" #include "ton/ton-shard.h" -#include "vm/continuation.h" +#include "vm/vm.h" +#include "td/utils/Timer.h" + +namespace { +/** + * Logger that stores the tail of log messages. + * + * @param max_size The size of the buffer. Default is 256. + */ +class StringLoggerTail : public td::LogInterface { + public: + explicit StringLoggerTail(size_t max_size = 256) : buf(max_size, '\0') {} + + /** + * Appends a slice of data to the buffer. + * + * @param slice The slice of data to be appended. + */ + void append(td::CSlice slice) override { + if (slice.size() > buf.size()) { + slice.remove_prefix(slice.size() - buf.size()); + } + while (!slice.empty()) { + size_t s = std::min(buf.size() - pos, slice.size()); + std::copy(slice.begin(), slice.begin() + s, buf.begin() + pos); + pos += s; + if (pos == buf.size()) { + pos = 0; + truncated = true; + } + slice.remove_prefix(s); + } + } + + /** + * Retrieves the tail of the log. + * + * @returns The log as std::string. + */ + std::string get_log() const { + if (truncated) { + std::string res = buf; + std::rotate(res.begin(), res.begin() + pos, res.end()); + return res; + } else { + return buf.substr(0, pos); + } + } + + private: + std::string buf; + size_t pos = 0; + bool truncated = false; +}; +} namespace block { using td::Ref; +/** + * Looks up a library among public libraries. + * + * @param key A constant bit pointer representing the key of the library to lookup. + * + * @returns A reference to the library cell if found, null otherwise. + */ Ref ComputePhaseConfig::lookup_library(td::ConstBitPtr key) const { return libraries ? vm::lookup_library_in(key, libraries->get_root_cell()) : Ref{}; } @@ -38,12 +100,27 @@ Ref ComputePhaseConfig::lookup_library(td::ConstBitPtr key) const { * */ +/** + * Sets the address of the account. + * + * @param wc The workchain ID of the account. + * @param new_addr The new address of the account. + * + * @returns True if the address was successfully set, false otherwise. + */ bool Account::set_address(ton::WorkchainId wc, td::ConstBitPtr new_addr) { workchain = wc; addr = new_addr; return true; } +/** + * Sets the split depth of the account. + * + * @param new_split_depth The new split depth value to be set. + * + * @returns True if the split depth was successfully set, False otherwise. + */ bool Account::set_split_depth(int new_split_depth) { if (new_split_depth < 0 || new_split_depth > 30) { return false; // invalid value for split_depth @@ -57,11 +134,26 @@ bool Account::set_split_depth(int new_split_depth) { } } +/** + * Checks if the given split depth is valid for the Account. + * + * @param split_depth The split depth to be checked. + * + * @returns True if the split depth is valid, False otherwise. + */ bool Account::check_split_depth(int split_depth) const { return split_depth_set_ ? (split_depth == split_depth_) : (split_depth >= 0 && split_depth <= 30); } -// initializes split_depth and addr_rewrite +/** + * Parses anycast data of the account address. + * + * Initializes split_depth and addr_rewrite. + * + * @param cs The cell slice containing partially-parsed account addressa. + * + * @returns True if parsing was successful, false otherwise. + */ bool Account::parse_maybe_anycast(vm::CellSlice& cs) { int t = (int)cs.fetch_ulong(1); if (t < 0) { @@ -76,6 +168,13 @@ bool Account::parse_maybe_anycast(vm::CellSlice& cs) { && set_split_depth(depth); } +/** + * Stores the anycast information to a serialized account address. + * + * @param cb The vm::CellBuilder object to store the information in. + * + * @returns True if the anycast information was successfully stored, false otherwise. + */ bool Account::store_maybe_anycast(vm::CellBuilder& cb) const { if (!split_depth_set_ || !split_depth_) { return cb.store_bool_bool(false); @@ -85,6 +184,13 @@ bool Account::store_maybe_anycast(vm::CellBuilder& cb) const { && cb.store_bits_bool(addr_rewrite.cbits(), split_depth_); // rewrite_pfx:(bits depth) } +/** + * Unpacks the address from a given CellSlice. + * + * @param addr_cs The CellSlice containing the address. + * + * @returns True if the address was successfully unpacked, False otherwise. + */ bool Account::unpack_address(vm::CellSlice& addr_cs) { int addr_tag = block::gen::t_MsgAddressInt.get_tag(addr_cs); int new_wc = ton::workchainInvalid; @@ -135,6 +241,15 @@ bool Account::unpack_address(vm::CellSlice& addr_cs) { return true; } +/** + * Unpacks storage information from a CellSlice. + * + * Storage information is serialized using StorageInfo TLB-scheme. + * + * @param cs The CellSlice containing the storage information. + * + * @returns True if the unpacking is successful, false otherwise. + */ bool Account::unpack_storage_info(vm::CellSlice& cs) { block::gen::StorageInfo::Record info; block::gen::StorageUsed::Record used; @@ -150,7 +265,7 @@ bool Account::unpack_storage_info(vm::CellSlice& cs) { return false; } } else { - due_payment = td::RefInt256{true, 0}; + due_payment = td::zero_refint(); } unsigned long long u = 0; u |= storage_stat.cells = block::tlb::t_VarUInteger_7.as_uint(*used.cells); @@ -161,7 +276,16 @@ bool Account::unpack_storage_info(vm::CellSlice& cs) { return (u != std::numeric_limits::max()); } -// initializes split_depth (from account state - StateInit) +/** + * Unpacks the state of an Account from a CellSlice. + * + * State is serialized using StateInit TLB-scheme. + * Initializes split_depth (from account state - StateInit) + * + * @param cs The CellSlice containing the serialized state. + * + * @returns True if the state was successfully unpacked, False otherwise. + */ bool Account::unpack_state(vm::CellSlice& cs) { block::gen::StateInit::Record state; if (!tlb::unpack_exact(cs, state)) { @@ -189,6 +313,13 @@ bool Account::unpack_state(vm::CellSlice& cs) { return true; } +/** + * Computes the address of the account. + * + * @param force If set to true, the address will be recomputed even if it already exists. + * + * @returns True if the address was successfully computed, false otherwise. + */ bool Account::compute_my_addr(bool force) { if (!force && my_addr.not_null() && my_addr_exact.not_null()) { return true; @@ -229,6 +360,15 @@ bool Account::compute_my_addr(bool force) { return true; } +/** + * Computes the address of the Account. + * + * @param tmp_addr A reference to the CellSlice for the result. + * @param split_depth The split depth for the address. + * @param orig_addr_rewrite Address prefox of length split_depth. + * + * @returns True if the address was successfully computed, false otherwise. + */ bool Account::recompute_tmp_addr(Ref& tmp_addr, int split_depth, td::ConstBitPtr orig_addr_rewrite) const { if (!split_depth && my_addr_exact.not_null()) { @@ -270,8 +410,16 @@ bool Account::recompute_tmp_addr(Ref& tmp_addr, int split_depth, (tmp_addr = vm::load_cell_slice_ref(std::move(cell))).not_null(); } +/** + * Sets address rewriting info for a newly-activated account. + * + * @param split_depth The split depth for the account address. + * @param orig_addr_rewrite Address frepix of length split_depth. + * + * @returns True if the rewriting info was successfully set, false otherwise. + */ bool Account::init_rewrite_addr(int split_depth, td::ConstBitPtr orig_addr_rewrite) { - if (split_depth_set_ || !created || !set_split_depth(split_depth)) { + if (split_depth_set_ || !set_split_depth(split_depth)) { return false; } addr_orig = addr; @@ -280,8 +428,18 @@ bool Account::init_rewrite_addr(int split_depth, td::ConstBitPtr orig_addr_rewri return compute_my_addr(true); } -// used to unpack previously existing accounts -bool Account::unpack(Ref shard_account, Ref extra, ton::UnixTime now, bool special) { +/** + * Unpacks the account information from the provided CellSlice. + * + * Used to unpack previously existing accounts. + * + * @param shard_account The ShardAccount to unpack. + * @param now The current Unix time. + * @param special Flag indicating if the account is special. + * + * @returns True if the unpacking is successful, false otherwise. + */ +bool Account::unpack(Ref shard_account, ton::UnixTime now, bool special) { LOG(DEBUG) << "unpacking " << (special ? "special " : "") << "account " << addr.to_hex(); if (shard_account.is_null()) { LOG(ERROR) << "account " << addr.to_hex() << " does not have a valid ShardAccount to unpack"; @@ -292,8 +450,7 @@ bool Account::unpack(Ref shard_account, Ref extra, block::gen::t_ShardAccount.print(std::cerr, *shard_account); } block::gen::ShardAccount::Record acc_info; - if (!(block::gen::t_ShardAccount.validate_csr(shard_account) && - block::tlb::t_ShardAccount.validate_csr(shard_account) && tlb::unpack_exact(shard_account.write(), acc_info))) { + if (!(block::tlb::t_ShardAccount.validate_csr(shard_account) && tlb::unpack_exact(shard_account.write(), acc_info))) { LOG(ERROR) << "account " << addr.to_hex() << " state is invalid"; return false; } @@ -304,21 +461,14 @@ bool Account::unpack(Ref shard_account, Ref extra, total_state = orig_total_state = account; auto acc_cs = load_cell_slice(std::move(account)); if (block::gen::t_Account.get_tag(acc_cs) == block::gen::Account::account_none) { - status = acc_nonexist; - last_paid = 0; - last_trans_end_lt_ = 0; is_special = special; - if (workchain != ton::workchainInvalid) { - addr_orig = addr; - addr_rewrite = addr.cbits(); - } - return compute_my_addr() && acc_cs.size_ext() == 1; + return acc_cs.size_ext() == 1 && init_new(now); } block::gen::Account::Record_account acc; block::gen::AccountStorage::Record storage; if (!(tlb::unpack_exact(acc_cs, acc) && (my_addr = acc.addr).not_null() && unpack_address(acc.addr.write()) && compute_my_addr() && unpack_storage_info(acc.storage_stat.write()) && - tlb::csr_unpack(std::move(acc.storage), storage) && + tlb::csr_unpack(this->storage = std::move(acc.storage), storage) && std::max(storage.last_trans_lt, 1ULL) > acc_info.last_trans_lt && balance.unpack(std::move(storage.balance)))) { return false; } @@ -328,6 +478,7 @@ bool Account::unpack(Ref shard_account, Ref extra, case block::gen::AccountState::account_uninit: status = orig_status = acc_uninit; state_hash = addr; + forget_split_depth(); break; case block::gen::AccountState::account_frozen: status = orig_status = acc_frozen; @@ -356,7 +507,13 @@ bool Account::unpack(Ref shard_account, Ref extra, return true; } -// used to initialize new accounts +/** + * Initializes a new Account object. + * + * @param now The current Unix time. + * + * @returns True if the initialization is successful, false otherwise. + */ bool Account::init_new(ton::UnixTime now) { // only workchain and addr are initialized at this point if (workchain == ton::workchainInvalid) { @@ -369,7 +526,7 @@ bool Account::init_new(ton::UnixTime now) { now_ = now; last_paid = 0; storage_stat.clear(); - due_payment = td::RefInt256{true, 0}; + due_payment = td::zero_refint(); balance.set_zero(); if (my_addr_exact.is_null()) { vm::CellBuilder cb; @@ -395,14 +552,72 @@ bool Account::init_new(ton::UnixTime now) { } state_hash = addr_orig; status = orig_status = acc_nonexist; - created = true; + split_depth_set_ = false; return true; } +/** + * Resets the split depth of the account. + * + * @returns True if the split depth was successfully reset, false otherwise. + */ +bool Account::forget_split_depth() { + split_depth_set_ = false; + split_depth_ = 0; + addr_orig = addr; + my_addr = my_addr_exact; + addr_rewrite = addr.bits(); + return true; +} + +/** + * Deactivates the account. + * + * @returns True if the account was successfully deactivated, false otherwise. + */ +bool Account::deactivate() { + if (status == acc_active) { + return false; + } + // forget special (tick/tock) info + tick = tock = false; + if (status == acc_nonexist || status == acc_uninit) { + // forget split depth and address rewriting info + forget_split_depth(); + // forget specific state hash for deleted or uninitialized accounts (revert to addr) + state_hash = addr; + } + // forget code and data (only active accounts remember these) + code.clear(); + data.clear(); + library.clear(); + // if deleted, balance must be zero + if (status == acc_nonexist && !balance.is_zero()) { + return false; + } + return true; +} + +/** + * Checks if the account belongs to a specific shard. + * + * @param shard The shard to check against. + * + * @returns True if the account belongs to the shard, False otherwise. + */ bool Account::belongs_to_shard(ton::ShardIdFull shard) const { return workchain == shard.workchain && ton::shard_is_ancestor(shard.shard, addr); } +/** + * Adds the partial storage payment to the total sum. + * + * @param payment The total sum to be updated. + * @param delta The time delta for which the payment is calculated. + * @param prices The storage prices. + * @param storage Account storage statistics. + * @param is_mc A flag indicating whether the account is in the masterchain. + */ void add_partial_storage_payment(td::BigInt256& payment, ton::UnixTime delta, const block::StoragePrices& prices, const vm::CellStorageStat& storage, bool is_mc) { td::BigInt256 c{(long long)storage.cells}, b{(long long)storage.bits}; @@ -416,16 +631,28 @@ void add_partial_storage_payment(td::BigInt256& payment, ton::UnixTime delta, co b.mul_short(prices.bit_price); } b += c; - b.mul_short(delta); + b.mul_short(delta).normalize(); CHECK(b.sgn() >= 0); payment += b; } +/** + * Computes the storage fees based on the given parameters. + * + * @param now The current Unix time. + * @param pricing The vector of storage prices. + * @param storage_stat Account storage statistics. + * @param last_paid The Unix time when the last payment was made. + * @param is_special A flag indicating if the account is special. + * @param is_masterchain A flag indicating if the account is in the masterchain. + * + * @returns The computed storage fees as RefInt256. + */ td::RefInt256 StoragePrices::compute_storage_fees(ton::UnixTime now, const std::vector& pricing, const vm::CellStorageStat& storage_stat, ton::UnixTime last_paid, bool is_special, bool is_masterchain) { if (now <= last_paid || !last_paid || is_special || pricing.empty() || now <= pricing[0].valid_since) { - return {}; + return td::zero_refint(); } std::size_t n = pricing.size(), i = n; while (i && pricing[i - 1].valid_since > last_paid) { @@ -444,14 +671,33 @@ td::RefInt256 StoragePrices::compute_storage_fees(ton::UnixTime now, const std:: } upto = valid_until; } - total.unique_write().rshift(16, 1); // divide by 2^16 with ceil rounding to obtain nanograms - return total; + return td::rshift(total, 16, 1); // divide by 2^16 with ceil rounding to obtain nanograms } +/** + * Computes the storage fees for the account. + * + * @param now The current Unix time. + * @param pricing The vector of storage prices. + * + * @returns The computed storage fees as RefInt256. + */ td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector& pricing) const { return StoragePrices::compute_storage_fees(now, pricing, storage_stat, last_paid, is_special, is_masterchain()); } +namespace transaction { +/** + * Constructs a new Transaction object. + * + * @param _account The Account object. + * @param ttype The type of the transaction (see transaction.cpp#309). + * @param req_start_lt The minimal logical time of the transaction. + * @param _now The current Unix time. + * @param _inmsg The input message that caused the transaction. + * + * @returns None + */ Transaction::Transaction(const Account& _account, int ttype, ton::LogicalTime req_start_lt, ton::UnixTime _now, Ref _inmsg) : trans_type(ttype) @@ -463,6 +709,7 @@ Transaction::Transaction(const Account& _account, int ttype, ton::LogicalTime re , my_addr(_account.my_addr) , my_addr_exact(_account.my_addr_exact) , balance(_account.balance) + , original_balance(_account.balance) , due_payment(_account.due_payment) , last_paid(_account.last_paid) , new_code(_account.code) @@ -472,8 +719,19 @@ Transaction::Transaction(const Account& _account, int ttype, ton::LogicalTime re start_lt = std::max(req_start_lt, account.last_trans_end_lt_); end_lt = start_lt + 1; acc_status = (account.status == Account::acc_nonexist ? Account::acc_uninit : account.status); + if (acc_status == Account::acc_frozen) { + frozen_hash = account.state_hash; + } } +/** + * Unpacks the input message of a transaction. + * + * @param ihr_delivered A boolean indicating whether the message was delivered using IHR (Instant Hypercube Routing). + * @param cfg Action phase configuration. + * + * @returns A boolean indicating whether the unpacking was successful. + */ bool Transaction::unpack_input_msg(bool ihr_delivered, const ActionPhaseConfig* cfg) { if (in_msg.is_null() || in_msg_type) { return false; @@ -503,7 +761,7 @@ bool Transaction::unpack_input_msg(bool ihr_delivered, const ActionPhaseConfig* if (ihr_delivered) { in_fwd_fee = std::move(ihr_fee); } else { - in_fwd_fee = td::RefInt256{true, 0}; + in_fwd_fee = td::zero_refint(); msg_balance_remaining += std::move(ihr_fee); } if (info.created_lt >= start_lt) { @@ -523,15 +781,19 @@ bool Transaction::unpack_input_msg(bool ihr_delivered, const ActionPhaseConfig* in_msg_type = 2; in_msg_extern = true; // compute forwarding fees for this external message - vm::CellStorageStat sstat; // for message size - sstat.compute_used_storage(cs); // message body - sstat.bits -= cs.size(); // bits in the root cells are free - sstat.cells--; // the root cell itself is not counted as a cell + vm::CellStorageStat sstat; // for message size + auto cell_info = sstat.compute_used_storage(cs).move_as_ok(); // message body + sstat.bits -= cs.size(); // bits in the root cells are free + sstat.cells--; // the root cell itself is not counted as a cell LOG(DEBUG) << "storage paid for a message: " << sstat.cells << " cells, " << sstat.bits << " bits"; - if (sstat.bits > max_msg_bits || sstat.cells > max_msg_cells) { + if (sstat.bits > cfg->size_limits.max_msg_bits || sstat.cells > cfg->size_limits.max_msg_cells) { LOG(DEBUG) << "inbound external message too large, invalid"; return false; } + if (cell_info.max_merkle_depth > max_allowed_merkle_depth) { + LOG(DEBUG) << "inbound external message has too big merkle depth, invalid"; + return false; + } // fetch message pricing info CHECK(cfg); const MsgPrices& msg_prices = cfg->fetch_msg_prices(account.is_masterchain()); @@ -543,7 +805,7 @@ bool Transaction::unpack_input_msg(bool ihr_delivered, const ActionPhaseConfig* LOG(DEBUG) << "computed fwd fees set to zero for special account"; fees_c.first = fees_c.second = 0; } - in_fwd_fee = td::RefInt256{true, fees_c.first}; + in_fwd_fee = td::make_refint(fees_c.first); if (balance.grams < in_fwd_fee) { LOG(DEBUG) << "cannot pay for importing this external message"; return false; @@ -564,14 +826,15 @@ bool Transaction::unpack_input_msg(bool ihr_delivered, const ActionPhaseConfig* vm::CellBuilder cb; if (!(cs.advance(2) && block::gen::t_StateInit.fetch_to(cs, state_init) && cb.append_cellslice_bool(std::move(state_init)) && cb.finalize_to(in_msg_state) && - block::gen::t_StateInit.validate_ref(in_msg_state))) { + block::gen::t_StateInitWithLibs.validate_ref(in_msg_state))) { LOG(DEBUG) << "cannot parse StateInit in inbound message"; return false; } break; } case 3: { // (just$1 (right$1 _:^StateInit )) - if (!(cs.advance(2) && cs.fetch_ref_to(in_msg_state) && block::gen::t_StateInit.validate_ref(in_msg_state))) { + if (!(cs.advance(2) && cs.fetch_ref_to(in_msg_state) && + block::gen::t_StateInitWithLibs.validate_ref(in_msg_state))) { LOG(DEBUG) << "cannot parse ^StateInit in inbound message"; return false; } @@ -600,14 +863,29 @@ bool Transaction::unpack_input_msg(bool ihr_delivered, const ActionPhaseConfig* return false; } total_fees += in_fwd_fee; + if (account.workchain == ton::masterchainId && cfg->mc_blackhole_addr && + cfg->mc_blackhole_addr.value() == account.addr) { + blackhole_burned.grams = msg_balance_remaining.grams; + msg_balance_remaining.grams = td::zero_refint(); + LOG(DEBUG) << "Burning " << blackhole_burned.grams << " nanoton (blackhole address)"; + } return true; } -bool Transaction::prepare_storage_phase(const StoragePhaseConfig& cfg, bool force_collect) { +/** + * Prepares the storage phase of a transaction. + * + * @param cfg The configuration for the storage phase. + * @param force_collect Flag indicating whether to collect fees for frozen accounts. + * @param adjust_msg_value Flag indicating whether to adjust the message value if the account balance becomes less than the message balance. + * + * @returns True if the storage phase was successfully prepared, false otherwise. + */ +bool Transaction::prepare_storage_phase(const StoragePhaseConfig& cfg, bool force_collect, bool adjust_msg_value) { if (now < account.last_paid) { return false; } - auto to_pay = account.compute_storage_fees(now, *(cfg.pricing)); + auto to_pay = account.compute_storage_fees(now, *(cfg.pricing)) + due_payment; if (to_pay.not_null() && sgn(to_pay) < 0) { return false; } @@ -615,25 +893,30 @@ bool Transaction::prepare_storage_phase(const StoragePhaseConfig& cfg, bool forc res->is_special = account.is_special; last_paid = res->last_paid_updated = (res->is_special ? 0 : now); if (to_pay.is_null() || sgn(to_pay) == 0) { - res->fees_collected = res->fees_due = td::RefInt256{true, 0}; + res->fees_collected = res->fees_due = td::zero_refint(); } else if (to_pay <= balance.grams) { res->fees_collected = to_pay; - res->fees_due = td::RefInt256{true, 0}; + res->fees_due = td::zero_refint(); balance -= std::move(to_pay); - } else if (acc_status == Account::acc_frozen && !force_collect && to_pay + due_payment < cfg.delete_due_limit) { + if (cfg.global_version >= 7) { + due_payment = td::zero_refint(); + } + } else if (acc_status == Account::acc_frozen && !force_collect && to_pay < cfg.delete_due_limit) { // do not collect fee res->last_paid_updated = (res->is_special ? 0 : account.last_paid); - res->fees_collected = res->fees_due = td::RefInt256{true, 0}; + res->fees_collected = res->fees_due = td::zero_refint(); } else { res->fees_collected = balance.grams; res->fees_due = std::move(to_pay) - std::move(balance.grams); - balance.grams = td::RefInt256{true, 0}; + balance.grams = td::zero_refint(); if (!res->is_special) { - auto total_due = res->fees_due + due_payment; + auto total_due = res->fees_due; switch (acc_status) { case Account::acc_uninit: case Account::acc_frozen: - if (total_due > cfg.delete_due_limit) { + if (total_due > cfg.delete_due_limit && balance.extra.is_null()) { + // Keeping accounts with non-null extras is a temporary measure before implementing proper collection of + // extracurrencies from deleted accounts res->deleted = true; acc_status = Account::acc_deleted; if (balance.extra.not_null()) { @@ -651,19 +934,38 @@ bool Transaction::prepare_storage_phase(const StoragePhaseConfig& cfg, bool forc } break; } + if (cfg.enable_due_payment) { + due_payment = total_due; + } } } + if (adjust_msg_value && msg_balance_remaining.grams > balance.grams) { + msg_balance_remaining.grams = balance.grams; + } total_fees += res->fees_collected; storage_phase = std::move(res); return true; } +/** + * Prepares the credit phase of a transaction. + * + * This function creates a CreditPhase object and performs the necessary calculations + * to determine the amount to be credited in the credit phase. It updates the due payment, + * credit, balance, and total fees accordingly. + * + * @returns True if the credit phase is prepared successfully, false otherwise. + */ bool Transaction::prepare_credit_phase() { credit_phase = std::make_unique(); - auto collected = std::min(msg_balance_remaining.grams, due_payment); - credit_phase->due_fees_collected = collected; - due_payment -= collected; - credit_phase->credit = msg_balance_remaining -= collected; + // Due payment is only collected in storage phase. + // For messages with bounce flag, contract always receives the amount specified in message + // auto collected = std::min(msg_balance_remaining.grams, due_payment); + // credit_phase->due_fees_collected = collected; + // due_payment -= collected; + // credit_phase->credit = msg_balance_remaining -= collected; + credit_phase->due_fees_collected = td::zero_refint(); + credit_phase->credit = msg_balance_remaining; if (!msg_balance_remaining.is_valid()) { LOG(ERROR) << "cannot compute the amount to be credited in the credit phase of transaction"; return false; @@ -674,16 +976,35 @@ bool Transaction::prepare_credit_phase() { LOG(ERROR) << "cannot credit currency collection to account"; return false; } - total_fees += std::move(collected); + // total_fees += std::move(collected); return true; } +} // namespace transaction +/** + * Parses the gas limits and prices from a given cell. + * + * @param cell The cell containing the gas limits and prices serialized using GasLimitsPricing TLB-scheme. + * @param freeze_due_limit Reference to store the freeze due limit. + * @param delete_due_limit Reference to store the delete due limit. + * + * @returns True if the parsing is successful, false otherwise. + */ bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cell, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit) { return cell.not_null() && parse_GasLimitsPrices(vm::load_cell_slice_ref(std::move(cell)), freeze_due_limit, delete_due_limit); } +/** + * Parses the gas limits and prices from a given cell slice. + * + * @param cs The cell slice containing the gas limits and prices serialized using GasLimitsPricing TLB-scheme. + * @param freeze_due_limit Reference to store the freeze due limit. + * @param delete_due_limit Reference to store the delete due limit. + * + * @returns True if the parsing is successful, false otherwise. + */ bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cs, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit) { if (cs.is_null()) { @@ -691,19 +1012,34 @@ bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cs, td::RefInt } block::gen::GasLimitsPrices::Record_gas_flat_pfx flat; if (tlb::csr_unpack(cs, flat)) { - bool ok = parse_GasLimitsPrices(std::move(flat.other), freeze_due_limit, delete_due_limit); - flat_gas_limit = flat.flat_gas_limit; - flat_gas_price = flat.flat_gas_price; - return ok; + return parse_GasLimitsPrices_internal(std::move(flat.other), freeze_due_limit, delete_due_limit, + flat.flat_gas_limit, flat.flat_gas_price); + } else { + return parse_GasLimitsPrices_internal(std::move(cs), freeze_due_limit, delete_due_limit); } - flat_gas_limit = flat_gas_price = 0; +} + +/** + * Parses the gas limits and prices from a gas limits and prices record. + * + * @param cs The cell slice containing the gas limits and prices serialized using GasLimitsPricing TLB-scheme. + * @param freeze_due_limit A reference to store the freeze due limit. + * @param delete_due_limit A reference to store the delete due limit. + * @param _flat_gas_limit The flat gas limit. + * @param _flat_gas_price The flat gas price. + * + * @returns True if the parsing is successful, false otherwise. + */ +bool ComputePhaseConfig::parse_GasLimitsPrices_internal(Ref cs, td::RefInt256& freeze_due_limit, + td::RefInt256& delete_due_limit, td::uint64 _flat_gas_limit, + td::uint64 _flat_gas_price) { auto f = [&](const auto& r, td::uint64 spec_limit) { gas_limit = r.gas_limit; special_gas_limit = spec_limit; gas_credit = r.gas_credit; gas_price = r.gas_price; - freeze_due_limit = td::RefInt256{true, r.freeze_due_limit}; - delete_due_limit = td::RefInt256{true, r.delete_due_limit}; + freeze_due_limit = td::make_refint(r.freeze_due_limit); + delete_due_limit = td::make_refint(r.delete_due_limit); }; block::gen::GasLimitsPrices::Record_gas_prices_ext rec; if (tlb::csr_unpack(cs, rec)) { @@ -716,20 +1052,70 @@ bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cs, td::RefInt return false; } } + flat_gas_limit = _flat_gas_limit; + flat_gas_price = _flat_gas_price; compute_threshold(); return true; } -void ComputePhaseConfig::compute_threshold() { - gas_price256 = td::RefInt256{true, gas_price}; - if (gas_limit > flat_gas_limit) { - max_gas_threshold = - td::rshift(gas_price256 * (gas_limit - flat_gas_limit), 16, 1) + td::make_refint(flat_gas_price); - } else { - max_gas_threshold = td::make_refint(flat_gas_price); +/** + * Checks if an address is suspended according to the ConfigParam(44). + * + * @param wc The workchain ID. + * @param addr The account address address. + * + * @returns True if the address is suspended, False otherwise. + */ +bool ComputePhaseConfig::is_address_suspended(ton::WorkchainId wc, td::Bits256 addr) const { + if (!suspended_addresses) { + return false; + } + try { + vm::CellBuilder key; + key.store_long_bool(wc, 32); + key.store_bits_bool(addr); + return !suspended_addresses->lookup(key.data_bits(), 288).is_null(); + } catch (vm::VmError) { + return false; } } +/** + * Computes the maximum gas fee based on the gas prices and limits. + * + * @param gas_price256 The gas price from config as RefInt256 + * @param gas_limit The gas limit from config + * @param flat_gas_limit The flat gas limit from config + * @param flat_gas_price The flat gas price from config + * + * @returns The maximum gas fee. + */ +static td::RefInt256 compute_max_gas_threshold(const td::RefInt256& gas_price256, td::uint64 gas_limit, + td::uint64 flat_gas_limit, td::uint64 flat_gas_price) { + if (gas_limit > flat_gas_limit) { + return td::rshift(gas_price256 * (gas_limit - flat_gas_limit), 16, 1) + td::make_bigint(flat_gas_price); + } else { + return td::make_refint(flat_gas_price); + } +} + +/** + * Computes the maximum for gas fee based on the gas prices and limits. + * + * Updates max_gas_threshold. + */ +void ComputePhaseConfig::compute_threshold() { + gas_price256 = td::make_refint(gas_price); + max_gas_threshold = compute_max_gas_threshold(gas_price256, gas_limit, flat_gas_limit, flat_gas_price); +} + +/** + * Computes the amount of gas that can be bought for a given amount of nanograms. + * + * @param nanograms The amount of nanograms to compute gas for. + * + * @returns The amount of gas. + */ td::uint64 ComputePhaseConfig::gas_bought_for(td::RefInt256 nanograms) const { if (nanograms.is_null() || sgn(nanograms) < 0) { return 0; @@ -744,35 +1130,121 @@ td::uint64 ComputePhaseConfig::gas_bought_for(td::RefInt256 nanograms) const { return res->to_long() + flat_gas_limit; } +/** + * Computes the gas price. + * + * @param gas_used The amount of gas used. + * + * @returns The computed gas price. + */ td::RefInt256 ComputePhaseConfig::compute_gas_price(td::uint64 gas_used) const { return gas_used <= flat_gas_limit ? td::make_refint(flat_gas_price) : td::rshift(gas_price256 * (gas_used - flat_gas_limit), 16, 1) + flat_gas_price; } +namespace transaction { + +/** + * Checks if it is required to increase gas_limit (from GasLimitsPrices config) to special_gas_limit * 2 + * from masterchain GasLimitsPrices config for the transaction. + * + * In January 2024 a highload wallet of @wallet Telegram bot in mainnet was stuck because current gas limit (1M) is + * not enough to clean up old queires, thus locking funds inside. + * See comment in crypto/smartcont/highload-wallet-v2-code.fc for details on why this happened. + * Account address: EQD_v9j1rlsuHHw2FIhcsCFFSD367ldfDdCKcsNmNpIRzUlu + * It was proposed to validators to increase gas limit for this account for a limited amount of time (until 2024-02-29). + * It is activated by setting global version to 5 in ConfigParam 8. + * This config change also activates new behavior for special accounts in masterchain. + * + * @param cfg The compute phase configuration. + * @param now The Unix time of the transaction. + * @param account The account of the transaction. + * + * @returns True if gas_limit override is required, false otherwise + */ +static bool override_gas_limit(const ComputePhaseConfig& cfg, ton::UnixTime now, const Account& account) { + if (!cfg.special_gas_full) { + return false; + } + ton::UnixTime until = 1709164800; // 2024-02-29 00:00:00 UTC + ton::WorkchainId wc = 0; + const char* addr_hex = "FFBFD8F5AE5B2E1C7C3614885CB02145483DFAEE575F0DD08A72C366369211CD"; + return now < until && account.workchain == wc && account.addr.to_hex() == addr_hex; +} + +/** + * Computes the amount of gas that can be bought for a given amount of nanograms. + * Usually equal to `cfg.gas_bought_for(nanograms)` + * However, it overrides gas_limit from config in special cases. + * + * @param cfg The compute phase configuration. + * @param nanograms The amount of nanograms to compute gas for. + * + * @returns The amount of gas. + */ +td::uint64 Transaction::gas_bought_for(const ComputePhaseConfig& cfg, td::RefInt256 nanograms) { + if (override_gas_limit(cfg, now, account)) { + gas_limit_overridden = true; + // Same as ComputePhaseConfig::gas_bought for, but with other gas_limit and max_gas_threshold + auto gas_limit = cfg.mc_gas_prices.special_gas_limit * 2; + auto max_gas_threshold = + compute_max_gas_threshold(cfg.gas_price256, gas_limit, cfg.flat_gas_limit, cfg.flat_gas_price); + if (nanograms.is_null() || sgn(nanograms) < 0) { + return 0; + } + if (nanograms >= max_gas_threshold) { + return gas_limit; + } + if (nanograms < cfg.flat_gas_price) { + return 0; + } + auto res = td::div((std::move(nanograms) - cfg.flat_gas_price) << 16, cfg.gas_price256); + return res->to_long() + cfg.flat_gas_limit; + } + return cfg.gas_bought_for(nanograms); +} + +/** + * Computes the gas limits for a transaction. + * + * @param cp The ComputePhase object to store the computed gas limits. + * @param cfg The compute phase configuration. + * + * @returns True if the gas limits were successfully computed, false otherwise. + */ bool Transaction::compute_gas_limits(ComputePhase& cp, const ComputePhaseConfig& cfg) { // Compute gas limits if (account.is_special) { cp.gas_max = cfg.special_gas_limit; } else { - cp.gas_max = cfg.gas_bought_for(balance.grams); + cp.gas_max = gas_bought_for(cfg, balance.grams); } - cp.gas_credit = 0; - if (trans_type != tr_ord) { + if (trans_type != tr_ord || (account.is_special && cfg.special_gas_full)) { // may use all gas that can be bought using remaining balance cp.gas_limit = cp.gas_max; } else { // originally use only gas bought using remaining message balance // if the message is "accepted" by the smart contract, the gas limit will be set to gas_max - cp.gas_limit = cfg.gas_bought_for(msg_balance_remaining.grams); - if (!block::tlb::t_Message.is_internal(in_msg)) { - // external messages carry no balance, give them some credit to check whether they are accepted - cp.gas_credit = std::min(cfg.gas_credit, cp.gas_max); - } + cp.gas_limit = std::min(gas_bought_for(cfg, msg_balance_remaining.grams), cp.gas_max); + } + if (trans_type == tr_ord && !block::tlb::t_Message.is_internal(in_msg)) { + // external messages carry no balance, give them some credit to check whether they are accepted + cp.gas_credit = std::min(cfg.gas_credit, cp.gas_max); + } else { + cp.gas_credit = 0; } LOG(DEBUG) << "gas limits: max=" << cp.gas_max << ", limit=" << cp.gas_limit << ", credit=" << cp.gas_credit; return true; } +/** + * Prepares a TVM stack for a transaction. + * + * @param cp The compute phase object. + * + * @returns A reference to the prepared virtual machine stack. + * Returns an empty reference if the transaction type is invalid. + */ Ref Transaction::prepare_vm_stack(ComputePhase& cp) { Ref stack_ref{true}; td::RefInt256 acc_addr{true}; @@ -799,6 +1271,14 @@ Ref Transaction::prepare_vm_stack(ComputePhase& cp) { } } +/** + * Prepares a random seed for a transaction. + * + * @param rand_seed The output random seed. + * @param cfg The configuration for the compute phase. + * + * @returns True if the random seed was successfully prepared, false otherwise. + */ bool Transaction::prepare_rand_seed(td::BitArray<256>& rand_seed, const ComputePhaseConfig& cfg) const { // we might use SHA256(block_rand_seed . addr . trans_lt) // instead, we use SHA256(block_rand_seed . addr) @@ -811,6 +1291,15 @@ bool Transaction::prepare_rand_seed(td::BitArray<256>& rand_seed, const ComputeP return true; } +/** + * Prepares the c7 tuple (virtual machine context) for a compute phase of a transaction. + * + * @param cfg The configuration for the compute phase. + * + * @returns A reference to a Tuple object. + * + * @throws CollatorError if the rand_seed cannot be computed for the transaction. + */ Ref Transaction::prepare_vm_c7(const ComputePhaseConfig& cfg) const { td::BitArray<256> rand_seed; td::RefInt256 rand_seed_int{true}; @@ -819,31 +1308,81 @@ Ref Transaction::prepare_vm_c7(const ComputePhaseConfig& cfg) const { throw CollatorError{"cannot generate valid SmartContractInfo"}; return {}; } - auto tuple = vm::make_tuple_ref( + std::vector tuple = { td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea - td::make_refint(0), // actions:Integer - td::make_refint(0), // msgs_sent:Integer + td::zero_refint(), // actions:Integer + td::zero_refint(), // msgs_sent:Integer td::make_refint(now), // unixtime:Integer td::make_refint(account.block_lt), // block_lt:Integer td::make_refint(start_lt), // trans_lt:Integer std::move(rand_seed_int), // rand_seed:Integer balance.as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)] - my_addr, // myself:MsgAddressInt - vm::StackEntry::maybe(cfg.global_config)); // global_config:(Maybe Cell) ] = SmartContractInfo; - LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string(); - return vm::make_tuple_ref(std::move(tuple)); + my_addr, // myself:MsgAddressInt + vm::StackEntry::maybe(cfg.global_config) // global_config:(Maybe Cell) ] = SmartContractInfo; + }; + if (cfg.global_version >= 4) { + tuple.push_back(vm::StackEntry::maybe(new_code)); // code:Cell + if (msg_balance_remaining.is_valid()) { + tuple.push_back(msg_balance_remaining.as_vm_tuple()); // in_msg_value:[Integer (Maybe Cell)] + } else { + tuple.push_back(block::CurrencyCollection::zero().as_vm_tuple()); + } + tuple.push_back(storage_phase->fees_collected); // storage_fees:Integer + + // See crypto/block/mc-config.cpp#2223 (get_prev_blocks_info) + // [ wc:Integer shard:Integer seqno:Integer root_hash:Integer file_hash:Integer] = BlockId; + // [ last_mc_blocks:[BlockId...] + // prev_key_block:BlockId ] : PrevBlocksInfo + // The only context where PrevBlocksInfo (13 parameter of c7) is null is inside emulator + // where it need to be set via transaction_emulator_set_prev_blocks_info (see emulator/emulator-extern.cpp) + // Inside validator, collator and liteserver checking external message contexts + // prev_blocks_info is always not null, since get_prev_blocks_info() + // may only return tuple or raise Error (See crypto/block/mc-config.cpp#2223) + tuple.push_back(vm::StackEntry::maybe(cfg.prev_blocks_info)); + } + if (cfg.global_version >= 6) { + tuple.push_back(vm::StackEntry::maybe(cfg.unpacked_config_tuple)); // unpacked_config_tuple:[...] + tuple.push_back(due_payment.not_null() ? due_payment : td::zero_refint()); // due_payment:Integer + tuple.push_back(compute_phase->precompiled_gas_usage + ? vm::StackEntry(td::make_refint(compute_phase->precompiled_gas_usage.value())) + : vm::StackEntry()); // precompiled_gas_usage:Integer + } + auto tuple_ref = td::make_cnt_ref>(std::move(tuple)); + LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple_ref).to_string(); + return vm::make_tuple_ref(std::move(tuple_ref)); } +/** + * Computes the number of output actions in a list. + * + * @param list c5 cell. + * + * @returns The number of output actions. + */ int output_actions_count(Ref list) { int i = -1; do { ++i; - list = load_cell_slice(std::move(list)).prefetch_ref(); + bool special = true; + auto cs = load_cell_slice_special(std::move(list), special); + if (special) { + break; + } + list = cs.prefetch_ref(); } while (list.not_null()); return i; } -bool Transaction::unpack_msg_state(bool lib_only) { +/** + * Unpacks the message StateInit. + * + * @param cfg The configuration for the compute phase. + * @param lib_only If true, only unpack libraries from the state. + * @param forbid_public_libs Don't allow public libraries in initstate. + * + * @returns True if the unpacking is successful, false otherwise. + */ +bool Transaction::unpack_msg_state(const ComputePhaseConfig& cfg, bool lib_only, bool forbid_public_libs) { block::gen::StateInit::Record state; if (in_msg_state.is_null() || !tlb::unpack_cell(in_msg_state, state)) { LOG(ERROR) << "cannot unpack StateInit from an inbound message"; @@ -867,12 +1406,32 @@ bool Transaction::unpack_msg_state(bool lib_only) { new_tock = z & 1; LOG(DEBUG) << "tick=" << new_tick << ", tock=" << new_tock; } + td::Ref old_code = new_code, old_data = new_data, old_library = new_library; new_code = state.code->prefetch_ref(); new_data = state.data->prefetch_ref(); new_library = state.library->prefetch_ref(); + auto size_limits = cfg.size_limits; + if (forbid_public_libs) { + size_limits.max_acc_public_libraries = 0; + } + auto S = check_state_limits(size_limits, false); + if (S.is_error()) { + LOG(DEBUG) << "Cannot unpack msg state: " << S.move_as_error(); + new_code = old_code; + new_data = old_data; + new_library = old_library; + return false; + } return true; } +/** + * Computes the set of libraries to be used during TVM execution. + * + * @param cfg The configuration for the compute phase. + * + * @returns A vector of hashmaps with libraries. + */ std::vector> Transaction::compute_vm_libraries(const ComputePhaseConfig& cfg) { std::vector> lib_set; if (in_msg_library.not_null()) { @@ -888,6 +1447,11 @@ std::vector> Transaction::compute_vm_libraries(const ComputePhaseC return lib_set; } +/** + * Checks if the input message StateInit hash corresponds to the account address. + * + * @returns True if the input message state hash is valid, False otherwise. + */ bool Transaction::check_in_msg_state_hash() { CHECK(in_msg_state.not_null()); CHECK(new_split_depth >= 0 && new_split_depth < 32); @@ -901,11 +1465,93 @@ bool Transaction::check_in_msg_state_hash() { return account.recompute_tmp_addr(my_addr, d, orig_addr_rewrite.bits()); } +/** + * Runs the precompiled smart contract and prepares the compute phase. + * + * @param cfg The configuration for the compute phase. + * @param impl Implementation of the smart contract + * + * @returns True if the contract was successfully executed, false otherwise. + */ +bool Transaction::run_precompiled_contract(const ComputePhaseConfig& cfg, precompiled::PrecompiledSmartContract& impl) { + ComputePhase& cp = *compute_phase; + CHECK(cp.precompiled_gas_usage); + td::uint64 gas_usage = cp.precompiled_gas_usage.value(); + td::Timer timer; + auto result = + impl.run(my_addr, now, start_lt, balance, new_data, *in_msg_body, in_msg, msg_balance_remaining, in_msg_extern, + compute_vm_libraries(cfg), cfg.global_version, cfg.max_vm_data_depth, new_code, + cfg.unpacked_config_tuple, due_payment.not_null() ? due_payment : td::zero_refint(), gas_usage); + double elapsed = timer.elapsed(); + cp.vm_init_state_hash = td::Bits256::zero(); + cp.exit_code = result.exit_code; + cp.out_of_gas = false; + cp.vm_final_state_hash = td::Bits256::zero(); + cp.vm_steps = 0; + cp.gas_used = gas_usage; + cp.accepted = result.accepted; + cp.success = (cp.accepted && result.committed); + LOG(INFO) << "Running precompiled smart contract " << impl.get_name() << ": exit_code=" << result.exit_code + << " accepted=" << result.accepted << " success=" << cp.success << " gas_used=" << gas_usage + << " time=" << elapsed << "s"; + if (cp.accepted & use_msg_state) { + was_activated = true; + acc_status = Account::acc_active; + } + if (cfg.with_vm_log) { + cp.vm_log = PSTRING() << "Running precompiled smart contract " << impl.get_name() + << ": exit_code=" << result.exit_code << " accepted=" << result.accepted + << " success=" << cp.success << " gas_used=" << gas_usage << " time=" << elapsed << "s"; + } + if (cp.success) { + cp.new_data = impl.get_c4(); + cp.actions = impl.get_c5(); + int out_act_num = output_actions_count(cp.actions); + if (verbosity > 2) { + std::cerr << "new smart contract data: "; + bool can_be_special = true; + load_cell_slice_special(cp.new_data, can_be_special).print_rec(std::cerr); + std::cerr << "output actions: "; + block::gen::OutList{out_act_num}.print_ref(std::cerr, cp.actions); + } + } + cp.mode = 0; + cp.exit_arg = 0; + if (!cp.success && result.exit_arg) { + auto value = td::narrow_cast_safe(result.exit_arg.value()); + if (value.is_ok()) { + cp.exit_arg = value.ok(); + } + } + if (cp.accepted) { + if (account.is_special) { + cp.gas_fees = td::zero_refint(); + } else { + cp.gas_fees = cfg.compute_gas_price(cp.gas_used); + total_fees += cp.gas_fees; + balance -= cp.gas_fees; + } + LOG(DEBUG) << "gas fees: " << cp.gas_fees->to_dec_string() << " = " << cfg.gas_price256->to_dec_string() << " * " + << cp.gas_used << " /2^16 ; price=" << cfg.gas_price << "; flat rate=[" << cfg.flat_gas_price << " for " + << cfg.flat_gas_limit << "]; remaining balance=" << balance.to_str(); + CHECK(td::sgn(balance.grams) >= 0); + } + return true; +} + +/** + * Prepares the compute phase of a transaction, which includes running TVM. + * + * @param cfg The configuration for the compute phase. + * + * @returns True if the compute phase was successfully prepared and executed, false otherwise. + */ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { // TODO: add more skip verifications + sometimes use state from in_msg to re-activate // ... compute_phase = std::make_unique(); ComputePhase& cp = *(compute_phase.get()); + original_balance -= total_fees; if (td::sgn(balance.grams) <= 0) { // no gas cp.skip_reason = ComputePhase::sk_no_gas; @@ -931,8 +1577,15 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { if (in_msg_state.not_null() && (acc_status == Account::acc_uninit || (acc_status == Account::acc_frozen && account.state_hash == in_msg_state->get_hash().bits()))) { + if (acc_status == Account::acc_uninit && cfg.is_address_suspended(account.workchain, account.addr)) { + LOG(DEBUG) << "address is suspended, skipping compute phase"; + cp.skip_reason = ComputePhase::sk_suspended; + return true; + } use_msg_state = true; - if (!(unpack_msg_state() && account.check_split_depth(new_split_depth))) { + bool forbid_public_libs = + acc_status == Account::acc_uninit && account.is_masterchain(); // Forbid for deploying, allow for unfreezing + if (!(unpack_msg_state(cfg, false, forbid_public_libs) && account.check_split_depth(new_split_depth))) { LOG(DEBUG) << "cannot unpack in_msg_state, or it has bad split_depth; cannot init account state"; cp.skip_reason = ComputePhase::sk_bad_state; return true; @@ -947,8 +1600,40 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { cp.skip_reason = in_msg_state.not_null() ? ComputePhase::sk_bad_state : ComputePhase::sk_no_state; return true; } else if (in_msg_state.not_null()) { - unpack_msg_state(true); // use only libraries + unpack_msg_state(cfg, true); // use only libraries } + if (in_msg_extern && in_msg_state.not_null() && account.addr != in_msg_state->get_hash().bits()) { + LOG(DEBUG) << "in_msg_state hash mismatch in external message"; + cp.skip_reason = ComputePhase::sk_bad_state; + return true; + } + + td::optional precompiled; + if (new_code.not_null() && trans_type == tr_ord) { + precompiled = cfg.precompiled_contracts.get_contract(new_code->get_hash().bits()); + } + + vm::GasLimits gas{(long long)cp.gas_limit, (long long)cp.gas_max, (long long)cp.gas_credit}; + if (precompiled) { + td::uint64 gas_usage = precompiled.value().gas_usage; + cp.precompiled_gas_usage = gas_usage; + if (gas_usage > cp.gas_limit) { + cp.skip_reason = ComputePhase::sk_no_gas; + return true; + } + auto impl = precompiled::get_implementation(new_code->get_hash().bits()); + if (impl != nullptr && !cfg.dont_run_precompiled_ && impl->required_version() <= cfg.global_version) { + return run_precompiled_contract(cfg, *impl); + } + + // Contract is marked as precompiled in global config, but implementation is not available + // In this case we run TVM and override gas_used + LOG(INFO) << "Unknown precompiled contract (code_hash=" << new_code->get_hash().to_hex() + << ", gas_usage=" << gas_usage << "), running VM"; + long long limit = account.is_special ? cfg.special_gas_limit : cfg.gas_limit; + gas = vm::GasLimits{limit, limit, gas.gas_credit ? limit : 0}; + } + // initialize VM Ref stack = prepare_vm_stack(cp); if (stack.is_null()) { @@ -957,16 +1642,41 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { } // OstreamLogger ostream_logger(error_stream); // auto log = create_vm_log(error_stream ? &ostream_logger : nullptr); - vm::GasLimits gas{(long long)cp.gas_limit, (long long)cp.gas_max, (long long)cp.gas_credit}; LOG(DEBUG) << "creating VM"; - vm::VmState vm{new_code, std::move(stack), gas, 1, new_data, vm::VmLog(), compute_vm_libraries(cfg)}; + std::unique_ptr logger; + auto vm_log = vm::VmLog(); + if (cfg.with_vm_log) { + size_t log_max_size = cfg.vm_log_verbosity > 0 ? 1024 * 1024 : 256; + logger = std::make_unique(log_max_size); + vm_log.log_interface = logger.get(); + vm_log.log_options = td::LogOptions(VERBOSITY_NAME(DEBUG), true, false); + if (cfg.vm_log_verbosity > 1) { + vm_log.log_mask |= vm::VmLog::ExecLocation; + if (cfg.vm_log_verbosity > 2) { + vm_log.log_mask |= vm::VmLog::GasRemaining; + if (cfg.vm_log_verbosity > 3) { + vm_log.log_mask |= vm::VmLog::DumpStack; + if (cfg.vm_log_verbosity > 4) { + vm_log.log_mask |= vm::VmLog::DumpStackVerbose; + } + } + } + } + } + vm::VmState vm{new_code, std::move(stack), gas, 1, new_data, vm_log, compute_vm_libraries(cfg)}; + vm.set_max_data_depth(cfg.max_vm_data_depth); + vm.set_global_version(cfg.global_version); vm.set_c7(prepare_vm_c7(cfg)); // tuple with SmartContractInfo + vm.set_chksig_always_succeed(cfg.ignore_chksig); + vm.set_stop_on_accept_message(cfg.stop_on_accept_message); // vm.incr_stack_trace(1); // enable stack dump after each step LOG(DEBUG) << "starting VM"; cp.vm_init_state_hash = vm.get_state_hash(); + td::Timer timer; cp.exit_code = ~vm.run(); + double elapsed = timer.elapsed(); LOG(DEBUG) << "VM terminated with exit code " << cp.exit_code; cp.out_of_gas = (cp.exit_code == ~(int)vm::Excno::out_of_gas); cp.vm_final_state_hash = vm.get_final_state_hash(cp.exit_code); @@ -980,16 +1690,30 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { was_activated = true; acc_status = Account::acc_active; } + if (precompiled) { + cp.gas_used = precompiled.value().gas_usage; + cp.vm_steps = 0; + cp.vm_init_state_hash = cp.vm_final_state_hash = td::Bits256::zero(); + if (cp.out_of_gas) { + LOG(ERROR) << "Precompiled smc got out_of_gas in TVM"; + return false; + } + } LOG(INFO) << "steps: " << vm.get_steps_count() << " gas: used=" << gas.gas_consumed() << ", max=" << gas.gas_max << ", limit=" << gas.gas_limit << ", credit=" << gas.gas_credit; - LOG(INFO) << "out_of_gas=" << cp.out_of_gas << ", accepted=" << cp.accepted << ", success=" << cp.success; + LOG(INFO) << "out_of_gas=" << cp.out_of_gas << ", accepted=" << cp.accepted << ", success=" << cp.success + << ", time=" << elapsed << "s"; + if (logger != nullptr) { + cp.vm_log = logger->get_log(); + } if (cp.success) { cp.new_data = vm.get_committed_state().c4; // c4 -> persistent data cp.actions = vm.get_committed_state().c5; // c5 -> action list int out_act_num = output_actions_count(cp.actions); if (verbosity > 2) { std::cerr << "new smart contract data: "; - load_cell_slice(cp.new_data).print_rec(std::cerr); + bool can_be_special = true; + load_cell_slice_special(cp.new_data, can_be_special).print_rec(std::cerr); std::cerr << "output actions: "; block::gen::OutList{out_act_num}.print_ref(std::cerr, cp.actions); } @@ -1004,21 +1728,27 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { } if (cp.accepted) { if (account.is_special) { - cp.gas_fees = td::RefInt256{true, 0}; + cp.gas_fees = td::zero_refint(); } else { cp.gas_fees = cfg.compute_gas_price(cp.gas_used); total_fees += cp.gas_fees; balance -= cp.gas_fees; } - if (verbosity > 2) { - std::cerr << "gas fees: " << cp.gas_fees << " = " << cfg.gas_price256 << " * " << cp.gas_used - << " /2^16 ; price=" << cfg.gas_price << "; remaining balance=" << balance << std::endl; - } + LOG(DEBUG) << "gas fees: " << cp.gas_fees->to_dec_string() << " = " << cfg.gas_price256->to_dec_string() << " * " + << cp.gas_used << " /2^16 ; price=" << cfg.gas_price << "; flat rate=[" << cfg.flat_gas_price << " for " + << cfg.flat_gas_limit << "]; remaining balance=" << balance.to_str(); CHECK(td::sgn(balance.grams) >= 0); } return true; } +/** + * Prepares the action phase of a transaction. + * + * @param cfg The configuration for the action phase. + * + * @returns True if the action phase was prepared successfully, false otherwise. + */ bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) { if (!compute_phase || !compute_phase->success) { return false; @@ -1033,14 +1763,43 @@ bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) { ap.action_list_hash = list->get_hash().bits(); ap.remaining_balance = balance; ap.end_lt = end_lt; - ap.total_fwd_fees = td::RefInt256{true, 0}; - ap.total_action_fees = td::RefInt256{true, 0}; + ap.total_fwd_fees = td::zero_refint(); + ap.total_action_fees = td::zero_refint(); ap.reserved_balance.set_zero(); + ap.action_fine = td::zero_refint(); + + td::Ref old_code = new_code, old_data = new_data, old_library = new_library; + auto enforce_state_limits = [&]() { + if (account.is_special) { + return true; + } + auto S = check_state_limits(cfg.size_limits); + if (S.is_error()) { + // Rollback changes to state, fail action phase + LOG(INFO) << "Account state size exceeded limits: " << S.move_as_error(); + new_storage_stat.clear(); + new_code = old_code; + new_data = old_data; + new_library = old_library; + ap.result_code = 50; + ap.state_exceeds_limits = true; + return false; + } + return true; + }; int n = 0; while (true) { ap.action_list.push_back(list); - auto cs = load_cell_slice(std::move(list)); + bool special = true; + auto cs = load_cell_slice_special(std::move(list), special); + if (special) { + ap.result_code = 32; // action list invalid + ap.result_arg = n; + ap.action_list_invalid = true; + LOG(DEBUG) << "action list invalid: special cell"; + return true; + } if (!cs.size_ext()) { break; } @@ -1082,6 +1841,7 @@ bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) { int tag = block::gen::t_OutAction.get_tag(cs); CHECK(tag >= 0); int err_code = 34; + ap.need_bounce_on_fail = false; switch (tag) { case block::gen::OutAction::action_set_code: err_code = try_action_set_code(cs, ap, cfg); @@ -1112,9 +1872,33 @@ bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) { ap.no_funds = true; } LOG(DEBUG) << "invalid action " << ap.result_arg << " in action list: error code " << ap.result_code; + // This is reuqired here because changes to libraries are applied even if actipn phase fails + enforce_state_limits(); + if (cfg.action_fine_enabled) { + ap.action_fine = std::min(ap.action_fine, balance.grams); + ap.total_action_fees = ap.action_fine; + balance.grams -= ap.action_fine; + total_fees += ap.action_fine; + } + if (ap.need_bounce_on_fail) { + ap.bounce = true; + } return true; } } + + if (cfg.action_fine_enabled) { + ap.total_action_fees += ap.action_fine; + } + end_lt = ap.end_lt; + if (ap.new_code.not_null()) { + new_code = ap.new_code; + } + new_data = compute_phase->new_data; // tentative persistent data update applied + if (!enforce_state_limits()) { + return true; + } + ap.result_arg = 0; ap.result_code = 0; CHECK(ap.remaining_balance.grams->sgn() >= 0); @@ -1128,18 +1912,22 @@ bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) { was_deleted = true; } ap.success = true; - end_lt = ap.end_lt; out_msgs = std::move(ap.out_msgs); - if (ap.new_code.not_null()) { - new_code = ap.new_code; - } - new_data = compute_phase->new_data; // tentative persistent data update applied total_fees += ap.total_action_fees; // NB: forwarding fees are not accounted here (they are not collected by the validators in this transaction) balance = ap.remaining_balance; return true; } +/** + * Tries to set the code for an account. + * + * @param cs The CellSlice containing the action data serialized as action_set_code TLB-scheme. + * @param ap The action phase object. + * @param cfg The action phase configuration. + * + * @returns 0 if the code was successfully set, -1 otherwise. + */ int Transaction::try_action_set_code(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg) { block::gen::OutAction::Record_action_set_code rec; if (!tlb::unpack_exact(cs, rec)) { @@ -1151,12 +1939,35 @@ int Transaction::try_action_set_code(vm::CellSlice& cs, ActionPhase& ap, const A return 0; } +/** + * Tries to change the library in the transaction. + * + * @param cs The cell slice containing the action data serialized as action_change_library TLB-scheme. + * @param ap The action phase object. + * @param cfg The action phase configuration. + * + * @returns 0 if the action was successfully performed, + * -1 if there was an error unpacking the data or the mode is invalid, + * 41 if the library reference is required but is null, + * 43 if the number of cells in the library exceeds the limit, + * 42 if there was a VM error during the operation. + */ int Transaction::try_action_change_library(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg) { block::gen::OutAction::Record_action_change_library rec; if (!tlb::unpack_exact(cs, rec)) { return -1; } - // mode: +0 = remove library, +1 = add private library, +2 = add public library + // mode: +0 = remove library, +1 = add private library, +2 = add public library, +16 - bounce on fail + if (rec.mode & 16) { + if (!cfg.bounce_on_fail_enabled) { + return -1; + } + ap.need_bounce_on_fail = true; + rec.mode &= ~16; + } + if (rec.mode > 2) { + return -1; + } Ref lib_ref = rec.libref->prefetch_ref(); ton::Bits256 hash; if (lib_ref.not_null()) { @@ -1188,6 +1999,11 @@ int Transaction::try_action_change_library(vm::CellSlice& cs, ActionPhase& ap, c // library code not found return 41; } + vm::CellStorageStat sstat; + auto cell_info = sstat.compute_used_storage(lib_ref).move_as_ok(); + if (sstat.cells > cfg.size_limits.max_library_cells || cell_info.max_merkle_depth > max_allowed_merkle_depth) { + return 43; + } vm::CellBuilder cb; CHECK(cb.store_bool_bool(rec.mode >> 1) && cb.store_ref_bool(std::move(lib_ref))); CHECK(dict.set_builder(hash, cb)); @@ -1200,10 +2016,20 @@ int Transaction::try_action_change_library(vm::CellSlice& cs, ActionPhase& ap, c ap.spec_actions++; return 0; } +} // namespace transaction -// msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms -// ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms -// bits in the root cell of a message are not included in msg.bits (lump_price pays for them) +/** + * Computes the forward fees for a message based on the number of cells and bits. + * + * msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms + * ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms + * bits in the root cell of a message are not included in msg.bits (lump_price pays for them) + * + * @param cells The number of cells in the message. + * @param bits The number of bits in the message. + * + * @returns The computed forward fees for the message. + */ td::uint64 MsgPrices::compute_fwd_fees(td::uint64 cells, td::uint64 bits) const { return lump_price + td::uint128(bit_price) .mult(bits) @@ -1213,6 +2039,34 @@ td::uint64 MsgPrices::compute_fwd_fees(td::uint64 cells, td::uint64 bits) const .lo(); } +/** + * Computes the forward fees for a message based on the number of cells and bits. + * Return the result as td::RefInt256 + * + * msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms + * ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms + * bits in the root cell of a message are not included in msg.bits (lump_price pays for them) + * + * @param cells The number of cells in the message. + * @param bits The number of bits in the message. + * + * @returns The computed forward fees for the message as td::RefInt256j. + */ +td::RefInt256 MsgPrices::compute_fwd_fees256(td::uint64 cells, td::uint64 bits) const { + return td::make_refint(lump_price) + + td::rshift(td::make_refint(bit_price) * bits + td::make_refint(cell_price) * cells, 16, + 1); // divide by 2^16 with ceil rounding +} + +/** + * Computes the forward fees and IHR fees for a message with the given number of cells and bits. + * + * @param cells The number of cells. + * @param bits The number of bits. + * @param ihr_disabled Flag indicating whether IHR is disabled. + * + * @returns A pair of values representing the forward fees and IHR fees. + */ std::pair MsgPrices::compute_fwd_ihr_fees(td::uint64 cells, td::uint64 bits, bool ihr_disabled) const { td::uint64 fwd = compute_fwd_fees(cells, bits); @@ -1222,18 +2076,47 @@ std::pair MsgPrices::compute_fwd_ihr_fees(td::uint64 cel return std::pair(fwd, td::uint128(fwd).mult(ihr_factor).shr(16).lo()); } +/** + * Computes the part of the fees that go to the total fees of the current block. + * + * @param total The amount of fees. + * + * @returns The the part of the fees that go to the total fees of the current block. + */ td::RefInt256 MsgPrices::get_first_part(td::RefInt256 total) const { return (std::move(total) * first_frac) >> 16; } +/** + * Computes the part of the fees that go to the total fees of the current block. + * + * @param total The amount of fees. + * + * @returns The the part of the fees that go to the total fees of the current block. + */ td::uint64 MsgPrices::get_first_part(td::uint64 total) const { return td::uint128(total).mult(first_frac).shr(16).lo(); } +/** + * Computes the part of the fees that go to the total fees of the transit block. + * + * @param total The amount of fees. + * + * @returns The the part of the fees that go to the total fees of the transit block. + */ td::RefInt256 MsgPrices::get_next_part(td::RefInt256 total) const { return (std::move(total) * next_frac) >> 16; } +namespace transaction { +/** + * Checks if the source address is addr_none and replaces is with the account address. + * + * @param src_addr A reference to the source address of the message. + * + * @returns True if the source address is addr_none or is equal to the account address. + */ bool Transaction::check_replace_src_addr(Ref& src_addr) const { int t = (int)src_addr->prefetch_ulong(2); if (!t && src_addr->size_ext() == 2) { @@ -1254,6 +2137,15 @@ bool Transaction::check_replace_src_addr(Ref& src_addr) const { return false; } +/** + * Checks the destination address of a message, rewrites it if it is an anycast address. + * + * @param dest_addr A reference to the destination address of the transaction. + * @param cfg The configuration for the action phase. + * @param is_mc A pointer to a boolean where it will be stored whether the destination is in the masterchain. + * + * @returns True if the destination address is valid, false otherwise. + */ bool Transaction::check_rewrite_dest_addr(Ref& dest_addr, const ActionPhaseConfig& cfg, bool* is_mc) const { if (!dest_addr->prefetch_ulong(1)) { @@ -1316,11 +2208,6 @@ bool Transaction::check_rewrite_dest_addr(Ref& dest_addr, const A } if (rec.anycast->size() > 1) { // destination address is an anycast - if (rec.workchain_id == ton::masterchainId) { - // anycast addresses disabled in masterchain - LOG(DEBUG) << "masterchain destination address has an anycast field"; - return false; - } vm::CellSlice cs{*rec.anycast}; int d = (int)cs.fetch_ulong(6) - 32; if (d <= 0 || d > 30) { @@ -1360,12 +2247,36 @@ bool Transaction::check_rewrite_dest_addr(Ref& dest_addr, const A return true; } +/** + * Tries to send a message. + * + * @param cs0 The cell slice containing the action data serialized as action_send_msg TLB-scheme. + * @param ap The action phase. + * @param cfg The action phase configuration. + * @param redoing The index of the attempt, starting from 0. On later attempts tries to move message body and StateInit to separate cells. + * + * @returns 0 if the message is successfully sent or if the error may be ignored, error code otherwise. + * Returns -2 if the action should be attempted again. + */ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, const ActionPhaseConfig& cfg, int redoing) { block::gen::OutAction::Record_action_send_msg act_rec; - // mode: +128 = attach all remaining balance, +64 = attach all remaining balance of the inbound message, +32 = delete smart contract if balance becomes zero, +1 = pay message fees, +2 = skip if message cannot be sent + // mode: + // +128 = attach all remaining balance + // +64 = attach all remaining balance of the inbound message + // +32 = delete smart contract if balance becomes zero + // +1 = pay message fees + // +2 = skip if message cannot be sent + // +16 = bounce if action fails vm::CellSlice cs{cs0}; - if (!tlb::unpack_exact(cs, act_rec) || (act_rec.mode & ~0xe3) || (act_rec.mode & 0xc0) == 0xc0) { + if (!tlb::unpack_exact(cs, act_rec)) { + return -1; + } + if ((act_rec.mode & 16) && cfg.bounce_on_fail_enabled) { + act_rec.mode &= ~16; + ap.need_bounce_on_fail = true; + } + if ((act_rec.mode & ~0xe3) || (act_rec.mode & 0xc0) == 0xc0) { return -1; } bool skip_invalid = (act_rec.mode & 2); @@ -1375,6 +2286,10 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, if (!tlb::type_unpack_cell(act_rec.out_msg, block::gen::t_MessageRelaxed_Any, msg)) { return -1; } + if (!block::tlb::validate_message_relaxed_libs(act_rec.out_msg)) { + LOG(DEBUG) << "outbound message has invalid libs in StateInit"; + return -1; + } if (redoing >= 1) { if (msg.init->size_refs() >= 2) { LOG(DEBUG) << "moving the StateInit of a suggested outbound message into a separate cell"; @@ -1388,7 +2303,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, && cb.store_long_bool(3, 2) // (just (right ... )) && cb.store_ref_bool(std::move(cell)) // z:^StateInit && cb.finalize_to(cell)); - msg.init = vm::load_cell_slice_ref(std::move(cell)); + msg.init = vm::load_cell_slice_ref(cell); } else { redoing = 2; } @@ -1405,7 +2320,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, && cb.store_long_bool(1, 1) // (right ... ) && cb.store_ref_bool(std::move(cell)) // x:^X && cb.finalize_to(cell)); - msg.body = vm::load_cell_slice_ref(std::move(cell)); + msg.body = vm::load_cell_slice_ref(cell); } block::gen::CommonMsgInfoRelaxed::Record_int_msg_info info; @@ -1416,13 +2331,16 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, if (!tlb::csr_unpack(msg.info, erec)) { return -1; } + if (act_rec.mode & ~3) { + return -1; // invalid mode for an external message + } info.src = std::move(erec.src); info.dest = std::move(erec.dest); // created_lt and created_at are ignored info.ihr_disabled = true; info.bounce = false; info.bounced = false; - fwd_fee = ihr_fee = td::RefInt256{true, 0}; + fwd_fee = ihr_fee = td::zero_refint(); } else { // int_msg_info$0 constructor if (!tlb::csr_unpack(msg.info, info) || !block::tlb::t_CurrencyCollection.validate_csr(info.value)) { @@ -1450,19 +2368,84 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, // fetch message pricing info const MsgPrices& msg_prices = cfg.fetch_msg_prices(to_mc || account.is_masterchain()); - // compute size of message - vm::CellStorageStat sstat; // for message size - // preliminary storage estimation of the resulting message - sstat.add_used_storage(msg.init, true, 3); // message init - sstat.add_used_storage(msg.body, true, 3); // message body (the root cell itself is not counted) - if (!ext_msg) { - sstat.add_used_storage(info.value->prefetch_ref()); + // If action fails, account is required to pay fine_per_cell for every visited cell + // Number of visited cells is limited depending on available funds + unsigned max_cells = cfg.size_limits.max_msg_cells; + td::uint64 fine_per_cell = 0; + if (cfg.action_fine_enabled && !account.is_special) { + fine_per_cell = (msg_prices.cell_price >> 16) / 4; + td::RefInt256 funds = ap.remaining_balance.grams; + if (!ext_msg && !(act_rec.mode & 0x80) && !(act_rec.mode & 1)) { + if (!block::tlb::t_CurrencyCollection.validate_csr(info.value)) { + LOG(DEBUG) << "invalid value:CurrencyCollection in proposed outbound message"; + return skip_invalid ? 0 : 37; + } + block::CurrencyCollection value; + CHECK(value.unpack(info.value)); + CHECK(value.grams.not_null()); + td::RefInt256 new_funds = value.grams; + if (act_rec.mode & 0x40) { + if (msg_balance_remaining.is_valid()) { + new_funds += msg_balance_remaining.grams; + } + if (compute_phase) { + new_funds -= compute_phase->gas_fees; + } + new_funds -= ap.action_fine; + if (new_funds->sgn() < 0) { + LOG(DEBUG) + << "not enough value to transfer with the message: all of the inbound message value has been consumed"; + return skip_invalid ? 0 : 37; + } + } + funds = std::min(funds, new_funds); + } + if (funds->cmp(max_cells * fine_per_cell) < 0) { + max_cells = static_cast((funds / td::make_refint(fine_per_cell))->to_long()); + } } - LOG(DEBUG) << "storage paid for a message: " << sstat.cells << " cells, " << sstat.bits << " bits"; - if (sstat.bits > max_msg_bits || sstat.cells > max_msg_cells) { - LOG(DEBUG) << "message too large, invalid"; + // compute size of message + vm::CellStorageStat sstat(max_cells); // for message size + // preliminary storage estimation of the resulting message + unsigned max_merkle_depth = 0; + auto add_used_storage = [&](const auto& x, unsigned skip_root_count) -> td::Status { + if (x.not_null()) { + TRY_RESULT(res, sstat.add_used_storage(x, true, skip_root_count)); + max_merkle_depth = std::max(max_merkle_depth, res.max_merkle_depth); + } + return td::Status::OK(); + }; + add_used_storage(msg.init, 3); // message init + add_used_storage(msg.body, 3); // message body (the root cell itself is not counted) + if (!ext_msg) { + add_used_storage(info.value->prefetch_ref(), 0); + } + auto collect_fine = [&] { + if (cfg.action_fine_enabled && !account.is_special) { + td::uint64 fine = fine_per_cell * std::min(max_cells, sstat.cells); + if (ap.remaining_balance.grams->cmp(fine) < 0) { + fine = ap.remaining_balance.grams->to_long(); + } + ap.action_fine += fine; + ap.remaining_balance.grams -= fine; + } + }; + if (sstat.cells > max_cells && max_cells < cfg.size_limits.max_msg_cells) { + LOG(DEBUG) << "not enough funds to process a message (max_cells=" << max_cells << ")"; + collect_fine(); return skip_invalid ? 0 : 40; } + if (sstat.bits > cfg.size_limits.max_msg_bits || sstat.cells > max_cells) { + LOG(DEBUG) << "message too large, invalid"; + collect_fine(); + return skip_invalid ? 0 : 40; + } + if (max_merkle_depth > max_allowed_merkle_depth) { + LOG(DEBUG) << "message has too big merkle depth, invalid"; + collect_fine(); + return skip_invalid ? 0 : 40; + } + LOG(DEBUG) << "storage paid for a message: " << sstat.cells << " cells, " << sstat.bits << " bits"; // compute forwarding fees auto fees_c = msg_prices.compute_fwd_ihr_fees(sstat.cells, sstat.bits, info.ihr_disabled); @@ -1475,10 +2458,10 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, // set fees to computed values if (fwd_fee->unsigned_fits_bits(63) && fwd_fee->to_long() < (long long)fees_c.first) { - fwd_fee = td::RefInt256{true, fees_c.first}; + fwd_fee = td::make_refint(fees_c.first); } if (fees_c.second && ihr_fee->unsigned_fits_bits(63) && ihr_fee->to_long() < (long long)fees_c.second) { - ihr_fee = td::RefInt256{true, fees_c.second}; + ihr_fee = td::make_refint(fees_c.second); } Ref new_msg; @@ -1491,11 +2474,12 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, // ... if (!block::tlb::t_CurrencyCollection.validate_csr(info.value)) { LOG(DEBUG) << "invalid value:CurrencyCollection in proposed outbound message"; + collect_fine(); return skip_invalid ? 0 : 37; } if (info.ihr_disabled) { // if IHR is disabled, IHR fees will be always zero - ihr_fee = td::RefInt256{true, 0}; + ihr_fee = td::zero_refint(); } // extract value to be carried by the message block::CurrencyCollection req; @@ -1509,11 +2493,15 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, } else if (act_rec.mode & 0x40) { // attach all remaining balance of the inbound message (in addition to the original value) req += msg_balance_remaining; - if (!(act_rec.mode & 1) && compute_phase) { - req -= compute_phase->gas_fees; + if (!(act_rec.mode & 1)) { + req -= ap.action_fine; + if (compute_phase) { + req -= compute_phase->gas_fees; + } if (!req.is_valid()) { LOG(DEBUG) << "not enough value to transfer with the message: all of the inbound message value has been consumed"; + collect_fine(); return skip_invalid ? 0 : 37; } } @@ -1529,6 +2517,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, // receiver pays the fees (but cannot) LOG(DEBUG) << "not enough value attached to the message to pay forwarding fees : have " << req.grams << ", need " << fees_total; + collect_fine(); return skip_invalid ? 0 : 37; // not enough grams } else { // decrease message value @@ -1539,15 +2528,25 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, if (ap.remaining_balance.grams < req_grams_brutto) { LOG(DEBUG) << "not enough grams to transfer with the message : remaining balance is " << ap.remaining_balance.to_str() << ", need " << req_grams_brutto << " (including forwarding fees)"; + collect_fine(); return skip_invalid ? 0 : 37; // not enough grams } Ref new_extra; if (!block::sub_extra_currency(ap.remaining_balance.extra, req.extra, new_extra)) { - LOG(DEBUG) << "not enough extra currency to send with the message"; + LOG(DEBUG) << "not enough extra currency to send with the message: " + << block::CurrencyCollection{0, req.extra}.to_str() << " required, only " + << block::CurrencyCollection{0, ap.remaining_balance.extra}.to_str() << " available"; + collect_fine(); return skip_invalid ? 0 : 38; // not enough (extra) funds } + if (ap.remaining_balance.extra.not_null() || req.extra.not_null()) { + LOG(DEBUG) << "subtracting extra currencies: " + << block::CurrencyCollection{0, ap.remaining_balance.extra}.to_str() << " minus " + << block::CurrencyCollection{0, req.extra}.to_str() << " equals " + << block::CurrencyCollection{0, new_extra}.to_str(); + } auto fwd_fee_mine = msg_prices.get_first_part(fwd_fee); auto fwd_fee_remain = fwd_fee - fwd_fee_mine; @@ -1562,7 +2561,11 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, vm::CellBuilder cb; if (!tlb::type_pack(cb, block::gen::t_MessageRelaxed_Any, msg)) { LOG(DEBUG) << "outbound message does not fit into a cell after rewriting"; - return redoing < 2 ? -2 : (skip_invalid ? 0 : 39); + if (redoing == 2) { + collect_fine(); + return skip_invalid ? 0 : 39; + } + return -2; } new_msg_bits = cb.size(); @@ -1584,6 +2587,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, // external messages also have forwarding fees if (ap.remaining_balance.grams < fwd_fee) { LOG(DEBUG) << "not enough funds to pay for an outbound external message"; + collect_fine(); return skip_invalid ? 0 : 37; // not enough grams } // repack message @@ -1597,7 +2601,11 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, vm::CellBuilder cb; if (!tlb::type_pack(cb, block::gen::t_MessageRelaxed_Any, msg)) { LOG(DEBUG) << "outbound message does not fit into a cell after rewriting"; - return redoing < 2 ? -2 : (skip_invalid ? 0 : 39); + if (redoing == 2) { + collect_fine(); + return (skip_invalid ? 0 : 39); + } + return -2; } new_msg_bits = cb.size(); @@ -1612,12 +2620,14 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, if (!block::tlb::t_Message.validate_ref(new_msg)) { LOG(ERROR) << "generated outbound message is not a valid (Message Any) according to hand-written check"; + collect_fine(); return -1; } if (!block::gen::t_Message_Any.validate_ref(new_msg)) { LOG(ERROR) << "generated outbound message is not a valid (Message Any) according to automated check"; block::gen::t_Message_Any.print_ref(std::cerr, new_msg); vm::load_cell_slice(new_msg).print_rec(std::cerr); + collect_fine(); return -1; } if (verbosity > 2) { @@ -1643,9 +2653,25 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, return 0; } +/** + * Tries to reserve a currency an action phase. + * + * @param cs The cell slice containing the action data serialized as action_reserve_currency TLB-scheme. + * @param ap The action phase. + * @param cfg The action phase configuration. + * + * @returns 0 if the currency is successfully reserved, error code otherwise. + */ int Transaction::try_action_reserve_currency(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg) { block::gen::OutAction::Record_action_reserve_currency rec; - if (!tlb::unpack_exact(cs, rec) || (rec.mode & ~3)) { + if (!tlb::unpack_exact(cs, rec)) { + return -1; + } + if ((rec.mode & 16) && cfg.bounce_on_fail_enabled) { + rec.mode &= ~16; + ap.need_bounce_on_fail = true; + } + if (rec.mode & ~15) { return -1; } int mode = rec.mode; @@ -1656,7 +2682,21 @@ int Transaction::try_action_reserve_currency(vm::CellSlice& cs, ActionPhase& ap, return -1; } LOG(DEBUG) << "action_reserve_currency: mode=" << mode << ", reserve=" << reserve.to_str() - << ", balance=" << ap.remaining_balance.to_str(); + << ", balance=" << ap.remaining_balance.to_str() << ", original balance=" << original_balance.to_str(); + if (mode & 4) { + if (mode & 8) { + reserve = original_balance - reserve; + } else { + reserve += original_balance; + } + } else if (mode & 8) { + LOG(DEBUG) << "invalid reserve mode " << mode; + return -1; + } + if (!reserve.is_valid() || td::sgn(reserve.grams) < 0) { + LOG(DEBUG) << "cannot reserve a negative amount: " << reserve.to_str(); + return -1; + } if (reserve.grams > ap.remaining_balance.grams) { if (mode & 2) { reserve.grams = ap.remaining_balance.grams; @@ -1667,7 +2707,9 @@ int Transaction::try_action_reserve_currency(vm::CellSlice& cs, ActionPhase& ap, } } if (!block::sub_extra_currency(ap.remaining_balance.extra, reserve.extra, newc.extra)) { - LOG(DEBUG) << "not enough extra currency to reserve"; + LOG(DEBUG) << "not enough extra currency to reserve: " << block::CurrencyCollection{0, reserve.extra}.to_str() + << " required, only " << block::CurrencyCollection{0, ap.remaining_balance.extra}.to_str() + << " available"; if (mode & 2) { // TODO: process (mode & 2) correctly by setting res_extra := inf (reserve.extra, ap.remaining_balance.extra) } @@ -1690,17 +2732,139 @@ int Transaction::try_action_reserve_currency(vm::CellSlice& cs, ActionPhase& ap, return 0; } +/** + * Calculates the number of public libraries in the dictionary. + * + * @param libraries The dictionary of account libraries. + * + * @returns The number of public libraries in the dictionary. + */ +static td::uint32 get_public_libraries_count(const td::Ref& libraries) { + td::uint32 count = 0; + vm::Dictionary dict{libraries, 256}; + dict.check_for_each([&](td::Ref value, td::ConstBitPtr key, int) { + if (block::is_public_library(key, std::move(value))) { + ++count; + } + return true; + }); + return count; +} + +/** + * Calculates the number of changes of public libraries in the dictionary. + * + * @param old_libraries The dictionary of account libraries before the transaction. + * @param new_libraries The dictionary of account libraries after the transaction. + * + * @returns The number of changed public libraries. + */ +static td::uint32 get_public_libraries_diff_count(const td::Ref& old_libraries, + const td::Ref& new_libraries) { + td::uint32 count = 0; + vm::Dictionary dict1{old_libraries, 256}; + vm::Dictionary dict2{new_libraries, 256}; + dict1.scan_diff(dict2, [&](td::ConstBitPtr key, int n, Ref val1, Ref val2) -> bool { + CHECK(n == 256); + bool is_public1 = val1.not_null() && block::is_public_library(key, val1); + bool is_public2 = val2.not_null() && block::is_public_library(key, val2); + if (is_public1 != is_public2) { + ++count; + } + return true; + }); + return count; +} + +/** + * Checks that the new account state fits in the limits. + * This function is not called for special accounts. + * + * @param size_limits The size limits configuration. + * @param update_storage_stat Store storage stat in the Transaction's CellStorageStat. + * + * @returns A `td::Status` indicating the result of the check. + * - If the state limits are within the allowed range, returns OK. + * - If the state limits exceed the maximum allowed range, returns an error. + */ +td::Status Transaction::check_state_limits(const SizeLimitsConfig& size_limits, bool update_storage_stat) { + auto cell_equal = [](const td::Ref& a, const td::Ref& b) -> bool { + if (a.is_null()) { + return b.is_null(); + } + if (b.is_null()) { + return false; + } + return a->get_hash() == b->get_hash(); + }; + if (cell_equal(account.code, new_code) && cell_equal(account.data, new_data) && + cell_equal(account.library, new_library)) { + return td::Status::OK(); + } + vm::CellStorageStat storage_stat; + storage_stat.limit_cells = size_limits.max_acc_state_cells; + storage_stat.limit_bits = size_limits.max_acc_state_bits; + td::Timer timer; + auto add_used_storage = [&](const td::Ref& cell) -> td::Status { + if (cell.not_null()) { + TRY_RESULT(res, storage_stat.add_used_storage(cell)); + if (res.max_merkle_depth > max_allowed_merkle_depth) { + return td::Status::Error("too big merkle depth"); + } + } + return td::Status::OK(); + }; + TRY_STATUS(add_used_storage(new_code)); + TRY_STATUS(add_used_storage(new_data)); + TRY_STATUS(add_used_storage(new_library)); + if (timer.elapsed() > 0.1) { + LOG(INFO) << "Compute used storage took " << timer.elapsed() << "s"; + } + if (acc_status == Account::acc_active) { + storage_stat.clear_limit(); + } else { + storage_stat.clear(); + } + td::Status res; + if (storage_stat.cells > size_limits.max_acc_state_cells || storage_stat.bits > size_limits.max_acc_state_bits) { + res = td::Status::Error(PSTRING() << "account state is too big"); + } else if (account.is_masterchain() && !cell_equal(account.library, new_library) && + get_public_libraries_count(new_library) > size_limits.max_acc_public_libraries) { + res = td::Status::Error("too many public libraries"); + } else { + res = td::Status::OK(); + } + if (update_storage_stat) { + // storage_stat will be reused in compute_state() + new_storage_stat = std::move(storage_stat); + } + return res; +} + +/** + * Prepares the bounce phase of a transaction. + * + * @param cfg The configuration for the action phase. + * + * @returns True if the bounce phase was successfully prepared, false otherwise. + */ bool Transaction::prepare_bounce_phase(const ActionPhaseConfig& cfg) { if (in_msg.is_null() || !bounce_enabled) { return false; } bounce_phase = std::make_unique(); BouncePhase& bp = *bounce_phase; + block::gen::Message::Record msg; block::gen::CommonMsgInfo::Record_int_msg_info info; - if (!tlb::unpack_cell_inexact(in_msg, info)) { + auto cs = vm::load_cell_slice(in_msg); + if (!(tlb::unpack(cs, info) && gen::t_Maybe_Either_StateInit_Ref_StateInit.skip(cs) && cs.have(1) && + cs.have_refs((int)cs.prefetch_ulong(1)))) { bounce_phase.reset(); return false; } + if (cs.fetch_ulong(1)) { + cs = vm::load_cell_slice(cs.prefetch_ref()); + } info.ihr_disabled = true; info.bounce = false; info.bounced = true; @@ -1726,6 +2890,9 @@ bool Transaction::prepare_bounce_phase(const ActionPhaseConfig& cfg) { if (compute_phase && compute_phase->gas_fees.not_null()) { msg_balance.grams -= compute_phase->gas_fees; } + if (action_phase && action_phase->action_fine.not_null()) { + msg_balance.grams -= action_phase->action_fine; + } if ((msg_balance.grams < 0) || (msg_balance.grams->signed_fits_bits(64) && msg_balance.grams->to_long() < (long long)bp.fwd_fees)) { // not enough funds @@ -1736,10 +2903,10 @@ bool Transaction::prepare_bounce_phase(const ActionPhaseConfig& cfg) { balance -= msg_balance; CHECK(balance.is_valid()); // debit total forwarding fees from the message's balance, then split forwarding fees into our part and remaining part - msg_balance -= td::RefInt256{true, bp.fwd_fees}; + msg_balance -= td::make_refint(bp.fwd_fees); bp.fwd_fees_collected = msg_prices.get_first_part(bp.fwd_fees); bp.fwd_fees -= bp.fwd_fees_collected; - total_fees += td::RefInt256{true, bp.fwd_fees_collected}; + total_fees += td::make_refint(bp.fwd_fees_collected); // serialize outbound message info.created_lt = end_lt++; info.created_at = now; @@ -1752,16 +2919,33 @@ bool Transaction::prepare_bounce_phase(const ActionPhaseConfig& cfg) { && block::tlb::t_Grams.store_long(cb, bp.fwd_fees) // fwd_fee:Grams && cb.store_long_bool(info.created_lt, 64) // created_lt:uint64 && cb.store_long_bool(info.created_at, 32) // created_at:uint32 - && cb.store_long_bool(0, 2) // init:(Maybe ...) state:(Either ..) - && cb.finalize_to(bp.out_msg)); + && cb.store_bool_bool(false)); // init:(Maybe ...) + if (cfg.bounce_msg_body) { + int body_bits = std::min((int)cs.size(), cfg.bounce_msg_body); + if (cb.remaining_bits() >= body_bits + 33u) { + CHECK(cb.store_bool_bool(false) // body:(Either X ^X) -> left X + && cb.store_long_bool(-1, 32) // int = -1 ("message type") + && cb.append_bitslice(cs.prefetch_bits(body_bits))); // truncated message body + } else { + vm::CellBuilder cb2; + CHECK(cb.store_bool_bool(true) // body:(Either X ^X) -> right ^X + && cb2.store_long_bool(-1, 32) // int = -1 ("message type") + && cb2.append_bitslice(cs.prefetch_bits(body_bits)) // truncated message body + && cb.store_builder_ref_bool(std::move(cb2))); // ^X + } + } else { + CHECK(cb.store_bool_bool(false)); // body:(Either ..) + } + CHECK(cb.finalize_to(bp.out_msg)); if (verbosity > 2) { - std::cerr << "generated bounced message: "; + LOG(INFO) << "generated bounced message: "; block::gen::t_Message_Any.print_ref(std::cerr, bp.out_msg); } out_msgs.push_back(bp.out_msg); bp.ok = true; return true; } +} // namespace transaction /* * @@ -1769,6 +2953,14 @@ bool Transaction::prepare_bounce_phase(const ActionPhaseConfig& cfg) { * */ +/** + * Stores the account status in a CellBuilder object. + * + * @param cb The CellBuilder object to store the account status in. + * @param acc_status The account status to store. + * + * @returns True if the account status was successfully stored, false otherwise. + */ bool Account::store_acc_status(vm::CellBuilder& cb, int acc_status) const { int v; switch (acc_status) { @@ -1791,6 +2983,49 @@ bool Account::store_acc_status(vm::CellBuilder& cb, int acc_status) const { return cb.store_long_bool(v, 2); } +/** + * Tries to update the storage statistics based on the old storage statistics and old account state without fully recomputing it. + * + * It succeeds if only root cell of AccountStorage is changed. + * + * @param old_stat The old storage statistics. + * @param old_cs The old AccountStorage. + * @param new_cell The new AccountStorage. + * + * @returns An optional value of type vm::CellStorageStat. If the update is successful, it returns the new storage statistics. Otherwise, it returns an empty optional. + */ +static td::optional try_update_storage_stat(const vm::CellStorageStat& old_stat, + td::Ref old_cs, + td::Ref new_cell) { + if (old_stat.cells == 0 || old_cs.is_null()) { + return {}; + } + vm::CellSlice new_cs = vm::CellSlice(vm::NoVm(), new_cell); + if (old_cs->size_refs() != new_cs.size_refs()) { + return {}; + } + for (unsigned i = 0; i < old_cs->size_refs(); ++i) { + if (old_cs->prefetch_ref(i)->get_hash() != new_cs.prefetch_ref(i)->get_hash()) { + return {}; + } + } + if (old_stat.bits < old_cs->size()) { + return {}; + } + + vm::CellStorageStat new_stat; + new_stat.cells = old_stat.cells; + new_stat.bits = old_stat.bits - old_cs->size() + new_cs.size(); + new_stat.public_cells = old_stat.public_cells; + return new_stat; +} + +namespace transaction { +/** + * Computes the new state of the account. + * + * @returns True if the state computation is successful, false otherwise. + */ bool Transaction::compute_state() { if (new_total_state.not_null()) { return true; @@ -1824,7 +3059,7 @@ bool Transaction::compute_state() { // code:(Maybe ^Cell) data:(Maybe ^Cell) library:(HashmapE 256 SimpleLib) auto frozen_state = cb2.finalize(); frozen_hash = frozen_state->get_hash().bits(); - if (verbosity >= 3 * 0) { // !!!DEBUG!!! + if (verbosity >= 3 * 1) { // !!!DEBUG!!! std::cerr << "freezing state of smart contract: "; block::gen::t_StateInit.print_ref(std::cerr, frozen_state); CHECK(block::gen::t_StateInit.validate_ref(frozen_state)); @@ -1852,6 +3087,7 @@ bool Transaction::compute_state() { // code:(Maybe ^Cell) data:(Maybe ^Cell) library:(HashmapE 256 SimpleLib) } auto storage = cb.finalize(); + new_storage = td::Ref(true, vm::NoVm(), storage); if (si_pos) { auto cs_ref = load_cell_slice_ref(storage); CHECK(cs_ref.unique_write().skip_ext(si_pos)); @@ -1860,7 +3096,16 @@ bool Transaction::compute_state() { new_inner_state.clear(); } vm::CellStorageStat& stats = new_storage_stat; - CHECK(stats.compute_used_storage(Ref(storage))); + auto new_stats = try_update_storage_stat(account.storage_stat, account.storage, storage); + if (new_stats) { + stats = new_stats.unwrap(); + } else { + td::Timer timer; + stats.add_used_storage(Ref(storage)).ensure(); + if (timer.elapsed() > 0.1) { + LOG(INFO) << "Compute used storage took " << timer.elapsed() << "s"; + } + } CHECK(cb.store_long_bool(1, 1) // account$1 && cb.append_cellslice_bool(account.my_addr) // addr:MsgAddressInt && block::store_UInt7(cb, stats.cells) // storage_used$_ cells:(VarUInteger 7) @@ -1879,11 +3124,17 @@ bool Transaction::compute_state() { std::cerr << "new account state: "; block::gen::t_Account.print_ref(std::cerr, new_total_state); } - CHECK(block::gen::t_Account.validate_ref(new_total_state)); CHECK(block::tlb::t_Account.validate_ref(new_total_state)); return true; } +/** + * Serializes the transaction object using Transaction TLB-scheme. + * + * Updates root. + * + * @returns True if the serialization is successful, False otherwise. + */ bool Transaction::serialize() { if (root.not_null()) { return true; @@ -1968,14 +3219,14 @@ bool Transaction::serialize() { vm::load_cell_slice(root).print_rec(std::cerr); } - if (!block::gen::t_Transaction.validate_ref(root)) { + if (!block::gen::t_Transaction.validate_ref(4096, root)) { LOG(ERROR) << "newly-generated transaction failed to pass automated validation:"; vm::load_cell_slice(root).print_rec(std::cerr); block::gen::t_Transaction.print_ref(std::cerr, root); root.clear(); return false; } - if (!block::tlb::t_Transaction.validate_ref(root)) { + if (!block::tlb::t_Transaction.validate_ref(4096, root)) { LOG(ERROR) << "newly-generated transaction failed to pass hand-written validation:"; vm::load_cell_slice(root).print_rec(std::cerr); block::gen::t_Transaction.print_ref(std::cerr, root); @@ -1986,6 +3237,13 @@ bool Transaction::serialize() { return true; } +/** + * Serializes the storage phase of a transaction. + * + * @param cb The CellBuilder to store the serialized data. + * + * @returns True if the serialization is successful, false otherwise. + */ bool Transaction::serialize_storage_phase(vm::CellBuilder& cb) { if (!storage_phase) { return false; @@ -2009,6 +3267,13 @@ bool Transaction::serialize_storage_phase(vm::CellBuilder& cb) { return ok; } +/** + * Serializes the credit phase of a transaction. + * + * @param cb The CellBuilder to store the serialized data. + * + * @returns True if the credit phase was successfully serialized, false otherwise. + */ bool Transaction::serialize_credit_phase(vm::CellBuilder& cb) { if (!credit_phase) { return false; @@ -2018,6 +3283,13 @@ bool Transaction::serialize_credit_phase(vm::CellBuilder& cb) { return block::store_Maybe_Grams_nz(cb, cp.due_fees_collected) && cp.credit.store(cb); } +/** + * Serializes the compute phase of a transaction. + * + * @param cb The CellBuilder to store the serialized data. + * + * @returns True if the serialization was successful, false otherwise. + */ bool Transaction::serialize_compute_phase(vm::CellBuilder& cb) { if (!compute_phase) { return false; @@ -2031,6 +3303,8 @@ bool Transaction::serialize_compute_phase(vm::CellBuilder& cb) { return cb.store_long_bool(1, 3); // cskip_bad_state$01 = ComputeSkipReason; case ComputePhase::sk_no_gas: return cb.store_long_bool(2, 3); // cskip_no_gas$10 = ComputeSkipReason; + case ComputePhase::sk_suspended: + return cb.store_long_bool(0b0110, 4); // cskip_suspended$110 = ComputeSkipReason; case ComputePhase::sk_none: break; default: @@ -2058,6 +3332,13 @@ bool Transaction::serialize_compute_phase(vm::CellBuilder& cb) { return ok; } +/** + * Serializes the action phase of a transaction. + * + * @param cb The CellBuilder to store the serialized data. + * + * @returns True if the serialization is successful, false otherwise. + */ bool Transaction::serialize_action_phase(vm::CellBuilder& cb) { if (!action_phase) { return false; @@ -2082,6 +3363,13 @@ bool Transaction::serialize_action_phase(vm::CellBuilder& cb) { return ok; } +/** + * Serializes the bounce phase of a transaction. + * + * @param cb The CellBuilder to store the serialized data. + * + * @returns True if the bounce phase was successfully serialized, false otherwise. + */ bool Transaction::serialize_bounce_phase(vm::CellBuilder& cb) { if (!bounce_phase) { return false; @@ -2102,6 +3390,15 @@ bool Transaction::serialize_bounce_phase(vm::CellBuilder& cb) { } } +/** + * Estimates the block storage profile increment if the transaction is added to the block. + * + * @param store_stat The current storage statistics of the block. + * @param usage_tree The usage tree of the block. + * + * @returns The estimated block storage profile increment. + * Returns Error if the transaction is not serialized or if its new state is not computed. + */ td::Result Transaction::estimate_block_storage_profile_incr( const vm::NewCellStorageStat& store_stat, const vm::CellUsageTree* usage_tree) const { if (root.is_null()) { @@ -2113,37 +3410,47 @@ td::Result Transaction::estimate_block_storage_pro return store_stat.tentative_add_proof(new_total_state, usage_tree) + store_stat.tentative_add_cell(root); } -bool Transaction::update_block_storage_profile(vm::NewCellStorageStat& store_stat, - const vm::CellUsageTree* usage_tree) const { - if (root.is_null() || new_total_state.is_null()) { +/** + * Updates the limits status of a block. + * + * @param blimst The block limit status object to update. + * @param with_size Flag indicating whether to update the size limits. + * + * @returns True if the limits were successfully updated, False otherwise. + */ +bool Transaction::update_limits(block::BlockLimitStatus& blimst, bool with_gas, bool with_size) const { + if (!(blimst.update_lt(end_lt) && blimst.update_gas(with_gas ? gas_used() : 0))) { return false; } - store_stat.add_proof(new_total_state, usage_tree); - store_stat.add_cell(root); + if (with_size) { + if (!(blimst.add_proof(new_total_state) && blimst.add_cell(root) && blimst.add_transaction() && + blimst.add_account(is_first))) { + return false; + } + if (account.is_masterchain()) { + if (was_frozen || was_deleted) { + blimst.public_library_diff += get_public_libraries_count(account.orig_library); + } else { + blimst.public_library_diff += get_public_libraries_diff_count(account.orig_library, new_library); + } + } + } return true; } -bool Transaction::would_fit(unsigned cls, const block::BlockLimitStatus& blimst) const { - auto res = estimate_block_storage_profile_incr(blimst.st_stat, blimst.limits.usage_tree); - if (res.is_error()) { - LOG(ERROR) << res.move_as_error(); - return false; - } - auto extra = res.move_as_ok(); - return blimst.would_fit(cls, end_lt, gas_used(), &extra); -} - -bool Transaction::update_limits(block::BlockLimitStatus& blimst) const { - return blimst.update_lt(end_lt) && blimst.update_gas(gas_used()) && blimst.add_proof(new_total_state) && - blimst.add_cell(root) && blimst.add_transaction() && blimst.add_account(is_first); -} - /* * * COMMIT TRANSACTION * */ +/** + * Commits a transaction for a given account. + * + * @param acc The account to commit the transaction for. + * + * @returns A reference to the root cell of the serialized transaction. + */ Ref Transaction::commit(Account& acc) { CHECK(account.last_trans_end_lt_ <= start_lt && start_lt < end_lt); CHECK(root.not_null()); @@ -2151,7 +3458,7 @@ Ref Transaction::commit(Account& acc) { CHECK((const void*)&acc == (const void*)&account); // export all fields modified by the Transaction into original account // NB: this is the only method that modifies account - if (orig_addr_rewrite_set && new_split_depth >= 0 && acc.status == Account::acc_nonexist && + if (orig_addr_rewrite_set && new_split_depth >= 0 && acc.status != Account::acc_active && acc_status == Account::acc_active) { LOG(DEBUG) << "setting address rewriting info for newly-activated account " << acc.addr.to_hex() << " with split_depth=" << new_split_depth @@ -2164,6 +3471,7 @@ Ref Transaction::commit(Account& acc) { acc.last_trans_hash_ = root->get_hash().bits(); acc.last_paid = last_paid; acc.storage_stat = new_storage_stat; + acc.storage = new_storage; acc.balance = std::move(balance); acc.due_payment = std::move(due_payment); acc.total_state = std::move(new_total_state); @@ -2180,31 +3488,64 @@ Ref Transaction::commit(Account& acc) { acc.tick = new_tick; acc.tock = new_tock; } else { - acc.tick = acc.tock = false; + CHECK(acc.deactivate()); } end_lt = 0; acc.push_transaction(root, start_lt); return root; } +/** + * Extracts the output message at the specified index from the transaction. + * + * @param i The index of the output message to extract. + * + * @returns A pair of the logical time and the extracted output message. + */ LtCellRef Transaction::extract_out_msg(unsigned i) { return {start_lt + i + 1, std::move(out_msgs.at(i))}; } +/** + * Extracts the output message at index i from the transaction. + * + * @param i The index of the output message to extract. + * + * @returns A triple of the logical time, the extracted output message and the transaction root. + */ NewOutMsg Transaction::extract_out_msg_ext(unsigned i) { return {start_lt + i + 1, std::move(out_msgs.at(i)), root}; } +/** + * Extracts the outgoing messages from the transaction and adds them to the given list. + * + * @param list The list to which the outgoing messages will be added. + */ void Transaction::extract_out_msgs(std::vector& list) { for (unsigned i = 0; i < out_msgs.size(); i++) { list.emplace_back(start_lt + i + 1, std::move(out_msgs[i])); } } +} // namespace transaction +/** + * Adds a transaction to the account's transaction list. + * + * @param trans_root The root of the transaction cell. + * @param trans_lt The logical time of the transaction. + */ void Account::push_transaction(Ref trans_root, ton::LogicalTime trans_lt) { transactions.emplace_back(trans_lt, std::move(trans_root)); } +/** + * Serializes an account block for the account using AccountBlock TLB-scheme. + * + * @param cb The CellBuilder used to store the serialized data. + * + * @returns True if the account block was successfully created, false otherwise. + */ bool Account::create_account_block(vm::CellBuilder& cb) { if (transactions.empty()) { return false; @@ -2233,6 +3574,11 @@ bool Account::create_account_block(vm::CellBuilder& cb) { && cb.store_ref_bool(cb2.finalize()); // state_update:^(HASH_UPDATE Account) } +/** + * Checks if the libraries stored in the account object have changed. + * + * @returns True if the libraries have changed, False otherwise. + */ bool Account::libraries_changed() const { bool s = orig_library.not_null(); bool t = library.not_null(); @@ -2243,4 +3589,141 @@ bool Account::libraries_changed() const { } } +/** + * Fetches and initializes various configuration parameters from masterchain config for transaction processing. + * + * @param config The masterchain configuration. + * @param old_mparams Pointer to store a dictionary of mandatory parameters (ConfigParam 9). + * @param storage_prices Pointer to store the storage prices. + * @param storage_phase_cfg Pointer to store the storage phase configuration. + * @param rand_seed Pointer to the random seed. Generates a new seed if the value is `td::Bits256::zero()`. + * @param compute_phase_cfg Pointer to store the compute phase configuration. + * @param action_phase_cfg Pointer to store the action phase configuration. + * @param masterchain_create_fee Pointer to store the masterchain create fee. + * @param basechain_create_fee Pointer to store the basechain create fee. + * @param wc The workchain ID. + * @param now The current Unix time. + */ +td::Status FetchConfigParams::fetch_config_params( + const block::ConfigInfo& config, Ref* old_mparams, std::vector* storage_prices, + StoragePhaseConfig* storage_phase_cfg, td::BitArray<256>* rand_seed, ComputePhaseConfig* compute_phase_cfg, + ActionPhaseConfig* action_phase_cfg, td::RefInt256* masterchain_create_fee, td::RefInt256* basechain_create_fee, + ton::WorkchainId wc, ton::UnixTime now) { + auto prev_blocks_info = config.get_prev_blocks_info(); + if (prev_blocks_info.is_error()) { + return prev_blocks_info.move_as_error_prefix( + td::Status::Error(-668, "cannot fetch prev blocks info from masterchain configuration: ")); + } + return fetch_config_params(config, prev_blocks_info.move_as_ok(), old_mparams, storage_prices, storage_phase_cfg, + rand_seed, compute_phase_cfg, action_phase_cfg, masterchain_create_fee, + basechain_create_fee, wc, now); +} + +/** + * Fetches and initializes various configuration parameters from masterchain config for transaction processing. + * + * @param config The masterchain configuration. + * @param prev_blocks_info The tuple with information about previous blocks. + * @param old_mparams Pointer to store a dictionary of mandatory parameters (ConfigParam 9). + * @param storage_prices Pointer to store the storage prices. + * @param storage_phase_cfg Pointer to store the storage phase configuration. + * @param rand_seed Pointer to the random seed. Generates a new seed if the value is `td::Bits256::zero()`. + * @param compute_phase_cfg Pointer to store the compute phase configuration. + * @param action_phase_cfg Pointer to store the action phase configuration. + * @param masterchain_create_fee Pointer to store the masterchain create fee. + * @param basechain_create_fee Pointer to store the basechain create fee. + * @param wc The workchain ID. + * @param now The current Unix time. + */ +td::Status FetchConfigParams::fetch_config_params( + const block::Config& config, td::Ref prev_blocks_info, Ref* old_mparams, + std::vector* storage_prices, StoragePhaseConfig* storage_phase_cfg, + td::BitArray<256>* rand_seed, ComputePhaseConfig* compute_phase_cfg, ActionPhaseConfig* action_phase_cfg, + td::RefInt256* masterchain_create_fee, td::RefInt256* basechain_create_fee, ton::WorkchainId wc, + ton::UnixTime now) { + *old_mparams = config.get_config_param(9); + { + auto res = config.get_storage_prices(); + if (res.is_error()) { + return res.move_as_error(); + } + *storage_prices = res.move_as_ok(); + } + if (rand_seed->is_zero()) { + // generate rand seed + prng::rand_gen().strong_rand_bytes(rand_seed->data(), 32); + LOG(DEBUG) << "block random seed set to " << rand_seed->to_hex(); + } + TRY_RESULT(size_limits, config.get_size_limits_config()); + { + // compute compute_phase_cfg / storage_phase_cfg + auto cell = config.get_config_param(wc == ton::masterchainId ? 20 : 21); + if (cell.is_null()) { + return td::Status::Error(-668, "cannot fetch current gas prices and limits from masterchain configuration"); + } + if (!compute_phase_cfg->parse_GasLimitsPrices(std::move(cell), storage_phase_cfg->freeze_due_limit, + storage_phase_cfg->delete_due_limit)) { + return td::Status::Error(-668, "cannot unpack current gas prices and limits from masterchain configuration"); + } + TRY_RESULT_PREFIX(mc_gas_prices, config.get_gas_limits_prices(true), + "cannot unpack masterchain gas prices and limits: "); + compute_phase_cfg->mc_gas_prices = std::move(mc_gas_prices); + compute_phase_cfg->special_gas_full = config.get_global_version() >= 5; + storage_phase_cfg->enable_due_payment = config.get_global_version() >= 4; + storage_phase_cfg->global_version = config.get_global_version(); + compute_phase_cfg->block_rand_seed = *rand_seed; + compute_phase_cfg->max_vm_data_depth = size_limits.max_vm_data_depth; + compute_phase_cfg->global_config = config.get_root_cell(); + compute_phase_cfg->global_version = config.get_global_version(); + if (compute_phase_cfg->global_version >= 4) { + compute_phase_cfg->prev_blocks_info = std::move(prev_blocks_info); + } + if (compute_phase_cfg->global_version >= 6) { + compute_phase_cfg->unpacked_config_tuple = config.get_unpacked_config_tuple(now); + } + compute_phase_cfg->suspended_addresses = config.get_suspended_addresses(now); + compute_phase_cfg->size_limits = size_limits; + compute_phase_cfg->precompiled_contracts = config.get_precompiled_contracts_config(); + } + { + // compute action_phase_cfg + block::gen::MsgForwardPrices::Record rec; + auto cell = config.get_config_param(24); + if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) { + return td::Status::Error(-668, "cannot fetch masterchain message transfer prices from masterchain configuration"); + } + action_phase_cfg->fwd_mc = + block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, + (unsigned)rec.first_frac, (unsigned)rec.next_frac}; + cell = config.get_config_param(25); + if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) { + return td::Status::Error(-668, "cannot fetch standard message transfer prices from masterchain configuration"); + } + action_phase_cfg->fwd_std = + block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, + (unsigned)rec.first_frac, (unsigned)rec.next_frac}; + action_phase_cfg->workchains = &config.get_workchain_list(); + action_phase_cfg->bounce_msg_body = (config.has_capability(ton::capBounceMsgBody) ? 256 : 0); + action_phase_cfg->size_limits = size_limits; + action_phase_cfg->action_fine_enabled = config.get_global_version() >= 4; + action_phase_cfg->bounce_on_fail_enabled = config.get_global_version() >= 4; + action_phase_cfg->mc_blackhole_addr = config.get_burning_config().blackhole_addr; + } + { + // fetch block_grams_created + auto cell = config.get_config_param(14); + if (cell.is_null()) { + *basechain_create_fee = *masterchain_create_fee = td::zero_refint(); + } else { + block::gen::BlockCreateFees::Record create_fees; + if (!(tlb::unpack_cell(cell, create_fees) && + block::tlb::t_Grams.as_integer_to(create_fees.masterchain_block_fee, *masterchain_create_fee) && + block::tlb::t_Grams.as_integer_to(create_fees.basechain_block_fee, *basechain_create_fee))) { + return td::Status::Error(-668, "cannot unpack BlockCreateFees from configuration parameter #14"); + } + } + } + return td::Status::OK(); +} + } // namespace block diff --git a/crypto/block/transaction.h b/crypto/block/transaction.h index 10150f1c..6d8e8a29 100644 --- a/crypto/block/transaction.h +++ b/crypto/block/transaction.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/refcnt.hpp" @@ -29,13 +29,17 @@ #include "ton/ton-types.h" #include "block/block.h" #include "block/mc-config.h" +#include "precompiled-smc/PrecompiledSmartContract.h" namespace block { using td::Ref; using LtCellRef = std::pair>; struct Account; + +namespace transaction { struct Transaction; +} // namespace transaction struct CollatorError { std::string msg; @@ -65,10 +69,10 @@ struct NewOutMsg { NewOutMsg(ton::LogicalTime _lt, Ref _msg, Ref _trans) : lt(_lt), msg(std::move(_msg)), trans(std::move(_trans)) { } - bool operator<(const NewOutMsg& other) const & { + bool operator<(const NewOutMsg& other) const& { return lt < other.lt || (lt == other.lt && msg->get_hash() < other.msg->get_hash()); } - bool operator>(const NewOutMsg& other) const & { + bool operator>(const NewOutMsg& other) const& { return lt > other.lt || (lt == other.lt && other.msg->get_hash() < msg->get_hash()); } }; @@ -77,6 +81,8 @@ struct StoragePhaseConfig { const std::vector* pricing{nullptr}; td::RefInt256 freeze_due_limit; td::RefInt256 delete_due_limit; + bool enable_due_payment{false}; + int global_version = 0; StoragePhaseConfig() = default; StoragePhaseConfig(const std::vector* _pricing, td::RefInt256 freeze_limit = {}, td::RefInt256 delete_limit = {}) @@ -100,18 +106,28 @@ struct ComputePhaseConfig { td::uint64 gas_credit; td::uint64 flat_gas_limit = 0; td::uint64 flat_gas_price = 0; + bool special_gas_full = false; + block::GasLimitsPrices mc_gas_prices; static constexpr td::uint64 gas_infty = (1ULL << 63) - 1; td::RefInt256 gas_price256; td::RefInt256 max_gas_threshold; std::unique_ptr libraries; Ref global_config; td::BitArray<256> block_rand_seed; - ComputePhaseConfig(td::uint64 _gas_price = 0, td::uint64 _gas_limit = 0, td::uint64 _gas_credit = 0) - : gas_price(_gas_price), gas_limit(_gas_limit), special_gas_limit(_gas_limit), gas_credit(_gas_credit) { - compute_threshold(); - } - ComputePhaseConfig(td::uint64 _gas_price, td::uint64 _gas_limit, td::uint64 _spec_gas_limit, td::uint64 _gas_credit) - : gas_price(_gas_price), gas_limit(_gas_limit), special_gas_limit(_spec_gas_limit), gas_credit(_gas_credit) { + bool ignore_chksig{false}; + bool with_vm_log{false}; + td::uint16 max_vm_data_depth = 512; + int global_version = 0; + Ref prev_blocks_info; + Ref unpacked_config_tuple; + std::unique_ptr suspended_addresses; + SizeLimitsConfig size_limits; + int vm_log_verbosity = 0; + bool stop_on_accept_message = false; + PrecompiledContractsConfig precompiled_contracts; + bool dont_run_precompiled_ = false; + + ComputePhaseConfig() : gas_price(0), gas_limit(0), special_gas_limit(0), gas_credit(0) { compute_threshold(); } void compute_threshold(); @@ -130,13 +146,24 @@ struct ComputePhaseConfig { } bool parse_GasLimitsPrices(Ref cs, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit); bool parse_GasLimitsPrices(Ref cell, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit); + bool is_address_suspended(ton::WorkchainId wc, td::Bits256 addr) const; + + private: + bool parse_GasLimitsPrices_internal(Ref cs, td::RefInt256& freeze_due_limit, + td::RefInt256& delete_due_limit, td::uint64 flat_gas_limit = 0, + td::uint64 flat_gas_price = 0); }; struct ActionPhaseConfig { int max_actions{255}; + int bounce_msg_body{0}; // usually 0 or 256 bits MsgPrices fwd_std; MsgPrices fwd_mc; // from/to masterchain + SizeLimitsConfig size_limits; const WorkchainSet* workchains{nullptr}; + bool action_fine_enabled{false}; + bool bounce_on_fail_enabled{false}; + td::optional mc_blackhole_addr; const MsgPrices& fetch_msg_prices(bool is_masterchain) const { return is_masterchain ? fwd_mc : fwd_std; } @@ -145,12 +172,10 @@ struct ActionPhaseConfig { struct CreditPhase { td::RefInt256 due_fees_collected; block::CurrencyCollection credit; - // td::RefInt256 credit; - // Ref credit_extra; }; struct ComputePhase { - enum { sk_none, sk_no_state, sk_bad_state, sk_no_gas }; + enum { sk_none, sk_no_state, sk_bad_state, sk_no_gas, sk_suspended }; int skip_reason{sk_none}; bool success{false}; bool msg_state_used{false}; @@ -167,6 +192,8 @@ struct ComputePhase { Ref in_msg; Ref new_data; Ref actions; + std::string vm_log; + td::optional precompiled_gas_usage; }; struct ActionPhase { @@ -176,6 +203,7 @@ struct ActionPhase { bool code_changed{false}; bool action_list_invalid{false}; bool acc_delete_req{false}; + bool state_exceeds_limits{false}; enum { acst_unchanged = 0, acst_frozen = 2, acst_deleted = 3 }; int acc_status_change{acst_unchanged}; td::RefInt256 total_fwd_fees; // all fees debited from the account @@ -189,14 +217,13 @@ struct ActionPhase { Ref new_code; td::BitArray<256> action_list_hash; block::CurrencyCollection remaining_balance, reserved_balance; - // td::RefInt256 remaining_balance; - // Ref remaining_extra; - // td::RefInt256 reserved_balance; - // Ref reserved_extra; std::vector> action_list; // processed in reverse order std::vector> out_msgs; ton::LogicalTime end_lt; unsigned long long tot_msg_bits{0}, tot_msg_cells{0}; + td::RefInt256 action_fine; + bool need_bounce_on_fail = false; + bool bounce = false; }; struct BouncePhase { @@ -213,17 +240,16 @@ struct Account { bool is_special{false}; bool tick{false}; bool tock{false}; - bool created{false}; bool split_depth_set_{false}; unsigned char split_depth_{0}; int verbosity{3 * 0}; ton::UnixTime now_{0}; ton::WorkchainId workchain{ton::workchainInvalid}; td::BitArray<32> addr_rewrite; // rewrite (anycast) data, split_depth bits - ton::StdSmcAddress addr; // rewritten address (by replacing a prefix of `addr_orig` with `addr_rewrite`) - ton::StdSmcAddress addr_orig; // address indicated in smart-contract data - Ref my_addr; // address as stored in the smart contract (MsgAddressInt) - Ref my_addr_exact; // exact address without anycast info + ton::StdSmcAddress addr; // rewritten address (by replacing a prefix of `addr_orig` with `addr_rewrite`); it is the key in ShardAccounts + ton::StdSmcAddress addr_orig; // address indicated in smart-contract data (must coincide with hash of StateInit) + Ref my_addr; // address as stored in the smart contract (MsgAddressInt); corresponds to `addr_orig` + anycast info + Ref my_addr_exact; // exact address without anycast info; corresponds to `addr` and has no anycast (rewrite) info ton::LogicalTime last_trans_end_lt_; ton::LogicalTime last_trans_lt_; ton::Bits256 last_trans_hash_; @@ -231,11 +257,10 @@ struct Account { ton::UnixTime last_paid; vm::CellStorageStat storage_stat; block::CurrencyCollection balance; - // td::RefInt256 balance; - // Ref extra_balance; td::RefInt256 due_payment; Ref orig_total_state; // ^Account Ref total_state; // ^Account + Ref storage; // AccountStorage Ref inner_state; // StateInit ton::Bits256 state_hash; // hash of StateInit for frozen accounts Ref code, data, library, orig_library; @@ -250,8 +275,9 @@ struct Account { return balance; } bool set_address(ton::WorkchainId wc, td::ConstBitPtr new_addr); - bool unpack(Ref account, Ref extra, ton::UnixTime now, bool special = false); + bool unpack(Ref account, ton::UnixTime now, bool special); bool init_new(ton::UnixTime now); + bool deactivate(); bool recompute_tmp_addr(Ref& tmp_addr, int split_depth, td::ConstBitPtr orig_addr_rewrite) const; td::RefInt256 compute_storage_fees(ton::UnixTime now, const std::vector& pricing) const; bool is_masterchain() const { @@ -267,9 +293,10 @@ struct Account { bool create_account_block(vm::CellBuilder& cb); // stores an AccountBlock with all transactions protected: - friend struct Transaction; + friend struct transaction::Transaction; bool set_split_depth(int split_depth); bool check_split_depth(int split_depth) const; + bool forget_split_depth(); bool init_rewrite_addr(int split_depth, td::ConstBitPtr orig_addr_rewrite); private: @@ -281,8 +308,9 @@ struct Account { bool compute_my_addr(bool force = false); }; +namespace transaction { struct Transaction { - static constexpr unsigned max_msg_bits = (1 << 21), max_msg_cells = (1 << 13); + static constexpr unsigned max_allowed_merkle_depth = 2; enum { tr_none, tr_ord, @@ -314,17 +342,17 @@ struct Transaction { const Account& account; // only `commit` method modifies the account Ref my_addr, my_addr_exact; // almost the same as in account.* ton::LogicalTime start_lt, end_lt; - block::CurrencyCollection balance; + block::CurrencyCollection balance, original_balance; block::CurrencyCollection msg_balance_remaining; td::RefInt256 due_payment; td::RefInt256 in_fwd_fee, msg_fwd_fees; block::CurrencyCollection total_fees{0}; + block::CurrencyCollection blackhole_burned{0}; ton::UnixTime last_paid; Ref root; Ref new_total_state; + Ref new_storage; Ref new_inner_state; - // Ref extra_balance; - // Ref msg_extra; Ref new_code, new_data, new_library; Ref in_msg, in_msg_state; Ref in_msg_body; @@ -338,17 +366,21 @@ struct Transaction { std::unique_ptr action_phase; std::unique_ptr bounce_phase; vm::CellStorageStat new_storage_stat; + bool gas_limit_overridden{false}; Transaction(const Account& _account, int ttype, ton::LogicalTime req_start_lt, ton::UnixTime _now, Ref _inmsg = {}); bool unpack_input_msg(bool ihr_delivered, const ActionPhaseConfig* cfg); bool check_in_msg_state_hash(); - bool prepare_storage_phase(const StoragePhaseConfig& cfg, bool force_collect = true); + bool prepare_storage_phase(const StoragePhaseConfig& cfg, bool force_collect = true, bool adjust_msg_value = false); bool prepare_credit_phase(); + td::uint64 gas_bought_for(const ComputePhaseConfig& cfg, td::RefInt256 nanograms); bool compute_gas_limits(ComputePhase& cp, const ComputePhaseConfig& cfg); Ref prepare_vm_stack(ComputePhase& cp); std::vector> compute_vm_libraries(const ComputePhaseConfig& cfg); + bool run_precompiled_contract(const ComputePhaseConfig& cfg, precompiled::PrecompiledSmartContract& precompiled); bool prepare_compute_phase(const ComputePhaseConfig& cfg); bool prepare_action_phase(const ActionPhaseConfig& cfg); + td::Status check_state_limits(const SizeLimitsConfig& size_limits, bool update_storage_stat = true); bool prepare_bounce_phase(const ActionPhaseConfig& cfg); bool compute_state(); bool serialize(); @@ -358,9 +390,7 @@ struct Transaction { td::Result estimate_block_storage_profile_incr( const vm::NewCellStorageStat& store_stat, const vm::CellUsageTree* usage_tree) const; - bool update_block_storage_profile(vm::NewCellStorageStat& store_stat, const vm::CellUsageTree* usage_tree) const; - bool would_fit(unsigned cls, const block::BlockLimitStatus& blk_lim_st) const; - bool update_limits(block::BlockLimitStatus& blk_lim_st) const; + bool update_limits(block::BlockLimitStatus& blk_lim_st, bool with_gas = true, bool with_size = true) const; Ref commit(Account& _account); // _account should point to the same account LtCellRef extract_out_msg(unsigned i); @@ -382,7 +412,23 @@ struct Transaction { bool serialize_compute_phase(vm::CellBuilder& cb); bool serialize_action_phase(vm::CellBuilder& cb); bool serialize_bounce_phase(vm::CellBuilder& cb); - bool unpack_msg_state(bool lib_only = false); + bool unpack_msg_state(const ComputePhaseConfig& cfg, bool lib_only = false, bool forbid_public_libs = false); +}; +} // namespace transaction + +struct FetchConfigParams { + static td::Status fetch_config_params(const block::ConfigInfo& config, Ref* old_mparams, + std::vector* storage_prices, + StoragePhaseConfig* storage_phase_cfg, td::BitArray<256>* rand_seed, + ComputePhaseConfig* compute_phase_cfg, ActionPhaseConfig* action_phase_cfg, + td::RefInt256* masterchain_create_fee, td::RefInt256* basechain_create_fee, + ton::WorkchainId wc, ton::UnixTime now); + static td::Status fetch_config_params(const block::Config& config, Ref prev_blocks_info, + Ref* old_mparams, std::vector* storage_prices, + StoragePhaseConfig* storage_phase_cfg, td::BitArray<256>* rand_seed, + ComputePhaseConfig* compute_phase_cfg, ActionPhaseConfig* action_phase_cfg, + td::RefInt256* masterchain_create_fee, td::RefInt256* basechain_create_fee, + ton::WorkchainId wc, ton::UnixTime now); }; } // namespace block diff --git a/crypto/common/AtomicRef.h b/crypto/common/AtomicRef.h index 91ad7334..e82bcda4 100644 --- a/crypto/common/AtomicRef.h +++ b/crypto/common/AtomicRef.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/crypto/common/bigexp.cpp b/crypto/common/bigexp.cpp index cdc7c778..365f0595 100644 --- a/crypto/common/bigexp.cpp +++ b/crypto/common/bigexp.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "bigexp.h" #include "td/utils/bits.h" diff --git a/crypto/common/bigexp.h b/crypto/common/bigexp.h index d58c8224..54dee48a 100644 --- a/crypto/common/bigexp.h +++ b/crypto/common/bigexp.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/crypto/common/bigint.cpp b/crypto/common/bigint.cpp index 3229e681..41780fb9 100644 --- a/crypto/common/bigint.cpp +++ b/crypto/common/bigint.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "common/bigint.hpp" diff --git a/crypto/common/bigint.hpp b/crypto/common/bigint.hpp index 6734f07e..f8756d9f 100644 --- a/crypto/common/bigint.hpp +++ b/crypto/common/bigint.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include @@ -174,6 +174,7 @@ class AnyIntView { public: enum { word_bits = Tr::word_bits, word_shift = Tr::word_shift }; typedef typename Tr::word_t word_t; + typedef typename Tr::uword_t uword_t; int& n_; PropagateConstSpan digits; @@ -262,15 +263,18 @@ class AnyIntView { return digits[size() - 1]; } double top_double() const { - return size() > 1 ? (double)digits[size() - 1] + (double)digits[size() - 2] * (1.0 / Tr::Base) + return size() > 1 ? (double)digits[size() - 1] + (double)digits[size() - 2] * Tr::InvBase : (double)digits[size() - 1]; } + bool is_odd_any() const { + return size() > 0 && (digits[0] & 1); + } word_t to_long_any() const; int parse_hex_any(const char* str, int str_len, int* frac = nullptr); int parse_binary_any(const char* str, int str_len, int* frac = nullptr); std::string to_dec_string_destroy_any(); std::string to_dec_string_slow_destroy_any(); - std::string to_hex_string_any(bool upcase = false) const; + std::string to_hex_string_any(bool upcase = false, int zero_pad = 0) const; std::string to_hex_string_slow_destroy_any(); std::string to_binary_string_any() const; @@ -284,6 +288,7 @@ class BigIntG { public: enum { word_bits = Tr::word_bits, word_shift = Tr::word_shift, max_bits = len, word_cnt = len / word_shift + 1 }; typedef typename Tr::word_t word_t; + typedef typename Tr::uword_t uword_t; typedef Tr Traits; typedef BigIntG DoubleInt; @@ -306,7 +311,34 @@ class BigIntG { BigIntG() : n(0) { } explicit BigIntG(word_t x) : n(1) { - digits[0] = x; + if (x >= -Tr::Half && x < Tr::Half) { + digits[0] = x; + } else if (len <= 1) { + digits[0] = x; + normalize_bool(); + } else { + digits[0] = ((x ^ Tr::Half) & (Tr::Base - 1)) - Tr::Half; + digits[n++] = (x >> Tr::word_shift) + (digits[0] < 0); + } + } + explicit BigIntG(uword_t x) : n(1) { + if (x < (uword_t)Tr::Half) { + digits[0] = x; + } else if (len <= 1) { + digits[0] = x; + normalize_bool(); + } else { + digits[0] = ((x ^ Tr::Half) & (Tr::Base - 1)) - Tr::Half; + digits[n++] = (x >> Tr::word_shift) + (digits[0] < 0); + } + } + explicit BigIntG(unsigned x) : BigIntG(uword_t(x)) { + } + explicit BigIntG(int x) : BigIntG(word_t(x)) { + } + explicit BigIntG(unsigned long x) : BigIntG(uword_t(x)) { + } + explicit BigIntG(long x) : BigIntG(word_t(x)) { } BigIntG(const BigIntG& x) : n(x.n) { std::memcpy(digits, x.digits, n * sizeof(word_t)); @@ -644,7 +676,7 @@ class BigIntG { std::string to_dec_string_destroy(); std::string to_dec_string_slow() const; std::string to_hex_string_slow() const; - std::string to_hex_string(bool upcase = false) const; + std::string to_hex_string(bool upcase = false, int zero_pad = 0) const; std::string to_binary_string() const; double to_double() const { return is_valid() ? ldexp(top_double(), (n - 1) * word_shift) : NAN; @@ -652,6 +684,15 @@ class BigIntG { word_t to_long() const { return as_any_int().to_long_any(); } + bool is_odd() const { + return n > 0 && (digits[0] & 1); + } + bool is_even() const { + return n > 0 && !(digits[0] & 1); + } + word_t mod_pow2_short(int pow) const { + return n > 0 ? digits[0] & ((1ULL << pow) - 1) : 0; + } private: word_t top_word() const { @@ -739,7 +780,7 @@ bool AnyIntView::add_pow2_any(int exponent, int factor) { while (size() <= k) { digits[inc_size()] = 0; } - digits[k] += (factor << dm.rem); + digits[k] += factor * ((word_t)1 << dm.rem); return true; } @@ -944,7 +985,7 @@ bool AnyIntView::add_mul_any(const AnyIntView& yp, const AnyIntView& if (hi && hi != -1) { return invalidate_bool(); } - digits[size() - 1] += (hi << word_shift); + digits[size() - 1] += ((uword_t)hi << word_shift); } return true; } @@ -989,7 +1030,7 @@ int AnyIntView::sgn_un_any() const { } int i = size() - 2; do { - v <<= word_shift; + v *= Tr::Base; word_t w = digits[i]; if (w >= -v + Tr::MaxDenorm) { return 1; @@ -1034,7 +1075,7 @@ typename Tr::word_t AnyIntView::to_long_any() const { } else if (size() == 1) { return digits[0]; } else { - word_t v = digits[0] + (digits[1] << word_shift); // approximation mod 2^64 + word_t v = (uword_t)digits[0] + ((uword_t)digits[1] << word_shift); // approximation mod 2^64 word_t w = (v & (Tr::Base - 1)) - digits[0]; w >>= word_shift; w += (v >> word_shift); // excess of approximation divided by Tr::Base @@ -1069,12 +1110,16 @@ int AnyIntView::cmp_any(const AnyIntView& yp) const { template int AnyIntView::cmp_any(word_t y) const { - if (size() > 1) { - return top_word() < 0 ? -1 : 1; - } else if (size() == 1) { + if (size() == 1) { return digits[0] < y ? -1 : (digits[0] > y ? 1 : 0); - } else { + } else if (!size()) { return 0x80000000; + } else if (size() == 2 && (y >= Tr::Half || y < -Tr::Half)) { + word_t x0 = digits[0] & (Tr::Base - 1), y0 = y & (Tr::Base - 1); + word_t x1 = digits[1] + (digits[0] >> Tr::word_shift), y1 = (y >> Tr::word_shift); + return x1 < y1 ? -1 : (x1 > y1 ? 1 : (x0 < y0 ? -1 : (x0 > y0 ? 1 : 0))); + } else { + return top_word() < 0 ? -1 : 1; } } @@ -1091,7 +1136,7 @@ int AnyIntView::cmp_un_any(const AnyIntView& yp) const { return -1; } while (xn > yn) { - v <<= word_shift; + v *= Tr::Base; word_t w = T::eval(digits[--xn]); if (w >= -v + Tr::MaxDenorm) { return 1; @@ -1108,7 +1153,7 @@ int AnyIntView::cmp_un_any(const AnyIntView& yp) const { return -1; } while (yn > xn) { - v <<= word_shift; + v *= Tr::Base; word_t w = yp.digits[--yn]; if (w <= v - Tr::MaxDenorm) { return 1; @@ -1121,7 +1166,7 @@ int AnyIntView::cmp_un_any(const AnyIntView& yp) const { v = 0; } while (--xn >= 0) { - v <<= word_shift; + v *= Tr::Base; word_t w = T::eval(digits[xn]) - yp.digits[xn]; if (w >= -v + Tr::MaxDenorm) { return 1; @@ -1168,7 +1213,7 @@ int AnyIntView::divmod_tiny_any(int y) { } int rem = 0; for (int i = size() - 1; i >= 0; i--) { - auto divmod = std::div(digits[i] + ((word_t)rem << word_shift), (word_t)y); + auto divmod = std::div(digits[i] + ((uword_t)rem << word_shift), (word_t)y); digits[i] = divmod.quot; rem = (int)divmod.rem; if ((rem ^ y) < 0 && rem) { @@ -1238,7 +1283,7 @@ bool AnyIntView::mul_add_short_any(word_t y, word_t z) { z += (digits[size() - 1] >> word_shift); digits[size() - 1] &= Tr::Base - 1; if (!z || z == -1) { - digits[size() - 1] += (z << word_shift); + digits[size() - 1] += ((uword_t)z << word_shift); return true; } else { return false; @@ -1294,14 +1339,14 @@ bool AnyIntView::mod_div_any(const AnyIntView& yp, AnyIntView& quot, if (k > quot.max_size()) { return invalidate_bool(); } - quot.set_size(k); + quot.set_size(std::max(k, 1)); + quot.digits[0] = 0; } else { if (k >= quot.max_size()) { return invalidate_bool(); } quot.set_size(k + 1); - double x_top = top_double(); - word_t q = std::llrint(x_top * y_inv * Tr::InvBase); + word_t q = std::llrint(top_double() * y_inv * Tr::InvBase); quot.digits[k] = q; int i = yp.size() - 1; word_t hi = 0; @@ -1309,31 +1354,33 @@ bool AnyIntView::mod_div_any(const AnyIntView& yp, AnyIntView& quot, while (--i >= 0) { Tr::sub_mul(&digits[k + i + 1], &digits[k + i], q, yp.digits[i]); } - digits[size() - 1] += (hi << word_shift); + digits[size() - 1] += ((uword_t)hi << word_shift); } } else { quot.set_size(1); quot.digits[0] = 0; } while (--k >= 0) { - double x_top = top_double(); - word_t q = std::llrint(x_top * y_inv); + word_t q = std::llrint(top_double() * y_inv); quot.digits[k] = q; for (int i = yp.size() - 1; i >= 0; --i) { Tr::sub_mul(&digits[k + i + 1], &digits[k + i], q, yp.digits[i]); } dec_size(); - digits[size() - 1] += (digits[size()] << word_shift); + digits[size() - 1] += ((uword_t)digits[size()] << word_shift); } - if (size() >= yp.size()) { - assert(size() == yp.size()); - double x_top = top_double(); - double t = x_top * y_inv * Tr::InvBase; + if (size() >= yp.size() - 1) { + assert(size() <= yp.size()); + bool grow = (size() < yp.size()); + double t = top_double() * y_inv * (grow ? Tr::InvBase * Tr::InvBase : Tr::InvBase); if (round_mode >= 0) { t += (round_mode ? 1 : 0.5); } word_t q = std::llrint(std::floor(t)); if (q) { + if (grow) { + digits[inc_size()] = 0; + } for (int i = 0; i < size(); i++) { digits[i] -= q * yp.digits[i]; } @@ -1390,6 +1437,7 @@ bool AnyIntView::mod_div_any(const AnyIntView& yp, AnyIntView& quot, return normalize_bool_any(); } +// works for almost-normalized numbers (digits -Base+1 .. Base-1, top non-zero), result also almost-normalized template bool AnyIntView::mod_pow2_any(int exponent) { if (!is_valid()) { @@ -1423,7 +1471,7 @@ bool AnyIntView::mod_pow2_any(int exponent) { dec_size(); q += word_shift; } - word_t pow = ((word_t)1 << q); + uword_t pow = ((uword_t)1 << q); word_t v = digits[size() - 1] & (pow - 1); if (!v) { int k = size() - 1; @@ -1441,25 +1489,21 @@ bool AnyIntView::mod_pow2_any(int exponent) { if (exponent >= max_size() * word_shift) { return invalidate_bool(); } - if (q - word_shift >= 0) { + if (q - word_shift >= 0) { // original top digit was a non-zero multiple of Base, impossible(?) digits[size() - 1] = 0; digits[inc_size()] = ((word_t)1 << (q - word_shift)); - } - if (q - word_shift == -1 && size() < max_size() - 1) { + } else if (q - word_shift == -1 && size() < max_size()) { digits[size() - 1] = -Tr::Half; digits[inc_size()] = 1; } else { digits[size() - 1] = pow; } return true; - } else if (v >= Tr::Half) { - if (size() == max_size() - 1) { - return invalidate_bool(); - } else { - digits[size() - 1] = v | -Tr::Half; - digits[inc_size()] = ((word_t)1 << (q - word_shift)); - return true; - } + } else if (v >= Tr::Half && size() < max_size()) { + word_t w = (((v >> (word_shift - 1)) + 1) >> 1); + digits[size() - 1] = (uword_t)v - ((uword_t)w << word_shift); + digits[inc_size()] = w; + return true; } else { digits[size() - 1] = v; return true; @@ -1595,7 +1639,7 @@ bool AnyIntView::lshift_any(int exponent) { } else if (v != -1) { return invalidate_bool(); } else { - digits[size() - 1] += (v << word_shift); + digits[size() - 1] += ((uword_t)v << word_shift); } } if (q) { @@ -1722,7 +1766,7 @@ int AnyIntView::bit_size_any(bool sgnd) const { int k = size() - 1; word_t q = digits[k]; if (k > 0 && q < Tr::MaxDenorm / 2) { - q <<= word_shift; + q *= Tr::Base; q += digits[--k]; } if (!k) { @@ -1738,7 +1782,7 @@ int AnyIntView::bit_size_any(bool sgnd) const { } else if (q <= -Tr::MaxDenorm / 2) { return s; } - q <<= word_shift; + q *= Tr::Base; q += digits[--k]; } return q >= 0 ? s + 1 : s; @@ -1746,7 +1790,7 @@ int AnyIntView::bit_size_any(bool sgnd) const { int k = size() - 1; word_t q = digits[k]; if (k > 0 && q > -Tr::MaxDenorm / 2) { - q <<= word_shift; + q *= Tr::Base; q += digits[--k]; } if (!k) { @@ -1762,7 +1806,7 @@ int AnyIntView::bit_size_any(bool sgnd) const { } else if (q <= -Tr::MaxDenorm / 2) { return s + 1; } - q <<= word_shift; + q *= Tr::Base; q += digits[--k]; } return q >= 0 ? s : s + 1; @@ -1789,7 +1833,7 @@ bool AnyIntView::export_bytes_any(unsigned char* buff, std::size_t buff_size for (int i = 0; i < size(); i++) { if ((word_shift & 7) && word_shift + 8 >= word_bits && k >= word_bits - word_shift - 1) { int k1 = 8 - k; - v += (digits[i] << k) & 0xff; + v += ((uword_t)digits[i] << k) & 0xff; if (ptr > buff) { *--ptr = (unsigned char)(v & 0xff); } else if ((unsigned char)(v & 0xff) != s) { @@ -1799,7 +1843,7 @@ bool AnyIntView::export_bytes_any(unsigned char* buff, std::size_t buff_size v += (digits[i] >> k1); k += word_shift - 8; } else { - v += (digits[i] << k); + v += ((uword_t)digits[i] << k); k += word_shift; } while (k >= 8) { @@ -1840,7 +1884,7 @@ bool AnyIntView::export_bytes_lsb_any(unsigned char* buff, std::size_t buff_ for (int i = 0; i < size(); i++) { if ((word_shift & 7) && word_shift + 8 >= word_bits && k >= word_bits - word_shift - 1) { int k1 = 8 - k; - v += (digits[i] << k) & 0xff; + v += ((uword_t)digits[i] << k) & 0xff; if (buff < end) { *buff++ = (unsigned char)(v & 0xff); } else if ((unsigned char)(v & 0xff) != s) { @@ -1850,7 +1894,7 @@ bool AnyIntView::export_bytes_lsb_any(unsigned char* buff, std::size_t buff_ v += (digits[i] >> k1); k += word_shift - 8; } else { - v += (digits[i] << k); + v += ((uword_t)digits[i] << k); k += word_shift; } while (k >= 8) { @@ -1894,7 +1938,7 @@ bool AnyIntView::export_bits_any(unsigned char* buff, int offs, unsigned bit return false; } } - td::bitstring::bits_store_long_top(buff, offs, v << (64 - bits), bits); + td::bitstring::bits_store_long_top(buff, offs, (unsigned long long)v << (64 - bits), bits); } else { if (!sgnd && v < 0) { return false; @@ -1917,7 +1961,7 @@ bool AnyIntView::export_bits_any(unsigned char* buff, int offs, unsigned bit for (int i = 0; i < size(); i++) { if (word_shift + 8 >= word_bits && k >= word_bits - word_shift - 1) { int k1 = 8 - k; - v += (digits[i] << k) & 0xff; + v += ((uword_t)digits[i] << k) & 0xff; if (ptr > buff) { if (--ptr > buff) { *ptr = (unsigned char)(v & 0xff); @@ -1935,7 +1979,7 @@ bool AnyIntView::export_bits_any(unsigned char* buff, int offs, unsigned bit v += (digits[i] >> k1); k += word_shift - 8; } else { - v += (digits[i] << k); + v += ((uword_t)digits[i] << k); k += word_shift; } while (k >= 8) { @@ -2000,7 +2044,7 @@ bool AnyIntView::import_bytes_any(const unsigned char* buff, std::size_t buf return invalidate_bool(); } } - v |= (((word_t) * --ptr) << k); + v |= (((uword_t) * --ptr) << k); k += 8; } if (s) { @@ -2015,7 +2059,9 @@ bool AnyIntView::import_bits_any(const unsigned char* buff, int offs, unsign if (bits < word_shift) { set_size(1); unsigned long long val = td::bitstring::bits_load_long_top(buff, offs, bits); - if (sgnd) { + if (bits == 0) { + digits[0] = 0; + } else if (sgnd) { digits[0] = ((long long)val >> (64 - bits)); } else { digits[0] = (val >> (64 - bits)); @@ -2284,16 +2330,19 @@ std::string AnyIntView::to_hex_string_slow_destroy_any() { } template -std::string AnyIntView::to_hex_string_any(bool upcase) const { +std::string AnyIntView::to_hex_string_any(bool upcase, int zero_pad) const { if (!is_valid()) { return "NaN"; } int s = sgn(), k = 0; if (!s) { + if (zero_pad > 0) { + return std::string(zero_pad, '0'); + } return "0"; } std::string x; - x.reserve(((size() * word_shift + word_bits) >> 2) + 2); + x.reserve(2 + std::max((size() * word_shift + word_bits) >> 2, zero_pad)); assert(word_shift < word_bits - 4); const char* hex_digs = (upcase ? HEX_digits : hex_digits); word_t v = 0; @@ -2311,6 +2360,11 @@ std::string AnyIntView::to_hex_string_any(bool upcase) const { x += hex_digs[v & 15]; v >>= 4; } + if (zero_pad > 0) { + while (x.size() < (unsigned)zero_pad) { + x += '0'; + } + } if (s < 0) { x += '-'; } @@ -2492,8 +2546,8 @@ std::string BigIntG::to_hex_string_slow() const { } template -std::string BigIntG::to_hex_string(bool upcase) const { - return as_any_int().to_hex_string_any(upcase); +std::string BigIntG::to_hex_string(bool upcase, int zero_pad) const { + return as_any_int().to_hex_string_any(upcase, zero_pad); } template @@ -2515,6 +2569,11 @@ extern template class AnyIntView; extern template class BigIntG<257, BigIntInfo>; typedef BigIntG<257, BigIntInfo> BigInt256; +template +BigIntG make_bigint(long long x) { + return BigIntG{x}; +} + namespace literals { extern BigInt256 operator""_i256(const char* str, std::size_t str_len); diff --git a/crypto/common/bitstring.cpp b/crypto/common/bitstring.cpp index 6a23f072..5135cdf0 100644 --- a/crypto/common/bitstring.cpp +++ b/crypto/common/bitstring.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "common/bitstring.h" #include @@ -22,7 +22,7 @@ #include "td/utils/as.h" #include "td/utils/bits.h" #include "td/utils/misc.h" -#include "crypto/openssl/digest.h" +#include "crypto/openssl/digest.hpp" namespace td { @@ -130,7 +130,7 @@ void bits_memcpy(unsigned char* to, int to_offs, const unsigned char* from, int from_offs &= 7; to_offs &= 7; //fprintf(stderr, "bits_memcpy: from=%p (%02x) to=%p (%02x) from_offs=%d to_offs=%d count=%lu\n", from, *from, to, *to, from_offs, to_offs, bit_count); - int sz = (int)bit_count; + int sz = static_cast(bit_count); bit_count += from_offs; if (from_offs == to_offs) { if (bit_count < 8) { @@ -191,7 +191,7 @@ void bits_memcpy(unsigned char* to, int to_offs, const unsigned char* from, int *to++ = (unsigned char)(acc >> b); } if (b > 0) { - *to = (unsigned char)((*to & (0xff >> b)) | ((int)acc << (8 - b))); + *to = (unsigned char)((*to & (0xff >> b)) | ((unsigned)acc << (8 - b))); } } } @@ -206,7 +206,7 @@ void bits_memset(unsigned char* to, int to_offs, bool val, std::size_t bit_count } to += (to_offs >> 3); to_offs &= 7; - int sz = (int)bit_count; + int sz = static_cast(bit_count); bit_count += to_offs; int c = *to; if (bit_count <= 8) { @@ -301,7 +301,7 @@ std::size_t bits_memscan(const unsigned char* ptr, int offs, std::size_t bit_cou ptr++; } while (rem >= 8 && !td::is_aligned_pointer<8>(ptr)) { - v = ((*ptr++ ^ xor_val) << 24); + v = ((unsigned)(*ptr++ ^ xor_val) << 24); // std::cerr << "[B] rem=" << rem << " ptr=" << (const void*)(ptr - 1) << " v=" << std::hex << v << std::dec << std::endl; if (v) { return bit_count - rem + td::count_leading_zeroes_non_zero32(v); @@ -319,7 +319,7 @@ std::size_t bits_memscan(const unsigned char* ptr, int offs, std::size_t bit_cou rem -= 64; } while (rem >= 8) { - v = ((*ptr++ ^ xor_val) << 24); + v = ((unsigned)(*ptr++ ^ xor_val) << 24); // std::cerr << "[D] rem=" << rem << " ptr=" << (const void*)(ptr - 1) << " v=" << std::hex << v << std::dec << std::endl; if (v) { return bit_count - rem + td::count_leading_zeroes_non_zero32(v); @@ -327,7 +327,7 @@ std::size_t bits_memscan(const unsigned char* ptr, int offs, std::size_t bit_cou rem -= 8; } if (rem > 0) { - v = ((*ptr ^ xor_val) << 24); + v = ((unsigned)(*ptr ^ xor_val) << 24); // std::cerr << "[E] rem=" << rem << " ptr=" << (const void*)ptr << " v=" << std::hex << v << std::dec << std::endl; c = td::count_leading_zeroes32(v); return c < rem ? bit_count - rem + c : bit_count; @@ -505,7 +505,7 @@ unsigned long long bits_load_long_top(ConstBitPtr from, unsigned top_bits) { } unsigned long long bits_load_ulong(ConstBitPtr from, unsigned bits) { - return bits_load_long_top(from, bits) >> (64 - bits); + return bits == 0 ? 0 : bits_load_long_top(from, bits) >> (64 - bits); } long long bits_load_long(ConstBitPtr from, unsigned bits) { @@ -596,7 +596,7 @@ long parse_bitstring_hex_literal(unsigned char* buff, std::size_t buff_size, con unsigned char* ptr = buff; const char* rptr = str; while (rptr < str_end) { - int c = *rptr++; + char c = *rptr++; if (c == ' ' || c == '\t') { continue; } @@ -627,14 +627,14 @@ long parse_bitstring_hex_literal(unsigned char* buff, std::size_t buff_size, con if (cmpl && bits) { int t = (hex_digits_count & 1) ? (0x100 + *ptr) >> 4 : (0x100 + *--ptr); while (bits > 0) { + if (t == 1) { + t = 0x100 + *--ptr; + } --bits; if (t & 1) { break; } t >>= 1; - if (t == 1) { - t = 0x100 + *--ptr; - } } } return bits; diff --git a/crypto/common/bitstring.h b/crypto/common/bitstring.h index 901ff709..dc3a2fa5 100644 --- a/crypto/common/bitstring.h +++ b/crypto/common/bitstring.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/refcnt.hpp" @@ -284,7 +284,7 @@ class BitSliceGen { ensure_throw(set_size_bool(bits)); return *this; } - BitSliceGen subslice(unsigned from, unsigned bits) const& { + BitSliceGen subslice(unsigned from, unsigned bits) const & { return BitSliceGen(*this, from, bits); } BitSliceGen subslice(unsigned from, unsigned bits) && { @@ -575,6 +575,14 @@ class BitArray { std::string to_binary() const { return bitstring::bits_to_binary(cbits(), size()); } + long from_hex(td::Slice hex_str, bool allow_partial = false) { + auto res = bitstring::parse_bitstring_hex_literal(data(), m, hex_str.begin(), hex_str.end()); + return allow_partial ? std::min(res, n) : (res == n ? res : -1); + } + long from_binary(td::Slice bin_str, bool allow_partial = false) { + auto res = bitstring::parse_bitstring_binary_literal(bits(), n, bin_str.begin(), bin_str.end()); + return allow_partial ? std::min(res, n) : (res == n ? res : -1); + } int compare(const BitArray& other) const { return (n % 8 == 0) ? std::memcmp(data(), other.data(), n / 8) : bitstring::bits_memcmp(bits(), other.bits(), n); } diff --git a/crypto/common/linalloc.hpp b/crypto/common/linalloc.hpp new file mode 100644 index 00000000..da2ed05b --- /dev/null +++ b/crypto/common/linalloc.hpp @@ -0,0 +1,50 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2020 Telegram Systems LLP +*/ +namespace td { + +class LinearAllocator { + std::size_t size; + char *ptr, *cur, *end; + + public: + LinearAllocator(std::size_t _size) : size(_size) { + cur = ptr = (char*)malloc(size); + if (!ptr) { + throw std::bad_alloc(); + } + end = ptr + size; + } + ~LinearAllocator() { + free(ptr); + } + void* allocate(std::size_t count) { + char* t = cur; + cur += (count + 7) & -8; + if (cur > end) { + throw std::bad_alloc(); + } + return (void*)t; + } +}; + +} // namespace td + +inline void* operator new(std::size_t count, td::LinearAllocator& alloc) { + return alloc.allocate(count); +} diff --git a/crypto/common/promiseop.hpp b/crypto/common/promiseop.hpp new file mode 100644 index 00000000..1e14bc7d --- /dev/null +++ b/crypto/common/promiseop.hpp @@ -0,0 +1,72 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2020 Telegram Systems LLP +*/ +#pragma once +#include "refcnt.hpp" +#include "td/actor/PromiseFuture.h" + +namespace td { + +template +class BinaryPromiseMerger : public CntObject { + Result first_; + Result second_; + Promise> promise_; + std::atomic pending_; + + public: + BinaryPromiseMerger(Promise> promise) : promise_(std::move(promise)), pending_(2) { + } + static std::pair, Promise> split(Promise> promise) { + auto ref = make_ref(std::move(promise)); + auto& obj = ref.write(); + return std::make_pair(obj.left(), obj.right()); + } + + private: + Promise left() { + return [this, self = Ref(this)](Result res) { + first_ = std::move(res); + work(); + }; + } + Promise right() { + return [this, self = Ref(this)](Result res) { + second_ = std::move(res); + work(); + }; + } + void work() { + if (!--pending_) { + if (first_.is_error()) { + promise_.set_error(first_.move_as_error()); + } else if (second_.is_error()) { + promise_.set_error(second_.move_as_error()); + } else { + promise_.set_result(std::pair(first_.move_as_ok(), second_.move_as_ok())); + } + } + } +}; + +template +std::pair, Promise> split_promise(Promise> promise) { + return BinaryPromiseMerger::split(std::move(promise)); +} + +} // namespace td diff --git a/crypto/common/refcnt.cpp b/crypto/common/refcnt.cpp index 5b95c8f3..7ef0857c 100644 --- a/crypto/common/refcnt.cpp +++ b/crypto/common/refcnt.cpp @@ -14,13 +14,18 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "refcnt.hpp" #include "td/utils/ScopeGuard.h" namespace td { + +Ref CntObject::clone() const { + return Ref{make_copy(), Ref::acquire_t()}; +} + namespace detail { struct SafeDeleter { public: diff --git a/crypto/common/refcnt.hpp b/crypto/common/refcnt.hpp index 08a58412..ef50c3b9 100644 --- a/crypto/common/refcnt.hpp +++ b/crypto/common/refcnt.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -83,6 +83,7 @@ class CntObject { void assert_unique() const { assert(is_unique()); } + Ref clone() const; }; typedef Ref RefAny; @@ -159,6 +160,7 @@ struct static_cast_ref {}; namespace detail { void safe_delete(const CntObject* ptr); } + template class Ref { T* ptr; diff --git a/crypto/common/refint.cpp b/crypto/common/refint.cpp index 662322c6..a2ccdaa6 100644 --- a/crypto/common/refint.cpp +++ b/crypto/common/refint.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "common/refint.h" #include @@ -38,6 +38,11 @@ RefInt256 operator+(RefInt256 x, long long y) { return x; } +RefInt256 operator+(RefInt256 x, const BigInt256& y) { + (x.write() += y).normalize(); + return x; +} + RefInt256 operator-(RefInt256 x, RefInt256 y) { (x.write() -= *y).normalize(); return x; @@ -48,6 +53,11 @@ RefInt256 operator-(RefInt256 x, long long y) { return x; } +RefInt256 operator-(RefInt256 x, const BigInt256& y) { + (x.write() -= y).normalize(); + return x; +} + RefInt256 operator-(RefInt256 x) { x.write().negate().normalize(); return x; @@ -69,6 +79,12 @@ RefInt256 operator*(RefInt256 x, long long y) { return x; } +RefInt256 operator*(RefInt256 x, const BigInt256& y) { + RefInt256 z{true, 0}; + z.write().add_mul(*x, y).normalize(); + return z; +} + RefInt256 operator/(RefInt256 x, RefInt256 y) { RefInt256 quot{true}; x.write().mod_div(*y, quot.write()); @@ -102,6 +118,22 @@ std::pair divmod(RefInt256 x, RefInt256 y, int round_mode) return std::make_pair(std::move(quot), std::move(x)); } +RefInt256 muldiv(RefInt256 x, RefInt256 y, RefInt256 z, int round_mode) { + typename td::BigInt256::DoubleInt tmp{0}; + tmp.add_mul(*x, *y); + RefInt256 quot{true}; + tmp.mod_div(*z, quot.unique_write(), round_mode); + quot.write().normalize(); + return quot; +} + +std::pair muldivmod(RefInt256 x, RefInt256 y, RefInt256 z, int round_mode) { + typename td::BigInt256::DoubleInt tmp{0}, quot; + tmp.add_mul(*x, *y); + tmp.mod_div(*z, quot, round_mode); + return std::make_pair(td::make_refint(quot.normalize()), td::make_refint(tmp)); +} + RefInt256 operator&(RefInt256 x, RefInt256 y) { x.write() &= *y; return x; @@ -142,6 +174,11 @@ RefInt256& operator+=(RefInt256& x, long long y) { return x; } +RefInt256& operator+=(RefInt256& x, const BigInt256& y) { + (x.write() += y).normalize(); + return x; +} + RefInt256& operator-=(RefInt256& x, RefInt256 y) { (x.write() -= *y).normalize(); return x; @@ -152,6 +189,11 @@ RefInt256& operator-=(RefInt256& x, long long y) { return x; } +RefInt256& operator-=(RefInt256& x, const BigInt256& y) { + (x.write() -= y).normalize(); + return x; +} + RefInt256& operator*=(RefInt256& x, RefInt256 y) { RefInt256 z{true, 0}; z.write().add_mul(*x, *y).normalize(); @@ -163,6 +205,12 @@ RefInt256& operator*=(RefInt256& x, long long y) { return x; } +RefInt256& operator*=(RefInt256& x, const BigInt256& y) { + RefInt256 z{true, 0}; + z.write().add_mul(*x, y).normalize(); + return x = z; +} + RefInt256& operator/=(RefInt256& x, RefInt256 y) { RefInt256 quot{true}; x.write().mod_div(*y, quot.write()); @@ -213,10 +261,16 @@ int sgn(RefInt256 x) { return x->sgn(); } -extern RefInt256 make_refint(long long x) { - auto xx = td::RefInt256{true, x}; - xx.unique_write().normalize(); - return xx; +RefInt256 zero_refint() { + // static RefInt256 Zero = td::RefInt256{true, 0}; + // return Zero; + return td::RefInt256{true, 0}; +} + +RefInt256 bits_to_refint(td::ConstBitPtr bits, int n, bool sgnd) { + td::RefInt256 x{true}; + x.unique_write().import_bits(bits, n, sgnd); + return x; } std::string dec_string(RefInt256 x) { @@ -227,8 +281,8 @@ std::string dec_string2(RefInt256&& x) { return x.is_null() ? "(null)" : (x.is_unique() ? x.unique_write().to_dec_string_destroy() : x->to_dec_string()); } -std::string hex_string(RefInt256 x, bool upcase) { - return x.is_null() ? "(null)" : x->to_hex_string(upcase); +std::string hex_string(RefInt256 x, bool upcase, int zero_pad) { + return x.is_null() ? "(null)" : x->to_hex_string(upcase, zero_pad); } std::string binary_string(RefInt256 x) { diff --git a/crypto/common/refint.h b/crypto/common/refint.h index a828caf1..e3305c2f 100644 --- a/crypto/common/refint.h +++ b/crypto/common/refint.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -33,15 +33,20 @@ typedef Ref RefInt256; extern RefInt256 operator+(RefInt256 x, RefInt256 y); extern RefInt256 operator+(RefInt256 x, long long y); +extern RefInt256 operator+(RefInt256 x, const BigInt256& y); extern RefInt256 operator-(RefInt256 x, RefInt256 y); extern RefInt256 operator-(RefInt256 x, long long y); +extern RefInt256 operator-(RefInt256 x, const BigInt256& y); extern RefInt256 operator*(RefInt256 x, RefInt256 y); extern RefInt256 operator*(RefInt256 x, long long y); +extern RefInt256 operator*(RefInt256 x, const BigInt256& y); extern RefInt256 operator/(RefInt256 x, RefInt256 y); extern RefInt256 operator%(RefInt256 x, RefInt256 y); extern RefInt256 div(RefInt256 x, RefInt256 y, int round_mode = -1); extern RefInt256 mod(RefInt256 x, RefInt256 y, int round_mode = -1); extern std::pair divmod(RefInt256 x, RefInt256 y, int round_mode = -1); +extern RefInt256 muldiv(RefInt256 x, RefInt256 y, RefInt256 z, int round_mode = -1); +extern std::pair muldivmod(RefInt256 x, RefInt256 y, RefInt256 z, int round_mode = -1); extern RefInt256 operator-(RefInt256 x); extern RefInt256 operator&(RefInt256 x, RefInt256 y); extern RefInt256 operator|(RefInt256 x, RefInt256 y); @@ -53,10 +58,13 @@ extern RefInt256 rshift(RefInt256 x, int y, int round_mode = -1); extern RefInt256& operator+=(RefInt256& x, RefInt256 y); extern RefInt256& operator+=(RefInt256& x, long long y); +extern RefInt256& operator+=(RefInt256& x, const BigInt256& y); extern RefInt256& operator-=(RefInt256& x, RefInt256 y); extern RefInt256& operator-=(RefInt256& x, long long y); +extern RefInt256& operator-=(RefInt256& x, const BigInt256& y); extern RefInt256& operator*=(RefInt256& x, RefInt256 y); extern RefInt256& operator*=(RefInt256& x, long long y); +extern RefInt256& operator*=(RefInt256& x, const BigInt256& y); extern RefInt256& operator/=(RefInt256& x, RefInt256 y); extern RefInt256& operator%=(RefInt256& x, RefInt256 y); @@ -100,11 +108,17 @@ extern int cmp(RefInt256 x, RefInt256 y); extern int cmp(RefInt256 x, long long y); extern int sgn(RefInt256 x); -extern RefInt256 make_refint(long long x); +template +RefInt256 make_refint(Args&&... args) { + return td::RefInt256{true, std::forward(args)...}; +} + +extern RefInt256 zero_refint(); +extern RefInt256 bits_to_refint(td::ConstBitPtr bits, int n, bool sgnd = false); extern std::string dec_string(RefInt256 x); extern std::string dec_string2(RefInt256&& x); -extern std::string hex_string(RefInt256 x, bool upcase = false); +extern std::string hex_string(RefInt256 x, bool upcase = false, int zero_pad = 0); extern std::string binary_string(RefInt256 x); extern RefInt256 dec_string_to_int256(const std::string& s); diff --git a/crypto/common/util.cpp b/crypto/common/util.cpp index 72118152..d1b24a76 100644 --- a/crypto/common/util.cpp +++ b/crypto/common/util.cpp @@ -14,12 +14,15 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "util.h" #include +#include "td/utils/crypto.h" +#include "td/utils/base64.h" + namespace td { std::size_t compute_base64_encoded_size(size_t bindata_size) { @@ -73,10 +76,6 @@ std::size_t buff_base64_encode(td::MutableSlice buffer, td::Slice raw, bool base return res_size; } -std::string str_base64_encode(std::string raw, bool base64_url) { - return str_base64_encode(td::Slice{raw}, base64_url); -} - std::string str_base64_encode(td::Slice raw, bool base64_url) { std::size_t res_size = compute_base64_encoded_size(raw.size()); std::string s; @@ -87,10 +86,6 @@ std::string str_base64_encode(td::Slice raw, bool base64_url) { return s; } -bool is_valid_base64(std::string encoded, bool allow_base64_url) { - return is_valid_base64(td::Slice{encoded}, allow_base64_url); -} - bool is_valid_base64(td::Slice encoded, bool allow_base64_url) { const unsigned char *ptr = (const unsigned char *)encoded.data(), *end = ptr + encoded.size(); if (encoded.size() & 3) { @@ -110,10 +105,6 @@ bool is_valid_base64(td::Slice encoded, bool allow_base64_url) { return ptr == end; } -td::int32 decoded_base64_size(std::string encoded, bool allow_base64_url) { - return decoded_base64_size(td::Slice{encoded}, allow_base64_url); -} - td::int32 decoded_base64_size(td::Slice encoded, bool allow_base64_url) { const unsigned char *ptr = (const unsigned char *)encoded.data(), *end = ptr + encoded.size(); if (encoded.size() & 3) { @@ -172,10 +163,6 @@ std::size_t buff_base64_decode(td::MutableSlice buffer, td::Slice encoded, bool return wptr - (unsigned char *)buffer.data(); } -td::BufferSlice base64_decode(std::string encoded, bool allow_base64_url) { - return base64_decode(td::Slice{encoded}, allow_base64_url); -} - td::BufferSlice base64_decode(td::Slice encoded, bool allow_base64_url) { auto s = decoded_base64_size(encoded, allow_base64_url); if (s <= 0) { @@ -190,10 +177,6 @@ td::BufferSlice base64_decode(td::Slice encoded, bool allow_base64_url) { return res; } -std::string str_base64_decode(std::string encoded, bool allow_base64_url) { - return str_base64_decode(td::Slice{encoded}, allow_base64_url); -} - std::string str_base64_decode(td::Slice encoded, bool allow_base64_url) { auto s = decoded_base64_size(encoded, allow_base64_url); if (s <= 0) { @@ -209,4 +192,45 @@ std::string str_base64_decode(td::Slice encoded, bool allow_base64_url) { return res; } +td::Result adnl_id_encode(td::Slice id, bool upper_case) { + if (id.size() != 32) { + return td::Status::Error("Wrong andl id size"); + } + td::uint8 buf[35]; + td::MutableSlice buf_slice(buf, 35); + buf_slice[0] = 0x2d; + buf_slice.substr(1).copy_from(id); + auto hash = td::crc16(buf_slice.substr(0, 33)); + buf[33] = static_cast((hash >> 8) & 255); + buf[34] = static_cast(hash & 255); + return td::base32_encode(buf_slice, upper_case).substr(1); +} + +std::string adnl_id_encode(td::Bits256 adnl_addr, bool upper_case) { + return adnl_id_encode(adnl_addr.as_slice(), upper_case).move_as_ok(); +} + +td::Result adnl_id_decode(td::Slice id) { + if (id.size() != 55) { + return td::Status::Error("Wrong length of adnl id"); + } + td::uint8 buf[56]; + buf[0] = 'f'; + td::MutableSlice buf_slice(buf, 56); + buf_slice.substr(1).copy_from(id); + TRY_RESULT(decoded_str, td::base32_decode(buf_slice)); + auto decoded = td::Slice(decoded_str); + if (decoded[0] != 0x2d) { + return td::Status::Error("Invalid first byte"); + } + auto got_hash = (decoded.ubegin()[33] << 8) | decoded.ubegin()[34]; + auto hash = td::crc16(decoded.substr(0, 33)); + if (hash != got_hash) { + return td::Status::Error("Hash mismatch"); + } + Bits256 res; + res.as_slice().copy_from(decoded.substr(1, 32)); + return res; +} + } // namespace td diff --git a/crypto/common/util.h b/crypto/common/util.h index 2a594efc..c56677b6 100644 --- a/crypto/common/util.h +++ b/crypto/common/util.h @@ -14,29 +14,32 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include #include "td/utils/Slice.h" +#include "td/utils/Status.h" #include "td/utils/buffer.h" +#include "bitstring.h" + namespace td { std::size_t compute_base64_encoded_size(size_t bindata_size); std::size_t buff_base64_encode(td::MutableSlice buffer, td::Slice raw, bool base64_url = false); -std::string str_base64_encode(std::string raw, bool base64_url = false); std::string str_base64_encode(td::Slice raw, bool base64_url = false); -bool is_valid_base64(std::string encoded, bool allow_base64_url = true); bool is_valid_base64(td::Slice encoded, bool allow_base64_url = true); -td::int32 decoded_base64_size(std::string encoded, bool allow_base64_url = true); td::int32 decoded_base64_size(td::Slice encoded, bool allow_base64_url = true); std::size_t buff_base64_decode(td::MutableSlice buffer, td::Slice data, bool allow_base64_url = true); -td::BufferSlice base64_decode(std::string encoded, bool allow_base64_url = true); td::BufferSlice base64_decode(td::Slice encoded, bool allow_base64_url = true); -std::string str_base64_decode(std::string encoded, bool allow_base64_url = true); std::string str_base64_decode(td::Slice encoded, bool allow_base64_url = true); +//TODO: move it somewhere else +td::Result adnl_id_encode(td::Slice id, bool upper_case = false); +std::string adnl_id_encode(Bits256 adnl_addr, bool upper_case = false); +td::Result adnl_id_decode(td::Slice id); + } // namespace td diff --git a/crypto/ellcurve/Ed25519.cpp b/crypto/ellcurve/Ed25519.cpp index dca882cf..a38eb079 100644 --- a/crypto/ellcurve/Ed25519.cpp +++ b/crypto/ellcurve/Ed25519.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "Ed25519.h" diff --git a/crypto/ellcurve/Ed25519.h b/crypto/ellcurve/Ed25519.h index cb899cc1..4b9e6348 100644 --- a/crypto/ellcurve/Ed25519.h +++ b/crypto/ellcurve/Ed25519.h @@ -14,12 +14,12 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "ellcurve/Montgomery.h" #include "ellcurve/TwEdwards.h" -#include "openssl/digest.h" +#include "openssl/digest.hpp" #include "openssl/rand.hpp" #include #include diff --git a/crypto/ellcurve/Fp25519.cpp b/crypto/ellcurve/Fp25519.cpp index 393da408..7b248f64 100644 --- a/crypto/ellcurve/Fp25519.cpp +++ b/crypto/ellcurve/Fp25519.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "ellcurve/Fp25519.h" diff --git a/crypto/ellcurve/Fp25519.h b/crypto/ellcurve/Fp25519.h index 3f17a540..b6b63be5 100644 --- a/crypto/ellcurve/Fp25519.h +++ b/crypto/ellcurve/Fp25519.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/refcnt.hpp" diff --git a/crypto/ellcurve/Montgomery.cpp b/crypto/ellcurve/Montgomery.cpp index c523f654..ed71910d 100644 --- a/crypto/ellcurve/Montgomery.cpp +++ b/crypto/ellcurve/Montgomery.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "ellcurve/Montgomery.h" diff --git a/crypto/ellcurve/Montgomery.h b/crypto/ellcurve/Montgomery.h index d40e8f8d..94d852c0 100644 --- a/crypto/ellcurve/Montgomery.h +++ b/crypto/ellcurve/Montgomery.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include diff --git a/crypto/ellcurve/TwEdwards.cpp b/crypto/ellcurve/TwEdwards.cpp index 6bcb42c2..eb131f70 100644 --- a/crypto/ellcurve/TwEdwards.cpp +++ b/crypto/ellcurve/TwEdwards.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "ellcurve/TwEdwards.h" #include diff --git a/crypto/ellcurve/TwEdwards.h b/crypto/ellcurve/TwEdwards.h index b226c291..c720ecca 100644 --- a/crypto/ellcurve/TwEdwards.h +++ b/crypto/ellcurve/TwEdwards.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include diff --git a/crypto/ellcurve/p256.cpp b/crypto/ellcurve/p256.cpp new file mode 100644 index 00000000..de539372 --- /dev/null +++ b/crypto/ellcurve/p256.cpp @@ -0,0 +1,91 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ + +#include "p256.h" +#include "td/utils/check.h" +#include "td/utils/misc.h" +#include +#include +#include + +namespace td { + +td::Status p256_check_signature(td::Slice data, td::Slice public_key, td::Slice signature) { + CHECK(public_key.size() == 33); + CHECK(signature.size() == 64); + + EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr); + if (pctx == nullptr) { + return td::Status::Error("Can't create EVP_PKEY_CTX"); + } + SCOPE_EXIT { + EVP_PKEY_CTX_free(pctx); + }; + if (EVP_PKEY_paramgen_init(pctx) <= 0) { + return td::Status::Error("EVP_PKEY_paramgen_init failed"); + } + if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1) <= 0) { + return td::Status::Error("EVP_PKEY_CTX_set_ec_paramgen_curve_nid failed"); + } + EVP_PKEY* pkey = nullptr; + if (EVP_PKEY_paramgen(pctx, &pkey) <= 0) { + return td::Status::Error("EVP_PKEY_paramgen failed"); + } + SCOPE_EXIT { + EVP_PKEY_free(pkey); + }; + if (EVP_PKEY_set1_tls_encodedpoint(pkey, public_key.ubegin(), public_key.size()) <= 0) { + return td::Status::Error("Failed to import public key"); + } + EVP_MD_CTX* md_ctx = EVP_MD_CTX_new(); + if (md_ctx == nullptr) { + return td::Status::Error("Can't create EVP_MD_CTX"); + } + SCOPE_EXIT { + EVP_MD_CTX_free(md_ctx); + }; + if (EVP_DigestVerifyInit(md_ctx, nullptr, nullptr, nullptr, pkey) <= 0) { + return td::Status::Error("Can't init DigestVerify"); + } + ECDSA_SIG* sig = ECDSA_SIG_new(); + SCOPE_EXIT { + ECDSA_SIG_free(sig); + }; + unsigned char buf[33]; + buf[0] = 0; + std::copy(signature.ubegin(), signature.ubegin() + 32, buf + 1); + BIGNUM* r = BN_bin2bn(buf, 33, nullptr); + std::copy(signature.ubegin() + 32, signature.ubegin() + 64, buf + 1); + BIGNUM* s = BN_bin2bn(buf, 33, nullptr); + if (ECDSA_SIG_set0(sig, r, s) != 1) { + return td::Status::Error("Invalid signature"); + } + unsigned char* signature_encoded = nullptr; + int signature_len = i2d_ECDSA_SIG(sig, &signature_encoded); + if (signature_len <= 0) { + return td::Status::Error("Invalid signature"); + } + SCOPE_EXIT { + OPENSSL_free(signature_encoded); + }; + if (EVP_DigestVerify(md_ctx, signature_encoded, signature_len, data.ubegin(), data.size()) == 1) { + return td::Status::OK(); + } + return td::Status::Error("Wrong signature"); +} + +} // namespace td diff --git a/crypto/ellcurve/p256.h b/crypto/ellcurve/p256.h new file mode 100644 index 00000000..22d08be4 --- /dev/null +++ b/crypto/ellcurve/p256.h @@ -0,0 +1,26 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#pragma once + +#include "td/utils/Slice.h" +#include "td/utils/Status.h" + +namespace td { + +td::Status p256_check_signature(td::Slice data, td::Slice public_key, td::Slice signature); + +} diff --git a/crypto/ellcurve/secp256k1.cpp b/crypto/ellcurve/secp256k1.cpp new file mode 100644 index 00000000..e890117a --- /dev/null +++ b/crypto/ellcurve/secp256k1.cpp @@ -0,0 +1,42 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ + +#include "secp256k1.h" +#include "td/utils/check.h" +#include +#include + +namespace td { + +bool ecrecover(const unsigned char* hash, const unsigned char* signature, unsigned char* public_key) { + static secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + secp256k1_ecdsa_recoverable_signature ecdsa_signature; + if (signature[64] > 3 || + !secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &ecdsa_signature, signature, signature[64])) { + return false; + } + secp256k1_pubkey pubkey; + if (!secp256k1_ecdsa_recover(ctx, &pubkey, &ecdsa_signature, hash)) { + return false; + } + size_t len = 65; + secp256k1_ec_pubkey_serialize(ctx, public_key, &len, &pubkey, SECP256K1_EC_UNCOMPRESSED); + CHECK(len == 65); + return true; +} + +} diff --git a/crypto/ellcurve/secp256k1.h b/crypto/ellcurve/secp256k1.h new file mode 100644 index 00000000..80ab6a87 --- /dev/null +++ b/crypto/ellcurve/secp256k1.h @@ -0,0 +1,23 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#pragma once + +namespace td { + +bool ecrecover(const unsigned char* hash, const unsigned char* signature, unsigned char* public_key); + +} diff --git a/crypto/fift/Continuation.cpp b/crypto/fift/Continuation.cpp new file mode 100644 index 00000000..e895082f --- /dev/null +++ b/crypto/fift/Continuation.cpp @@ -0,0 +1,535 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2020 Telegram Systems LLP +*/ +#include "Continuation.h" +#include "IntCtx.h" +#include "Dictionary.h" + +namespace fift { + +// +// FiftCont +// +bool FiftCont::print_dict_name(std::ostream& os, const IntCtx& ctx) const { + std::string word_name; + if (ctx.dictionary.lookup_def(this, &word_name)) { + if (word_name.size() && word_name.back() == ' ') { + word_name.pop_back(); + } + os << word_name; + return true; + } + return false; +} + +std::string FiftCont::get_dict_name(const IntCtx& ctx) const { + std::string word_name; + if (ctx.dictionary.lookup_def(this, &word_name)) { + if (word_name.size() && word_name.back() == ' ') { + word_name.pop_back(); + } + return word_name; + } + return {}; +} + +bool FiftCont::print_dummy_name(std::ostream& os, const IntCtx& ctx) const { + os << ""; + return false; +} + +bool FiftCont::print_name(std::ostream& os, const IntCtx& ctx) const { + return print_dict_name(os, ctx) || print_dummy_name(os, ctx); +} + +bool FiftCont::dump(std::ostream& os, const IntCtx& ctx) const { + bool ok = print_name(os, ctx); + os << std::endl; + return ok; +} + +// +// StackWord +// +Ref StackWord::run_tail(IntCtx& ctx) const { + f(ctx.stack); + return {}; +} + +// +// CtxWord +// +Ref CtxWord::run_tail(IntCtx& ctx) const { + f(ctx); + return {}; +} + +// +// CtxTailWord +// +Ref CtxTailWord::run_tail(IntCtx& ctx) const { + return f(ctx); +} + +// +// WordList +// +WordList::WordList(std::vector>&& _list) : list(std::move(_list)) { +} + +WordList::WordList(const std::vector>& _list) : list(_list) { +} + +WordList& WordList::push_back(Ref word_def) { + list.push_back(std::move(word_def)); + return *this; +} + +WordList& WordList::push_back(FiftCont& wd) { + list.emplace_back(&wd); + return *this; +} + +Ref WordList::run_tail(IntCtx& ctx) const { + if (list.empty()) { + return {}; + } + if (list.size() > 1) { + ctx.next = td::make_ref(std::move(ctx.next), Ref(this), 1); + } + return list[0]; +} + +void WordList::close() { + list.shrink_to_fit(); +} + +WordList& WordList::append(const std::vector>& other) { + list.insert(list.end(), other.begin(), other.end()); + return *this; +} + +WordList& WordList::append(const Ref* begin, const Ref* end) { + list.insert(list.end(), begin, end); + return *this; +} + +bool WordList::dump(std::ostream& os, const IntCtx& ctx) const { + os << "{"; + for (auto entry : list) { + os << ' '; + entry->print_name(os, ctx); + } + os << " }" << std::endl; + return true; +} + +// +// ListCont +// + +Ref ListCont::run_tail(IntCtx& ctx) const { + auto sz = list->size(); + if (pos >= sz) { + return std::move(ctx.next); + } else if (ctx.next.not_null()) { + ctx.next = td::make_ref(SeqCont::seq(next, std::move(ctx.next)), list, pos + 1); + } else if (pos + 1 == sz) { + ctx.next = next; + } else { + ctx.next = td::make_ref(next, list, pos + 1); + } + return list->at(pos); +} + +Ref ListCont::run_modify(IntCtx& ctx) { + auto sz = list->size(); + if (pos >= sz) { + return std::move(ctx.next); + } + auto cur = list->at(pos++); + if (ctx.next.not_null()) { + next = SeqCont::seq(next, std::move(ctx.next)); + } + if (pos == sz) { + ctx.next = std::move(next); + } else { + ctx.next = self(); + } + return cur; +} + +bool ListCont::dump(std::ostream& os, const IntCtx& ctx) const { + std::string dict_name = list->get_dict_name(ctx); + if (!dict_name.empty()) { + os << "[in " << dict_name << ":] "; + } + std::size_t sz = list->size(), i, a = (pos >= 16 ? pos - 16 : 0), b = std::min(pos + 16, sz); + if (a > 0) { + os << "... "; + } + for (i = a; i < b; i++) { + if (i == pos) { + os << "**HERE** "; + } + list->at(i)->print_name(os, ctx); + os << ' '; + } + if (b < sz) { + os << "..."; + } + os << std::endl; + return true; +} + +// +// QuitCont +// +Ref QuitCont::run_tail(IntCtx& ctx) const { + ctx.set_exit_code(exit_code); + ctx.next.clear(); + return {}; +} + +bool QuitCont::print_name(std::ostream& os, const IntCtx& ctx) const { + os << ""; + return true; +} + +// +// SeqCont +// +Ref SeqCont::run_tail(IntCtx& ctx) const { + ctx.next = seq(second, std::move(ctx.next)); + return first; +} + +Ref SeqCont::run_modify(IntCtx& ctx) { + if (ctx.next.is_null()) { + ctx.next = std::move(second); + return std::move(first); + } else { + auto res = std::move(first); + first = std::move(second); + second = std::move(ctx.next); + ctx.next = self(); + return res; + } +} + +bool SeqCont::print_name(std::ostream& os, const IntCtx& ctx) const { + if (first.not_null()) { + return first->print_name(os, ctx); + } else { + return true; + } +} + +bool SeqCont::dump(std::ostream& os, const IntCtx& ctx) const { + if (first.not_null()) { + return first->dump(os, ctx); + } else { + return true; + } +} + +// +// TimesCont +// +Ref TimesCont::run_tail(IntCtx& ctx) const { + if (count > 1) { + ctx.next = td::make_ref(body, SeqCont::seq(after, std::move(ctx.next)), count - 1); + } else { + ctx.next = SeqCont::seq(after, std::move(ctx.next)); + } + return body; +} + +Ref TimesCont::run_modify(IntCtx& ctx) { + if (ctx.next.not_null()) { + after = SeqCont::seq(std::move(after), std::move(ctx.next)); + } + if (count > 1) { + --count; + ctx.next = self(); + return body; + } else { + ctx.next = std::move(after); + return std::move(body); + } +} + +bool TimesCont::print_name(std::ostream& os, const IntCtx& ctx) const { + os << ""; + return true; +} + +bool TimesCont::dump(std::ostream& os, const IntCtx& ctx) const { + os << " "; + return body->dump(os, ctx); +} + +// +// UntilCont +// +Ref UntilCont::run_tail(IntCtx& ctx) const { + if (ctx.stack.pop_bool()) { + return after; + } else if (ctx.next.not_null()) { + ctx.next = td::make_ref(body, SeqCont::seq(after, std::move(ctx.next))); + return body; + } else { + ctx.next = self(); + return body; + } +} + +Ref UntilCont::run_modify(IntCtx& ctx) { + if (ctx.stack.pop_bool()) { + return std::move(after); + } else { + if (ctx.next.not_null()) { + after = SeqCont::seq(after, std::move(ctx.next)); + } + ctx.next = self(); + return body; + } +} + +bool UntilCont::print_name(std::ostream& os, const IntCtx& ctx) const { + os << ""; + return true; +} + +bool UntilCont::dump(std::ostream& os, const IntCtx& ctx) const { + os << " "; + return body->dump(os, ctx); +} + +// +// WhileCont +// +Ref WhileCont::run_tail(IntCtx& ctx) const { + if (!stage) { + ctx.next = td::make_ref(cond, body, SeqCont::seq(after, std::move(ctx.next)), true); + return cond; + } + if (!ctx.stack.pop_bool()) { + return after; + } else { + ctx.next = td::make_ref(cond, body, SeqCont::seq(after, std::move(ctx.next))); + return body; + } +} + +Ref WhileCont::run_modify(IntCtx& ctx) { + if (!stage) { + if (ctx.next.not_null()) { + after = SeqCont::seq(std::move(after), std::move(ctx.next)); + } + stage = true; + ctx.next = self(); + return cond; + } + if (!ctx.stack.pop_bool()) { + return std::move(after); + } else { + if (ctx.next.not_null()) { + after = SeqCont::seq(std::move(after), std::move(ctx.next)); + } + stage = false; + ctx.next = self(); + return body; + } +} + +bool WhileCont::print_name(std::ostream& os, const IntCtx& ctx) const { + os << ""; + return true; +} + +bool WhileCont::dump(std::ostream& os, const IntCtx& ctx) const { + os << " "; + return (stage ? body : cond)->dump(os, ctx); +} + +// +// LoopCont +// +Ref LoopCont::run_tail(IntCtx& ctx) const { + return Ref(clone()); +} + +Ref LoopCont::run_modify(IntCtx& ctx) { + if (ctx.next.not_null()) { + after = SeqCont::seq(std::move(after), std::move(ctx.next)); + } + switch (state) { + case 0: + if (!init(ctx)) { + return std::move(after); + } + state = 1; + // fallthrough + case 1: + if (!pre_exec(ctx)) { + state = 3; + if (finalize(ctx)) { + return std::move(after); + } else { + return {}; + } + } + state = 2; + ctx.next = self(); + return func; + case 2: + if (post_exec(ctx)) { + state = 1; + return self(); + } + state = 3; + // fallthrough + case 3: + if (finalize(ctx)) { + return std::move(after); + } else { + return {}; + } + default: + throw IntError{"invalid LoopCont state"}; + } +} + +bool LoopCont::print_name(std::ostream& os, const IntCtx& ctx) const { + os << ""; + return true; +} + +// +// GenericLitCont +// +bool GenericLitCont::print_name(std::ostream& os, const IntCtx& ctx) const { + auto list = get_literals(); + bool sp = false; + for (auto entry : list) { + if (sp) { + os << ' '; + } + sp = true; + int tp = entry.type(); + if (entry.is_int() || entry.is(vm::StackEntry::t_string) || entry.is(vm::StackEntry::t_bytes)) { + entry.dump(os); + } else if (entry.is_atom()) { + os << '`'; + entry.dump(os); + } else { + auto cont_lit = entry.as_object(); + if (cont_lit.not_null()) { + os << "{ "; + cont_lit->print_name(os, ctx); + os << " }"; + } else { + os << ""; + } + } + } + return true; +} + +// +// SmallIntLitCont +// +Ref SmallIntLitCont::run_tail(IntCtx& ctx) const { + ctx.stack.push_smallint(value_); + return {}; +} + +std::vector SmallIntLitCont::get_literals() const { + return {td::make_refint(value_)}; +} + +// +// IntLitCont +// +Ref IntLitCont::run_tail(IntCtx& ctx) const { + ctx.stack.push_int(value_); + return {}; +} + +Ref IntLitCont::run_modify(IntCtx& ctx) { + ctx.stack.push_int(std::move(value_)); + return {}; +} + +Ref IntLitCont::literal(td::RefInt256 int_value) { + if (int_value->signed_fits_bits(64)) { + return literal(int_value->to_long()); + } else { + return td::make_ref(std::move(int_value)); + } +} + +// +// LitCont +// +Ref LitCont::run_tail(IntCtx& ctx) const { + ctx.stack.push(value_); + return {}; +} + +Ref LitCont::run_modify(IntCtx& ctx) { + ctx.stack.push(std::move(value_)); + return {}; +} + +Ref LitCont::literal(vm::StackEntry value) { + if (value.is_int()) { + return literal(std::move(value).as_int()); + } else { + return td::make_ref(std::move(value)); + } +} + +// +// MultiLitCont +// +Ref MultiLitCont::run_tail(IntCtx& ctx) const { + for (auto& value : values_) { + ctx.stack.push(value); + } + return {}; +} + +Ref MultiLitCont::run_modify(IntCtx& ctx) { + for (auto& value : values_) { + ctx.stack.push(std::move(value)); + } + return {}; +} + +MultiLitCont& MultiLitCont::push_back(vm::StackEntry new_literal) { + values_.push_back(std::move(new_literal)); + return *this; +} + +vm::StackEntry MultiLitCont::at(int idx) const { + return values_.at(idx); +} + +} // namespace fift diff --git a/crypto/fift/Continuation.h b/crypto/fift/Continuation.h new file mode 100644 index 00000000..6623b642 --- /dev/null +++ b/crypto/fift/Continuation.h @@ -0,0 +1,368 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2020 Telegram Systems LLP +*/ +#pragma once +#include +#include "common/refcnt.hpp" +#include "common/refint.h" +#include "vm/stack.hpp" + +namespace fift { +using td::Ref; +struct IntCtx; + +/* + * + * FIFT CONTINUATIONS + * + */ + +class FiftCont : public td::CntObject { + public: + FiftCont() = default; + virtual ~FiftCont() override = default; + virtual Ref run_tail(IntCtx& ctx) const = 0; + virtual Ref run_modify(IntCtx& ctx) { + return run_tail(ctx); + } + virtual Ref handle_tail(IntCtx& ctx) const { + return {}; + } + virtual Ref handle_modify(IntCtx& ctx) { + return handle_tail(ctx); + } + virtual Ref up() const { + return {}; + } + virtual bool is_list() const { + return false; + } + virtual long long list_size() const { + return -1; + } + virtual const Ref* get_list() const { + return nullptr; + } + virtual bool is_literal() const { + return false; + } + virtual int literal_count() const { + return -1; + } + virtual std::vector get_literals() const { + return {}; + } + std::string get_dict_name(const IntCtx& ctx) const; + bool print_dict_name(std::ostream& os, const IntCtx& ctx) const; + bool print_dummy_name(std::ostream& os, const IntCtx& ctx) const; + virtual bool print_name(std::ostream& os, const IntCtx& ctx) const; + virtual bool dump(std::ostream& os, const IntCtx& ctx) const; + Ref self() const { + return Ref{this}; + } +}; + +typedef std::function StackWordFunc; +typedef std::function CtxWordFunc; +typedef std::function(IntCtx&)> CtxTailWordFunc; + +class NopWord : public FiftCont { + public: + NopWord() = default; + ~NopWord() override = default; + Ref run_tail(IntCtx& ctx) const override { + return {}; + } +}; + +class StackWord : public FiftCont { + StackWordFunc f; + + public: + StackWord(StackWordFunc _f) : f(std::move(_f)) { + } + ~StackWord() override = default; + Ref run_tail(IntCtx& ctx) const override; +}; + +class CtxWord : public FiftCont { + CtxWordFunc f; + + public: + CtxWord(CtxWordFunc _f) : f(std::move(_f)) { + } + ~CtxWord() override = default; + Ref run_tail(IntCtx& ctx) const override; +}; + +class CtxTailWord : public FiftCont { + CtxTailWordFunc f; + + public: + CtxTailWord(CtxTailWordFunc _f) : f(std::move(_f)) { + } + ~CtxTailWord() override = default; + Ref run_tail(IntCtx& ctx) const override; +}; + +class WordList : public FiftCont { + std::vector> list; + + public: + ~WordList() override = default; + WordList() = default; + WordList(std::vector>&& _list); + WordList(const std::vector>& _list); + WordList& push_back(Ref word_def); + WordList& push_back(FiftCont& wd); + Ref run_tail(IntCtx& ctx) const override; + void close(); + bool is_list() const override { + return true; + } + long long list_size() const override { + return (long long)list.size(); + } + std::size_t size() const { + return list.size(); + } + const Ref& at(std::size_t idx) const { + return list.at(idx); + } + const Ref* get_list() const override { + return list.data(); + } + WordList& append(const std::vector>& other); + WordList& append(const Ref* begin, const Ref* end); + WordList* make_copy() const override { + return new WordList(list); + } + bool dump(std::ostream& os, const IntCtx& ctx) const override; +}; + +class ListCont : public FiftCont { + Ref next; + Ref list; + std::size_t pos; + + public: + ListCont(Ref nxt, Ref wl, std::size_t p = 0) : next(std::move(nxt)), list(std::move(wl)), pos(p) { + } + ~ListCont() override = default; + Ref run_tail(IntCtx& ctx) const override; + Ref run_modify(IntCtx& ctx) override; + Ref up() const override { + return next; + } + bool dump(std::ostream& os, const IntCtx& ctx) const override; +}; + +class QuitCont : public FiftCont { + int exit_code; + + public: + QuitCont(int _exit_code = -1) : exit_code(_exit_code) { + } + Ref run_tail(IntCtx& ctx) const override; + bool print_name(std::ostream& os, const IntCtx& ctx) const override; +}; + +class SeqCont : public FiftCont { + Ref first, second; + + public: + SeqCont(Ref _first, Ref _second) : first(std::move(_first)), second(std::move(_second)) { + } + static Ref seq(Ref _first, Ref _second) { + return _second.is_null() ? std::move(_first) : td::make_ref(std::move(_first), std::move(_second)); + } + Ref run_tail(IntCtx& ctx) const override; + Ref run_modify(IntCtx& ctx) override; + Ref up() const override { + return second; + } + bool print_name(std::ostream& os, const IntCtx& ctx) const override; + bool dump(std::ostream& os, const IntCtx& ctx) const override; +}; + +class TimesCont : public FiftCont { + Ref body, after; + int count; + + public: + TimesCont(Ref _body, Ref _after, int _count) + : body(std::move(_body)), after(std::move(_after)), count(_count) { + } + Ref run_tail(IntCtx& ctx) const override; + Ref run_modify(IntCtx& ctx) override; + Ref up() const override { + return after; + } + bool print_name(std::ostream& os, const IntCtx& ctx) const override; + bool dump(std::ostream& os, const IntCtx& ctx) const override; +}; + +class UntilCont : public FiftCont { + Ref body, after; + + public: + UntilCont(Ref _body, Ref _after) : body(std::move(_body)), after(std::move(_after)) { + } + Ref run_tail(IntCtx& ctx) const override; + Ref run_modify(IntCtx& ctx) override; + bool print_name(std::ostream& os, const IntCtx& ctx) const override; + bool dump(std::ostream& os, const IntCtx& ctx) const override; +}; + +class WhileCont : public FiftCont { + Ref cond, body, after; + bool stage; + + public: + WhileCont(Ref _cond, Ref _body, Ref _after, bool _stage = false) + : cond(std::move(_cond)), body(std::move(_body)), after(std::move(_after)), stage(_stage) { + } + Ref run_tail(IntCtx& ctx) const override; + Ref run_modify(IntCtx& ctx) override; + Ref up() const override { + return after; + } + bool print_name(std::ostream& os, const IntCtx& ctx) const override; + bool dump(std::ostream& os, const IntCtx& ctx) const override; +}; + +class LoopCont : public FiftCont { + Ref func, after; + int state; + + public: + LoopCont(Ref _func, Ref _after, int _state = 0) + : func(std::move(_func)), after(std::move(_after)), state(_state) { + } + LoopCont(const LoopCont&) = default; + virtual bool init(IntCtx& ctx) { + return true; + } + virtual bool pre_exec(IntCtx& ctx) { + return true; + } + virtual bool post_exec(IntCtx& ctx) { + return true; + } + virtual bool finalize(IntCtx& ctx) { + return true; + } + Ref run_tail(IntCtx& ctx) const override; + Ref run_modify(IntCtx& ctx) override; + Ref up() const override { + return after; + } + bool print_name(std::ostream& os, const IntCtx& ctx) const override; +}; + +class GenericLitCont : public FiftCont { + public: + bool is_literal() const override { + return true; + } + std::vector get_literals() const override = 0; + bool print_name(std::ostream& os, const IntCtx& ctx) const override; +}; + +class SmallIntLitCont : public GenericLitCont { + long long value_; + + public: + SmallIntLitCont(long long value) : value_(value) { + } + Ref run_tail(IntCtx& ctx) const override; + std::vector get_literals() const override; + static Ref literal(long long int_value) { + return td::make_ref(int_value); + } +}; + +class IntLitCont : public GenericLitCont { + td::RefInt256 value_; + + public: + IntLitCont(td::RefInt256 value) : value_(std::move(value)) { + } + Ref run_tail(IntCtx& ctx) const override; + Ref run_modify(IntCtx& ctx) override; + std::vector get_literals() const override { + return {vm::StackEntry(value_)}; + } + static Ref literal(td::RefInt256 int_value); + static Ref literal(long long int_value) { + return SmallIntLitCont::literal(int_value); + } +}; + +class LitCont : public GenericLitCont { + vm::StackEntry value_; + + public: + LitCont(const vm::StackEntry& value) : value_(value) { + } + LitCont(vm::StackEntry&& value) : value_(std::move(value)) { + } + Ref run_tail(IntCtx& ctx) const override; + Ref run_modify(IntCtx& ctx) override; + std::vector get_literals() const override { + return {value_}; + } + static Ref literal(vm::StackEntry value); + static Ref literal(td::RefInt256 int_value) { + return IntLitCont::literal(std::move(int_value)); + } + static Ref literal(long long int_value) { + return SmallIntLitCont::literal(int_value); + } +}; + +class MultiLitCont : public GenericLitCont { + std::vector values_; + + public: + MultiLitCont(const std::vector& values) : values_(values) { + } + MultiLitCont(std::vector&& values) : values_(std::move(values)) { + } + MultiLitCont(std::initializer_list value_list) : values_(value_list) { + } + Ref run_tail(IntCtx& ctx) const override; + Ref run_modify(IntCtx& ctx) override; + std::vector get_literals() const override { + return values_; + } + MultiLitCont& push_back(vm::StackEntry new_literal); + vm::StackEntry at(int idx) const; +}; + +class InterpretCont : public FiftCont { + public: + InterpretCont() = default; + Ref run_tail(IntCtx&) const override; // text interpreter, defined in words.cpp + bool print_name(std::ostream& os, const IntCtx& ctx) const override { + os << ""; + return true; + } +}; + +} // namespace fift diff --git a/crypto/fift/Dictionary.cpp b/crypto/fift/Dictionary.cpp index 3298132a..d2eae0a3 100644 --- a/crypto/fift/Dictionary.cpp +++ b/crypto/fift/Dictionary.cpp @@ -14,126 +14,69 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "Dictionary.h" +#include "IntCtx.h" namespace fift { // -// WordDef +// DictEntry // -void WordDef::run(IntCtx& ctx) const { - auto next = run_tail(ctx); - while (next.not_null()) { - next = next->run_tail(ctx); + +DictEntry::DictEntry(StackWordFunc func) : def(Ref{true, std::move(func)}), active(false) { +} + +DictEntry::DictEntry(CtxWordFunc func, bool _act) : def(Ref{true, std::move(func)}), active(_act) { +} + +DictEntry::DictEntry(CtxTailWordFunc func, bool _act) : def(Ref{true, std::move(func)}), active(_act) { +} + +DictEntry DictEntry::create_from(vm::StackEntry se) { + if (se.is_tuple()) { + auto& tuple = *se.as_tuple(); + if (tuple.size() == 1) { + auto def = tuple[0].as_object(); + if (def.not_null()) { + return DictEntry{std::move(def), true}; + } + } + } else { + auto def = std::move(se).as_object(); + if (def.not_null()) { + return DictEntry{std::move(def)}; + } } -} - -// -// StackWord -// -Ref StackWord::run_tail(IntCtx& ctx) const { - f(ctx.stack); return {}; } -// -// CtxWord -// -Ref CtxWord::run_tail(IntCtx& ctx) const { - f(ctx); - return {}; -} - -// -// CtxTailWord -// -Ref CtxTailWord::run_tail(IntCtx& ctx) const { - return f(ctx); -} - -// -// WordList -// -WordList::WordList(std::vector>&& _list) : list(std::move(_list)) { -} - -WordList::WordList(const std::vector>& _list) : list(_list) { -} - -WordList& WordList::push_back(Ref word_def) { - list.push_back(std::move(word_def)); - return *this; -} - -WordList& WordList::push_back(WordDef& wd) { - list.emplace_back(&wd); - return *this; -} - -Ref WordList::run_tail(IntCtx& ctx) const { - if (list.empty()) { +DictEntry::operator vm::StackEntry() const& { + if (def.is_null()) { return {}; + } else if (active) { + return vm::make_tuple_ref(vm::StackEntry{vm::from_object, def}); + } else { + return {vm::from_object, def}; } - auto it = list.cbegin(), it2 = list.cend() - 1; - while (it < it2) { - (*it)->run(ctx); - ++it; +} + +DictEntry::operator vm::StackEntry() && { + if (def.is_null()) { + return {}; + } else if (active) { + return vm::make_tuple_ref(vm::StackEntry{vm::from_object, std::move(def)}); + } else { + return {vm::from_object, std::move(def)}; } - return *it; -} - -void WordList::close() { - list.shrink_to_fit(); -} - -WordList& WordList::append(const std::vector>& other) { - list.insert(list.end(), other.begin(), other.end()); - return *this; -} - -// -// WordRef -// - -WordRef::WordRef(Ref _def, bool _act) : def(std::move(_def)), active(_act) { -} - -WordRef::WordRef(StackWordFunc func) : def(Ref{true, std::move(func)}), active(false) { -} - -WordRef::WordRef(CtxWordFunc func, bool _act) : def(Ref{true, std::move(func)}), active(_act) { -} - -WordRef::WordRef(CtxTailWordFunc func, bool _act) : def(Ref{true, std::move(func)}), active(_act) { -} - -Ref WordRef::get_def() const & { - return def; -} - -Ref WordRef::get_def() && { - return std::move(def); -} - -void WordRef::operator()(IntCtx& ctx) const { - def->run(ctx); -} - -bool WordRef::is_active() const { - return active; } // // Dictionary // -WordRef* Dictionary::lookup(td::Slice name) { - auto it = words_.find(name); - if (it == words_.end()) { - return nullptr; - } - return &it->second; +DictEntry Dictionary::lookup(std::string name) const { + return DictEntry::create_from(words().get(name)); } void Dictionary::def_ctx_word(std::string name, CtxWordFunc func) { @@ -141,7 +84,7 @@ void Dictionary::def_ctx_word(std::string name, CtxWordFunc func) { } void Dictionary::def_active_word(std::string name, CtxWordFunc func) { - Ref wdef = Ref{true, std::move(func)}; + Ref wdef = Ref{true, std::move(func)}; def_word(std::move(name), {std::move(wdef), true}); } @@ -153,48 +96,33 @@ void Dictionary::def_ctx_tail_word(std::string name, CtxTailWordFunc func) { def_word(std::move(name), std::move(func)); } -void Dictionary::def_word(std::string name, WordRef word) { - auto res = words_.emplace(name, std::move(word)); - LOG_IF(FATAL, !res.second) << "Cannot redefine word: " << name; +void Dictionary::def_word(std::string name, DictEntry word) { + auto dict = words(); + dict.set(std::move(name), vm::StackEntry(std::move(word))); + set_words(dict); } -void Dictionary::undef_word(td::Slice name) { - auto it = words_.find(name); - if (it == words_.end()) { - return; +void Dictionary::undef_word(std::string name) { + auto dict = words(); + if (dict.remove(name)) { + set_words(dict); } - words_.erase(it); } -void interpret_nop(vm::Stack& stack) { -} - -Ref Dictionary::nop_word_def = Ref{true, interpret_nop}; - -// -// functions for wordef -// -Ref pop_exec_token(vm::Stack& stack) { - stack.check_underflow(1); - auto wd_ref = stack.pop().as_object(); - if (wd_ref.is_null()) { - throw IntError{"execution token expected"}; +bool Dictionary::lookup_def(const FiftCont* cont, std::string* word_ptr) const { + if (!cont) { + return false; } - return wd_ref; -} - -Ref pop_word_list(vm::Stack& stack) { - stack.check_underflow(1); - auto wl_ref = stack.pop().as_object(); - if (wl_ref.is_null()) { - throw IntError{"word list expected"}; + for (auto entry : words()) { + auto val = DictEntry::create_from(entry.value()); + if (val.get_def().get() == cont && entry.key().is_string()) { + if (word_ptr) { + *word_ptr = vm::StackEntry(entry.key()).as_string(); + } + return true; + } } - return wl_ref; -} - -void push_argcount(vm::Stack& stack, int args) { - stack.push_smallint(args); - stack.push({vm::from_object, Dictionary::nop_word_def}); + return false; } } // namespace fift diff --git a/crypto/fift/Dictionary.h b/crypto/fift/Dictionary.h index 34cdb832..b24bc742 100644 --- a/crypto/fift/Dictionary.h +++ b/crypto/fift/Dictionary.h @@ -14,133 +14,64 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once - -#include -#include - -#include "IntCtx.h" +#include "Continuation.h" +#include "HashMap.h" +#include "vm/box.hpp" namespace fift { using td::Ref; + +struct IntCtx; + /* * * WORD CLASSES * */ -typedef std::function StackWordFunc; -typedef std::function CtxWordFunc; - -class WordDef : public td::CntObject { - public: - WordDef() = default; - virtual ~WordDef() override = default; - virtual Ref run_tail(IntCtx& ctx) const = 0; - void run(IntCtx& ctx) const; - virtual bool is_list() const { - return false; - } - virtual long long list_size() const { - return -1; - } - virtual const std::vector>* get_list() const { - return nullptr; - } -}; - -class StackWord : public WordDef { - StackWordFunc f; +class DictEntry { + Ref def; + bool active{false}; public: - StackWord(StackWordFunc _f) : f(std::move(_f)) { + DictEntry() = default; + DictEntry(const DictEntry& ref) = default; + DictEntry(DictEntry&& ref) = default; + DictEntry(Ref _def, bool _act = false) : def(std::move(_def)), active(_act) { } - ~StackWord() override = default; - Ref run_tail(IntCtx& ctx) const override; -}; - -class CtxWord : public WordDef { - CtxWordFunc f; - - public: - CtxWord(CtxWordFunc _f) : f(std::move(_f)) { + DictEntry(StackWordFunc func); + DictEntry(CtxWordFunc func, bool _act = false); + DictEntry(CtxTailWordFunc func, bool _act = false); + //DictEntry(const std::vector>& word_list); + //DictEntry(std::vector>&& word_list); + DictEntry& operator=(const DictEntry&) = default; + DictEntry& operator=(DictEntry&&) = default; + static DictEntry create_from(vm::StackEntry se); + explicit operator vm::StackEntry() const&; + explicit operator vm::StackEntry() &&; + Ref get_def() const& { + return def; } - ~CtxWord() override = default; - Ref run_tail(IntCtx& ctx) const override; -}; - -typedef std::function(IntCtx&)> CtxTailWordFunc; - -class CtxTailWord : public WordDef { - CtxTailWordFunc f; - - public: - CtxTailWord(CtxTailWordFunc _f) : f(std::move(_f)) { + Ref get_def() && { + return std::move(def); } - ~CtxTailWord() override = default; - Ref run_tail(IntCtx& ctx) const override; -}; - -class WordList : public WordDef { - std::vector> list; - - public: - ~WordList() override = default; - WordList() = default; - WordList(std::vector>&& _list); - WordList(const std::vector>& _list); - WordList& push_back(Ref word_def); - WordList& push_back(WordDef& wd); - Ref run_tail(IntCtx& ctx) const override; - void close(); - bool is_list() const override { - return true; + bool is_active() const { + return active; } - long long list_size() const override { - return (long long)list.size(); + bool empty() const { + return def.is_null(); } - const std::vector>* get_list() const override { - return &list; + explicit operator bool() const { + return def.not_null(); } - WordList& append(const std::vector>& other); - WordList* make_copy() const override { - return new WordList(list); + bool operator!() const { + return def.is_null(); } }; -class WordRef { - Ref def; - bool active; - - public: - WordRef() = delete; - WordRef(const WordRef& ref) = default; - WordRef(WordRef&& ref) = default; - WordRef(Ref _def, bool _act = false); - WordRef(StackWordFunc func); - WordRef(CtxWordFunc func, bool _act = false); - WordRef(CtxTailWordFunc func, bool _act = false); - //WordRef(const std::vector>& word_list); - //WordRef(std::vector>&& word_list); - WordRef& operator=(const WordRef&) = default; - WordRef& operator=(WordRef&&) = default; - Ref get_def() const &; - Ref get_def() &&; - void operator()(IntCtx& ctx) const; - bool is_active() const; - ~WordRef() = default; -}; - -/* -WordRef::WordRef(const std::vector>& word_list) : def(Ref{true, word_list}) { -} - -WordRef::WordRef(std::vector>&& word_list) : def(Ref{true, std::move(word_list)}) { -} -*/ - /* * * DICTIONARIES @@ -149,34 +80,52 @@ WordRef::WordRef(std::vector>&& word_list) : def(Ref{true class Dictionary { public: - WordRef* lookup(td::Slice name); + Dictionary() : box_(true) { + } + Dictionary(Ref box) : box_(std::move(box)) { + } + Dictionary(Ref hmap) : box_(true, vm::from_object, std::move(hmap)) { + } + + DictEntry lookup(std::string name) const; void def_ctx_word(std::string name, CtxWordFunc func); void def_ctx_tail_word(std::string name, CtxTailWordFunc func); void def_active_word(std::string name, CtxWordFunc func); void def_stack_word(std::string name, StackWordFunc func); - void def_word(std::string name, WordRef word); - void undef_word(td::Slice name); - + void def_word(std::string name, DictEntry word); + void undef_word(std::string name); + bool lookup_def(const FiftCont* cont, std::string* word_ptr = nullptr) const; + bool lookup_def(Ref cont, std::string* word_ptr = nullptr) const { + return lookup_def(cont.get(), word_ptr); + } auto begin() const { - return words_.begin(); + return words().begin(); } auto end() const { - return words_.end(); + return words().end(); + } + HashmapKeeper words() const { + if (box_->empty()) { + return {}; + } else { + return box_->get().as_object(); + } + } + Ref get_box() const { + return box_; + } + void set_words(Ref new_words) { + box_->set(vm::StackEntry{vm::from_object, std::move(new_words)}); + } + bool operator==(const Dictionary& other) const { + return box_ == other.box_; + } + bool operator!=(const Dictionary& other) const { + return box_ != other.box_; } - static Ref nop_word_def; - private: - std::map> words_; + Ref box_; }; -/* - * - * AUX FUNCTIONS FOR WORD DEFS - * - */ - -Ref pop_exec_token(vm::Stack& stack); -Ref pop_word_list(vm::Stack& stack); -void push_argcount(vm::Stack& stack, int args); } // namespace fift diff --git a/crypto/fift/Fift.cpp b/crypto/fift/Fift.cpp index 5a4439ef..85511a38 100644 --- a/crypto/fift/Fift.cpp +++ b/crypto/fift/Fift.cpp @@ -14,10 +14,10 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "Fift.h" - +#include "IntCtx.h" #include "words.h" #include "td/utils/PathView.h" @@ -37,40 +37,46 @@ td::Result Fift::interpret_file(std::string fname, std::string current_dir, return td::Status::Error("cannot locate file `" + fname + "`"); } auto file = r_file.move_as_ok(); - IntCtx ctx; std::stringstream ss(file.data); - ctx.input_stream = &ss; - ctx.filename = td::PathView(file.path).file_name().str(); - ctx.currentd_dir = td::PathView(file.path).parent_dir().str(); - ctx.include_depth = is_interactive ? 0 : 1; - return do_interpret(ctx); + IntCtx ctx{ss, td::PathView(file.path).file_name().str(), td::PathView(file.path).parent_dir().str(), + (int)!is_interactive}; + return do_interpret(ctx, is_interactive); } td::Result Fift::interpret_istream(std::istream& stream, std::string current_dir, bool is_interactive) { - IntCtx ctx; - ctx.input_stream = &stream; - ctx.filename = "stdin"; - ctx.currentd_dir = current_dir; - ctx.include_depth = is_interactive ? 0 : 1; - return do_interpret(ctx); + IntCtx ctx{stream, "stdin", current_dir, (int)!is_interactive}; + return do_interpret(ctx, is_interactive); } -td::Result Fift::do_interpret(IntCtx& ctx) { +td::Result Fift::do_interpret(IntCtx& ctx, bool is_interactive) { ctx.ton_db = &config_.ton_db; ctx.source_lookup = &config_.source_lookup; - ctx.dictionary = &config_.dictionary; + ctx.dictionary = ctx.main_dictionary = ctx.context = config_.dictionary; ctx.output_stream = config_.output_stream; ctx.error_stream = config_.error_stream; if (!ctx.output_stream) { return td::Status::Error("Cannot run interpreter without output_stream"); } - try { - return funny_interpret_loop(ctx); - } catch (fift::IntError ab) { - return td::Status::Error(ab.msg); - } catch (fift::Quit q) { - return q.res; + while (true) { + auto res = ctx.run(td::make_ref()); + if (res.is_error()) { + res = ctx.add_error_loc(res.move_as_error()); + if (config_.show_backtrace) { + std::ostringstream os; + ctx.print_error_backtrace(os); + LOG(ERROR) << os.str(); + } + if (is_interactive) { + LOG(ERROR) << res.move_as_error().message(); + ctx.top_ctx(); + ctx.clear_error(); + ctx.stack.clear(); + ctx.parser->load_next_line(); + continue; + } + } + return res; } - return 0; } + } // namespace fift diff --git a/crypto/fift/Fift.h b/crypto/fift/Fift.h index bd3c8023..ebcf2ef4 100644 --- a/crypto/fift/Fift.h +++ b/crypto/fift/Fift.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -26,7 +26,6 @@ namespace fift { struct IntCtx; -int funny_interpret_loop(IntCtx& ctx); struct Fift { public: @@ -36,6 +35,7 @@ struct Fift { fift::Dictionary dictionary; std::ostream* output_stream{&std::cout}; std::ostream* error_stream{&std::cerr}; + bool show_backtrace{true}; }; // Fift must own ton_db and dictionary, no concurrent access is allowed explicit Fift(Config config); @@ -48,6 +48,6 @@ struct Fift { private: Config config_; - td::Result do_interpret(IntCtx& ctx); + td::Result do_interpret(IntCtx& ctx, bool is_interactive = false); }; } // namespace fift diff --git a/crypto/fift/HashMap.cpp b/crypto/fift/HashMap.cpp new file mode 100644 index 00000000..a4ca0e9b --- /dev/null +++ b/crypto/fift/HashMap.cpp @@ -0,0 +1,371 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#include "HashMap.h" +#include "td/utils/Random.h" +#include "IntCtx.h" + +namespace fift { +using td::Ref; + +DictKey::DictKey(vm::StackEntry se) { + auto tp = tp_ = se.type(); + switch (tp) { + case Type::t_int: + ref_ = se.as_int(); + break; + case Type::t_atom: + ref_ = se.as_atom(); + break; + case Type::t_string: + ref_ = se.as_string_ref(); + break; + case Type::t_bytes: + ref_ = se.as_bytes_ref(); + break; + case Type::t_null: + break; + default: + throw IntError{"unsupported key type"}; + } + compute_hash(); +} + +DictKey::operator vm::StackEntry() const& { + switch (tp_) { + case Type::t_int: + return value(); + case Type::t_atom: + return value(); + case Type::t_string: + case Type::t_bytes: + return {value>(), tp_ == Type::t_bytes}; + default: + return {}; + } +} + +DictKey::operator vm::StackEntry() && { + switch (tp_) { + case Type::t_int: + return move_value(); + case Type::t_atom: + return move_value(); + case Type::t_string: + case Type::t_bytes: + return {move_value>(), tp_ == Type::t_bytes}; + default: + return {}; + } +} + +std::ostream& operator<<(std::ostream& os, const DictKey& dkey) { + return os << vm::StackEntry(dkey).to_string(); +} + +int DictKey::cmp_internal(const DictKey& other) const { + if (tp_ != other.tp_) { + return tp_ < other.tp_ ? -1 : 1; + } + switch (tp_) { + case Type::t_int: + return td::cmp(value(), other.value()); + case Type::t_atom: { + int u = value()->index(), v = other.value()->index(); + return u == v ? 0 : (u < v ? -1 : 1); + } + case Type::t_string: + case Type::t_bytes: + return value>()->compare(*other.value>()); + default: + return 0; + } +} + +int DictKey::cmp(const DictKey& other) const { + if (hash_ < other.hash_) { + return -1; + } else if (hash_ > other.hash_) { + return 1; + } else { + return cmp_internal(other); + } +} + +DictKey::keyhash_t DictKey::compute_str_hash(DictKey::keyhash_t h, const char* str, std::size_t len) { + const char* end = str + len; + while (str < end) { + h = h * StrHash + (unsigned char)*str++; + } + return h; +} + +DictKey::keyhash_t DictKey::compute_int_hash(td::AnyIntView<> x) { + keyhash_t h = IntHash0; + for (int i = 0; i < x.size(); i++) { + h = h * MixConst3 + x.digits[i]; + } + return h * MixConst4; +} + +DictKey::keyhash_t DictKey::compute_hash() { + switch (tp_) { + case Type::t_int: + return hash_ = compute_int_hash(value()->as_any_int()); + case Type::t_atom: + return hash_ = value()->index() * MixConst1 + MixConst2; + case Type::t_string: + case Type::t_bytes: { + auto ref = value>(); + return hash_ = compute_str_hash(tp_, ref->data(), ref->size()); + } + default: + return hash_ = 0; + } +} + +const Hashmap* Hashmap::lookup_key_aux(const Hashmap* root, const DictKey& key) { + if (key.is_null()) { + return nullptr; + } + while (root) { + int r = key.cmp(root->key_); + if (!r) { + break; + } + root = (r < 0 ? root->left_.get() : root->right_.get()); + } + return root; +} + +Ref Hashmap::lookup_key(Ref root, const DictKey& key) { + return Ref(lookup_key_aux(root.get(), key)); +} + +vm::StackEntry Hashmap::get_key(Ref root, const DictKey& key) { + auto node = lookup_key_aux(root.get(), key); + if (node) { + return node->value_; + } else { + return {}; + } +} + +std::pair, vm::StackEntry> Hashmap::get_remove_key(Ref root, const DictKey& key) { + if (root.is_null() || key.is_null()) { + return {std::move(root), {}}; + } + vm::StackEntry val; + auto res = root->get_remove_internal(key, val); + if (val.is_null()) { + return {std::move(root), {}}; + } else { + return {std::move(res), std::move(val)}; + } +} + +Ref Hashmap::remove_key(Ref root, const DictKey& key) { + if (root.is_null() || key.is_null()) { + return root; + } + vm::StackEntry val; + auto res = root->get_remove_internal(key, val); + if (val.is_null()) { + return root; + } else { + return res; + } +} + +Ref Hashmap::get_remove_internal(const DictKey& key, vm::StackEntry& val) const { + int r = key.cmp(key_); + if (!r) { + val = value_; + return merge(left_, right_); + } else if (r < 0) { + if (left_.is_null()) { + return {}; + } else { + auto res = left_->get_remove_internal(key, val); + if (val.is_null()) { + return res; + } else { + return td::make_ref(key_, value_, std::move(res), right_, y_); + } + } + } else if (right_.is_null()) { + return {}; + } else { + auto res = right_->get_remove_internal(key, val); + if (val.is_null()) { + return res; + } else { + return td::make_ref(key_, value_, left_, std::move(res), y_); + } + } +} + +Ref Hashmap::merge(Ref a, Ref b) { + if (a.is_null()) { + return b; + } else if (b.is_null()) { + return a; + } else if (a->y_ > b->y_) { + auto& aa = a.write(); + aa.right_ = merge(std::move(aa.right_), std::move(b)); + return a; + } else { + auto& bb = b.write(); + bb.left_ = merge(std::move(a), std::move(bb.left_)); + return b; + } +} + +Ref Hashmap::set(Ref root, const DictKey& key, vm::StackEntry value) { + if (!key.is_null() && !replace(root, key, value) && !value.is_null()) { + insert(root, key, value, new_y()); + } + return root; +} + +bool Hashmap::replace(Ref& root, const DictKey& key, vm::StackEntry value) { + if (root.is_null() || key.is_null()) { + return false; + } + if (value.is_null()) { + auto res = root->get_remove_internal(key, value); + if (value.is_null()) { + return false; + } else { + root = std::move(res); + return true; + } + } + bool found = false; + auto res = root->replace_internal(key, std::move(value), found); + if (found) { + root = std::move(res); + } + return found; +} + +Ref Hashmap::replace_internal(const DictKey& key, const vm::StackEntry& value, bool& found) const { + int r = key.cmp(key_); + if (!r) { + found = true; + return td::make_ref(key_, value, left_, right_, y_); + } else if (r < 0) { + if (left_.is_null()) { + found = false; + return {}; + } + auto res = left_->replace_internal(key, value, found); + if (!found) { + return {}; + } + return td::make_ref(key_, value_, std::move(res), right_, y_); + } else { + if (right_.is_null()) { + found = false; + return {}; + } + auto res = right_->replace_internal(key, value, found); + if (!found) { + return {}; + } + return td::make_ref(key_, value_, left_, std::move(res), y_); + } +} + +void Hashmap::insert(Ref& root, const DictKey& key, vm::StackEntry value, long long y) { + if (root.is_null()) { + root = td::make_ref(key, std::move(value), empty(), empty(), y); + return; + } + if (root->y_ <= y) { + auto res = split(std::move(root), key); + root = td::make_ref(key, std::move(value), std::move(res.first), std::move(res.second), y); + return; + } + int r = key.cmp(root->key_); + CHECK(r); + insert(r < 0 ? root.write().left_ : root.write().right_, key, std::move(value), y); +} + +std::pair, Ref> Hashmap::split(Ref root, const DictKey& key, bool cmpv) { + if (root.is_null()) { + return {{}, {}}; + } + int r = key.cmp(root->key_); + if (r < (int)cmpv) { + if (root->left_.is_null()) { + return {{}, std::move(root)}; + } + auto res = split(root->left_, key, cmpv); + return {std::move(res.first), + td::make_ref(root->key_, root->value_, std::move(res.second), root->right_, root->y_)}; + } else { + if (root->right_.is_null()) { + return {std::move(root), {}}; + } + auto res = split(root->right_, key, cmpv); + return {td::make_ref(root->key_, root->value_, root->left_, std::move(res.first), root->y_), + std::move(res.second)}; + } +} + +long long Hashmap::new_y() { + return td::Random::fast_uint64(); +} + +bool HashmapIterator::unwind(Ref root) { + if (root.is_null()) { + return false; + } + while (true) { + auto left = root->lr(down_); + if (left.is_null()) { + cur_ = std::move(root); + return true; + } + stack_.push_back(std::move(root)); + root = std::move(left); + } +} + +bool HashmapIterator::next() { + if (cur_.not_null()) { + cur_ = cur_->rl(down_); + if (cur_.not_null()) { + while (true) { + auto left = cur_->lr(down_); + if (left.is_null()) { + return true; + } + stack_.push_back(std::move(cur_)); + cur_ = std::move(left); + } + } + } + if (stack_.empty()) { + return false; + } + cur_ = std::move(stack_.back()); + stack_.pop_back(); + return true; +} + +} // namespace fift diff --git a/crypto/fift/HashMap.h b/crypto/fift/HashMap.h new file mode 100644 index 00000000..d3d9ffbe --- /dev/null +++ b/crypto/fift/HashMap.h @@ -0,0 +1,306 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#pragma once +#include "common/refcnt.hpp" +#include "vm/stack.hpp" +#include "vm/atom.h" + +namespace fift { + +using td::Ref; +using td::RefAny; + +class DictKey { + public: + typedef vm::StackEntry::Type Type; + typedef unsigned long long keyhash_t; + + private: + RefAny ref_; + Type tp_ = Type::t_null; + keyhash_t hash_ = 0; + + static constexpr keyhash_t IntHash0 = 0xce6ab89d724409ed, MixConst1 = 0xcd5c126501510979, + MixConst2 = 0xb8f44d7fd6274ad1, MixConst3 = 0xd08726ea2422e405, + MixConst4 = 0x6407d2aeb5039dfb, StrHash = 0x93ff128344add06d; + keyhash_t compute_hash(); + static keyhash_t compute_str_hash(DictKey::keyhash_t h, const char* str, std::size_t len); + static keyhash_t compute_int_hash(td::AnyIntView<> x); + int cmp_internal(const DictKey& other) const; + template + Ref value() const { + return Ref{td::static_cast_ref(), ref_}; + } + template + Ref move_value() { + return Ref{td::static_cast_ref(), std::move(ref_)}; + } + + public: + DictKey() : ref_(), tp_(Type::t_null) { + } + DictKey(const DictKey& other) = default; + DictKey(DictKey&& other) = default; + DictKey& operator=(const DictKey& other) = default; + DictKey& operator=(DictKey&& other) = default; + DictKey(Ref atom_ref) : ref_(std::move(atom_ref)), tp_(Type::t_atom) { + compute_hash(); + } + DictKey(td::RefInt256 int_ref) : ref_(std::move(int_ref)), tp_(Type::t_int) { + compute_hash(); + } + explicit DictKey(vm::StackEntry se); + DictKey(std::string str, bool bytes = false) : ref_(), tp_(bytes ? Type::t_bytes : Type::t_string) { + ref_ = Ref>{true, std::move(str)}; + compute_hash(); + } + Type type() const { + return tp_; + } + void swap(DictKey& other) { + ref_.swap(other.ref_); + std::swap(tp_, other.tp_); + } + + operator vm::StackEntry() const&; + operator vm::StackEntry() &&; + int cmp(const DictKey& other) const; + bool operator==(const DictKey& other) const { + return hash_ == other.hash_ && !cmp_internal(other); + } + bool operator!=(const DictKey& other) const { + return hash_ != other.hash_ || cmp_internal(other); + } + bool operator<(const DictKey& other) const { + return hash_ < other.hash_ || (hash_ == other.hash_ && cmp_internal(other) < 0); + } + bool is_null() const { + return tp_ == Type::t_null; + } + bool is_string() const { + return tp_ == Type::t_string; + } +}; + +std::ostream& operator<<(std::ostream& os, const DictKey& dkey); + +class Hashmap : public td::CntObject { + DictKey key_; + vm::StackEntry value_; + Ref left_; + Ref right_; + long long y_; + + public: + Hashmap(DictKey key, vm::StackEntry value, Ref left, Ref right, long long y) + : key_(std::move(key)), value_(std::move(value)), left_(std::move(left)), right_(std::move(right)), y_(y) { + } + Hashmap(const Hashmap& other) = default; + Hashmap(Hashmap&& other) = default; + virtual ~Hashmap() { + } + Hashmap* make_copy() const override { + return new Hashmap(*this); + } + const DictKey& key() const& { + return key_; + } + DictKey key() && { + return std::move(key_); + } + const vm::StackEntry& value() const& { + return value_; + } + vm::StackEntry value() && { + return std::move(value_); + } + Ref left() const { + return left_; + } + Ref right() const { + return right_; + } + Ref lr(bool branch) const { + return branch ? right_ : left_; + } + Ref rl(bool branch) const { + return branch ? left_ : right_; + } + static Ref lookup_key(Ref root, const DictKey& key); + template + static Ref lookup(Ref root, Args&&... args) { + return lookup_key(std::move(root), DictKey{std::forward(args)...}); + } + static vm::StackEntry get_key(Ref root, const DictKey& key); + template + static vm::StackEntry get(Ref root, Args&&... args) { + return get_key(std::move(root), DictKey{std::forward(args)...}); + } + static Ref remove_key(Ref root, const DictKey& key); + template + static Ref remove(Ref root, Args&&... args) { + return remove_key(std::move(root), DictKey{std::forward(args)...}); + } + static std::pair, vm::StackEntry> get_remove_key(Ref root, const DictKey& key); + template + static std::pair, vm::StackEntry> get_remove(Ref root, Args&&... args) { + return get_remove_key(std::move(root), DictKey{std::forward(args)...}); + } + static Ref set(Ref root, const DictKey& key, vm::StackEntry value); + static bool replace(Ref& root, const DictKey& key, vm::StackEntry value); + static std::pair, Ref> split(Ref root, const DictKey& key, bool eq_left = false); + static Ref empty() { + return {}; + } + + private: + static Ref merge(Ref a, Ref b); + static const Hashmap* lookup_key_aux(const Hashmap* root, const DictKey& key); + Ref get_remove_internal(const DictKey& key, vm::StackEntry& val) const; + Ref replace_internal(const DictKey& key, const vm::StackEntry& value, bool& found) const; + static void insert(Ref& root, const DictKey& key, vm::StackEntry value, long long y); + static long long new_y(); +}; + +struct HashmapIdx { + Ref& root_; + DictKey idx_; + template + HashmapIdx(Ref& root, Args&&... args) : root_(root), idx_(std::forward(args)...) { + } + operator vm::StackEntry() const { + return Hashmap::get(root_, idx_); + } + template + HashmapIdx& operator=(T&& value) { + root_ = Hashmap::set(root_, idx_, vm::StackEntry(std::forward(value))); + return *this; + } +}; + +class HashmapIterator { + std::vector> stack_; + Ref cur_; + const bool down_{false}; + bool unwind(Ref root); + + public: + HashmapIterator() = default; + HashmapIterator(Ref root, bool down = false) : down_(down) { + unwind(std::move(root)); + } + const Hashmap& operator*() const { + return *cur_; + } + const Hashmap* operator->() const { + return cur_.get(); + } + bool eof() { + return cur_.is_null(); + } + bool next(); + bool operator<(const HashmapIterator& other) const { + if (other.cur_.is_null()) { + return cur_.not_null(); + } else if (cur_.is_null()) { + return false; + } else { + return cur_->key().cmp(other.cur_->key()) * (down_ ? -1 : 1) < 0; + } + } + bool operator==(const HashmapIterator& other) const { + return other.cur_.is_null() ? cur_.is_null() : (cur_.not_null() && cur_->key() == other.cur_->key()); + } + bool operator!=(const HashmapIterator& other) const { + return other.cur_.is_null() ? cur_.not_null() : (cur_.is_null() || cur_->key() != other.cur_->key()); + } + HashmapIterator& operator++() { + next(); + return *this; + } +}; + +struct HashmapKeeper { + Ref root; + HashmapKeeper() = default; + HashmapKeeper(Ref _root) : root(std::move(_root)) { + } + Ref extract() { + return std::move(root); + } + operator Ref() const& { + return root; + } + operator Ref() && { + return std::move(root); + } + template + HashmapIdx operator[](Args&&... args) { + return HashmapIdx{root, DictKey{std::forward(args)...}}; + } + template + vm::StackEntry operator[](Args&&... args) const { + return Hashmap::get(root, DictKey{std::forward(args)...}); + } + vm::StackEntry get_key(const DictKey& key) const { + return Hashmap::get(root, key); + } + template + vm::StackEntry get(Args&&... args) const { + return Hashmap::get(root, DictKey{std::forward(args)...}); + } + vm::StackEntry get_remove_key(const DictKey& key) { + auto res = Hashmap::get_remove_key(root, key); + root = std::move(res.first); + return std::move(res.second); + } + template + vm::StackEntry get_remove(Args&&... args) { + return get_remove_key(DictKey{std::forward(args)...}); + } + bool remove_key(const DictKey& key) { + auto res = Hashmap::get_remove(root, key); + root = std::move(res.first); + return !res.second.is_null(); + } + template + bool remove(Args&&... args) { + return remove_key(DictKey{std::forward(args)...}); + } + template + void set(T key, vm::StackEntry value) { + root = Hashmap::set(root, DictKey(key), std::move(value)); + } + template + bool replace(T key, vm::StackEntry value) { + return Hashmap::replace(root, DictKey(key), std::move(value)); + } + HashmapIterator begin(bool reverse = false) const { + return HashmapIterator{root, reverse}; + } + HashmapIterator end() const { + return HashmapIterator{}; + } + HashmapIterator rbegin() const { + return HashmapIterator{root, true}; + } + HashmapIterator rend() const { + return HashmapIterator{}; + } +}; + +} // namespace fift diff --git a/crypto/fift/IntCtx.cpp b/crypto/fift/IntCtx.cpp index a091c049..2cac8849 100644 --- a/crypto/fift/IntCtx.cpp +++ b/crypto/fift/IntCtx.cpp @@ -14,13 +14,13 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "IntCtx.h" namespace fift { -td::StringBuilder& operator<<(td::StringBuilder& os, const IntCtx& ctx) { +td::StringBuilder& operator<<(td::StringBuilder& os, const ParseCtx& ctx) { if (ctx.include_depth) { return os << ctx.filename << ":" << ctx.line_no << ": "; } else { @@ -28,7 +28,7 @@ td::StringBuilder& operator<<(td::StringBuilder& os, const IntCtx& ctx) { } } -std::ostream& operator<<(std::ostream& os, const IntCtx& ctx) { +std::ostream& operator<<(std::ostream& os, const ParseCtx& ctx) { return os << (PSLICE() << ctx).c_str(); } @@ -67,37 +67,7 @@ void CharClassifier::set_char_class(int c, int cl) { *p = static_cast((*p & ~mask) | cl); } -IntCtx::Savepoint::Savepoint(IntCtx& _ctx, std::string new_filename, std::string new_current_dir, - std::istream* new_input_stream) - : ctx(_ctx) - , old_line_no(_ctx.line_no) - , old_need_line(_ctx.need_line) - , old_filename(_ctx.filename) - , old_current_dir(_ctx.currentd_dir) - , old_input_stream(_ctx.input_stream) - , old_curline(_ctx.str) - , old_curpos(_ctx.input_ptr - _ctx.str.c_str()) { - ctx.line_no = 0; - ctx.filename = new_filename; - ctx.currentd_dir = new_current_dir; - ctx.input_stream = new_input_stream; - ctx.str = ""; - ctx.input_ptr = 0; - ++(ctx.include_depth); -} - -IntCtx::Savepoint::~Savepoint() { - ctx.line_no = old_line_no; - ctx.need_line = old_need_line; - ctx.filename = old_filename; - ctx.currentd_dir = old_current_dir; - ctx.input_stream = old_input_stream; - ctx.str = old_curline; - ctx.input_ptr = ctx.str.c_str() + old_curpos; - --(ctx.include_depth); -} - -bool IntCtx::load_next_line() { +bool ParseCtx::load_next_line() { if (!std::getline(*input_stream, str)) { return false; } @@ -109,11 +79,11 @@ bool IntCtx::load_next_line() { return true; } -bool IntCtx::is_sb() const { +bool ParseCtx::is_sb() const { return !eof() && line_no == 1 && *input_ptr == '#' && input_ptr[1] == '!'; } -td::Slice IntCtx::scan_word_to(char delim, bool err_endl) { +td::Slice ParseCtx::scan_word_to(char delim, bool err_endl) { load_next_line_ifreq(); auto ptr = input_ptr; while (*ptr && *ptr != delim) { @@ -131,7 +101,7 @@ td::Slice IntCtx::scan_word_to(char delim, bool err_endl) { } } -td::Slice IntCtx::scan_word() { +td::Slice ParseCtx::scan_word() { skipspc(true); auto ptr = input_ptr; while (*ptr && *ptr != ' ' && *ptr != '\t' && *ptr != '\r') { @@ -143,7 +113,7 @@ td::Slice IntCtx::scan_word() { return td::Slice{ptr, ptr2}; } -td::Slice IntCtx::scan_word_ext(const CharClassifier& classifier) { +td::Slice ParseCtx::scan_word_ext(const CharClassifier& classifier) { skipspc(true); auto ptr = input_ptr; while (*ptr && *ptr != '\r' && *ptr != '\n') { @@ -160,7 +130,7 @@ td::Slice IntCtx::scan_word_ext(const CharClassifier& classifier) { return td::Slice{ptr, input_ptr}; } -void IntCtx::skipspc(bool skip_eol) { +void ParseCtx::skipspc(bool skip_eol) { do { while (*input_ptr == ' ' || *input_ptr == '\t' || *input_ptr == '\r') { ++input_ptr; @@ -171,27 +141,166 @@ void IntCtx::skipspc(bool skip_eol) { } while (load_next_line()); } -void check_compile(const IntCtx& ctx) { - if (ctx.state <= 0) { +bool IntCtx::enter_ctx(std::unique_ptr new_parser) { + if (!new_parser) { + return false; + } + if (parser) { + parser_save_stack.push_back(std::move(parser)); + } + parser = std::move(new_parser); + return true; +} + +bool IntCtx::enter_ctx(std::string new_filename, std::string new_current_dir, + std::unique_ptr new_input_stream) { + if (!new_input_stream) { + return false; + } else { + return enter_ctx( + std::make_unique(std::move(new_input_stream), new_filename, new_current_dir, include_depth() + 1)); + } +} + +bool IntCtx::leave_ctx() { + if (parser_save_stack.empty()) { + return false; + } else { + parser = std::move(parser_save_stack.back()); + parser_save_stack.pop_back(); + return true; + } +} + +bool IntCtx::top_ctx() { + if (!parser_save_stack.empty()) { + parser = std::move(parser_save_stack[0]); + parser_save_stack.clear(); + } + return true; +} + +void IntCtx::check_compile() const { + if (state <= 0) { throw IntError{"compilation mode only"}; } } -void check_execute(const IntCtx& ctx) { - if (ctx.state != 0) { +void IntCtx::check_execute() const { + if (state != 0) { throw IntError{"interpret mode only"}; } } -void check_not_int_exec(const IntCtx& ctx) { - if (ctx.state < 0) { +void IntCtx::check_not_int_exec() const { + if (state < 0) { throw IntError{"not allowed in internal interpret mode"}; } } -void check_int_exec(const IntCtx& ctx) { - if (ctx.state >= 0) { +void IntCtx::check_int_exec() const { + if (state >= 0) { throw IntError{"internal interpret mode only"}; } } + +bool IntCtx::print_error_backtrace(std::ostream& os) const { + if (exc_cont.is_null() && exc_next.is_null()) { + os << "(no backtrace)\n"; + return false; + } + if (exc_cont.not_null()) { + os << "top: "; + exc_cont->dump(os, *this); + } + return print_backtrace(os, exc_next); +} + +bool IntCtx::print_backtrace(std::ostream& os, Ref cont) const { + for (int i = 1; cont.not_null() && i <= 16; i++) { + os << "level " << i << ": "; + cont->dump(os, *this); + cont = cont->up(); + } + if (cont.not_null()) { + os << "... more levels ...\n"; + } + return true; +} + +Ref IntCtx::throw_exception(td::Status err, Ref cur) { + exc_cont = std::move(cur); + exc_next = std::move(next); + error = std::move(err); + next.clear(); + auto cont = std::move(exc_handler); + if (cont.is_null()) { + return {}; // no Fift exception handler set + } else if (cont.is_unique()) { + return cont.unique_write().handle_modify(*this); + } else { + return cont->handle_tail(*this); + } +} + +void IntCtx::clear_error() { + error = td::Status::OK(); + exit_code = 0; +} + +td::Result IntCtx::get_result() { + if (error.is_error()) { + return error.move_as_error(); + } else { + return exit_code; + } +} + +std::ostream& ParseCtx::show_context(std::ostream& os) const { + if (include_depth && line_no) { + os << filename << ":" << line_no << ":\t"; + } + if (!word.empty()) { + os << word << ":"; + } + return os; +} + +td::Status IntCtx::add_error_loc(td::Status err) const { + if (err.is_error() && parser) { + std::ostringstream os; + parser->show_context(os); + return err.move_as_error_prefix(os.str()); + } else { + return err; + } +} + +td::Result IntCtx::run(Ref cont) { + clear_error(); + while (cont.not_null()) { + try { + if (cont.is_unique()) { + cont = cont.unique_write().run_modify(*this); + } else { + cont = cont->run_tail(*this); + } + } catch (IntError& err) { + cont = throw_exception(td::Status::Error(err.msg), std::move(cont)); + } catch (vm::VmError& err) { + cont = throw_exception(err.as_status(), std::move(cont)); + } catch (vm::VmVirtError& err) { + cont = throw_exception(err.as_status(), std::move(cont)); + } catch (vm::CellBuilder::CellWriteError&) { + cont = throw_exception(td::Status::Error("Cell builder write error"), std::move(cont)); + } catch (vm::VmFatal&) { + cont = throw_exception(td::Status::Error("fatal vm error"), std::move(cont)); + } + if (cont.is_null()) { + cont = std::move(next); + } + } + return get_result(); +} + } // namespace fift diff --git a/crypto/fift/IntCtx.h b/crypto/fift/IntCtx.h index b2725cda..5d574ae5 100644 --- a/crypto/fift/IntCtx.h +++ b/crypto/fift/IntCtx.h @@ -14,19 +14,29 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once -#include "crypto/vm/db/TonDb.h" // FIXME #include "crypto/vm/stack.hpp" +#include "crypto/vm/box.hpp" #include "crypto/common/bitstring.h" +#include "td/utils/Status.h" + +#include "Dictionary.h" +#include "Continuation.h" + #include #include #include #include +namespace vm { +class TonDbImpl; // from crypto/vm/db/TonDb.h +using TonDb = std::unique_ptr; +} // namespace vm + namespace fift { class Dictionary; class SourceLookup; @@ -63,32 +73,34 @@ class CharClassifier { } }; -struct IntCtx { - vm::Stack stack; - int state{0}; +struct ParseCtx { int include_depth{0}; int line_no{0}; bool need_line{true}; std::string filename; std::string currentd_dir; std::istream* input_stream{nullptr}; - std::ostream* output_stream{nullptr}; - std::ostream* error_stream{nullptr}; - - vm::TonDb* ton_db{nullptr}; - Dictionary* dictionary{nullptr}; - SourceLookup* source_lookup{nullptr}; - int* now{nullptr}; + std::unique_ptr input_stream_holder; + std::string word; private: std::string str; - const char* input_ptr; + const char* input_ptr = nullptr; public: - IntCtx() = default; - - operator vm::Stack&() { - return stack; + ParseCtx() = default; + ParseCtx(std::istream& _istream, std::string _filename, std::string _curdir = "", int _depth = 0) + : include_depth(_depth) + , filename(std::move(_filename)) + , currentd_dir(std::move(_curdir)) + , input_stream(&_istream) { + } + ParseCtx(std::unique_ptr _istream_ptr, std::string _filename, std::string _curdir = "", int _depth = 0) + : include_depth(_depth) + , filename(std::move(_filename)) + , currentd_dir(std::move(_curdir)) + , input_stream(_istream_ptr.get()) + , input_stream_holder(std::move(_istream_ptr)) { } td::Slice scan_word_to(char delim, bool err_endl = true); @@ -123,31 +135,79 @@ struct IntCtx { bool is_sb() const; + std::ostream& show_context(std::ostream& os) const; +}; + +struct IntCtx { + vm::Stack stack; + Ref next, exc_handler; + Ref exc_cont, exc_next; + int state{0}; + int exit_code{0}; + td::Status error; + + std::unique_ptr parser; + std::vector> parser_save_stack; + + std::ostream* output_stream{nullptr}; // move to OutCtx? + std::ostream* error_stream{nullptr}; + + vm::TonDb* ton_db{nullptr}; + SourceLookup* source_lookup{nullptr}; + int* now{nullptr}; + + Dictionary dictionary, main_dictionary, context; + + public: + IntCtx() = default; + IntCtx(std::istream& _istream, std::string _filename, std::string _curdir = "", int _depth = 0) { + parser = std::make_unique(_istream, _filename, _curdir, _depth); + } + IntCtx(std::unique_ptr _istream, std::string _filename, std::string _curdir = "", int _depth = 0) { + parser = std::make_unique(std::move(_istream), _filename, _curdir, _depth); + } + + bool enter_ctx(std::unique_ptr new_ctx); + bool enter_ctx(std::string new_filename, std::string new_current_dir, std::unique_ptr new_input_stream); + bool leave_ctx(); + bool top_ctx(); + int include_depth() const { + return parser ? parser->include_depth : -1; + } + + operator vm::Stack &() { + return stack; + } + void clear() { state = 0; stack.clear(); } - class Savepoint { - IntCtx& ctx; - int old_line_no; - bool old_need_line; - std::string old_filename; - std::string old_current_dir; - std::istream* old_input_stream; - std::string old_curline; - std::ptrdiff_t old_curpos; - public: - Savepoint(IntCtx& _ctx, std::string new_filename, std::string new_current_dir, std::istream* new_input_stream); - ~Savepoint(); - }; + void check_compile() const; + void check_execute() const; + void check_not_int_exec() const; + void check_int_exec() const; + + bool print_error_backtrace(std::ostream& os) const; + bool print_backtrace(std::ostream& os, Ref cont) const; + + td::Status add_error_loc(td::Status err) const; + + void set_exit_code(int err_code) { + exit_code = err_code; + } + int get_exit_code() const { + return exit_code; + } + + void clear_error(); + td::Result get_result(); + + Ref throw_exception(td::Status err, Ref cur = {}); + td::Result run(Ref cont); }; -void check_compile(const IntCtx& ctx); -void check_execute(const IntCtx& ctx); -void check_not_int_exec(const IntCtx& ctx); -void check_int_exec(const IntCtx& ctx); - -td::StringBuilder& operator<<(td::StringBuilder& os, const IntCtx& ctx); -std::ostream& operator<<(std::ostream& os, const IntCtx& ctx); +td::StringBuilder& operator<<(td::StringBuilder& os, const ParseCtx& ctx); +std::ostream& operator<<(std::ostream& os, const ParseCtx& ctx); } // namespace fift diff --git a/crypto/fift/SourceLookup.cpp b/crypto/fift/SourceLookup.cpp index 50cd8c31..cd80e314 100644 --- a/crypto/fift/SourceLookup.cpp +++ b/crypto/fift/SourceLookup.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "SourceLookup.h" diff --git a/crypto/fift/SourceLookup.h b/crypto/fift/SourceLookup.h index 7d51396c..816ad667 100644 --- a/crypto/fift/SourceLookup.h +++ b/crypto/fift/SourceLookup.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include diff --git a/crypto/fift/fift-main.cpp b/crypto/fift/fift-main.cpp index 8702f13d..ef833f43 100644 --- a/crypto/fift/fift-main.cpp +++ b/crypto/fift/fift-main.cpp @@ -1,4 +1,4 @@ -/* +/* This file is part of TON Blockchain source code. TON Blockchain is free software; you can redistribute it and/or @@ -14,16 +14,16 @@ You should have received a copy of the GNU General Public License along with TON Blockchain. If not, see . - In addition, as a special exception, the copyright holders give permission - to link the code of portions of this program with the OpenSSL library. - You must obey the GNU General Public License in all respects for all - of the code used other than OpenSSL. If you modify file(s) with this - exception, you may extend this exception to your version of the file(s), - but you are not obligated to do so. If you do not wish to do so, delete this - exception statement from your version. If you delete this exception statement + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/stack.hpp" #include @@ -53,6 +53,8 @@ #include "td/utils/Parser.h" #include "td/utils/port/path.h" +#include "git.h" + void usage(const char* progname) { std::cerr << "A simple Fift interpreter. Type `bye` to quit, or `words` to get a list of all commands\n"; std::cerr @@ -65,7 +67,8 @@ void usage(const char* progname) { "\t-L\tPre-loads a library source file\n" "\t-d\tUse a ton database\n" "\t-s\tScript mode: use first argument as a fift source file and import remaining arguments as $n)\n" - "\t-v\tSet verbosity level\n"; + "\t-v\tSet verbosity level\n" + "\t-V\tShow fift build information\n"; std::exit(2); } @@ -92,7 +95,7 @@ int main(int argc, char* const argv[]) { int i; int new_verbosity_level = VERBOSITY_NAME(INFO); - while (!script_mode && (i = getopt(argc, argv, "hinI:L:d:sv:")) != -1) { + while (!script_mode && (i = getopt(argc, argv, "hinI:L:d:sv:V")) != -1) { switch (i) { case 'i': interactive = true; @@ -116,6 +119,11 @@ int main(int argc, char* const argv[]) { case 'v': new_verbosity_level = VERBOSITY_NAME(FATAL) + td::to_integer(td::Slice(optarg)); break; + case 'V': + std::cout << "Fift build information: [ Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate() << "]\n"; + std::exit(0); + break; + case 'h': default: usage(argv[0]); @@ -156,7 +164,7 @@ int main(int argc, char* const argv[]) { } fift::init_words_common(config.dictionary); - fift::init_words_vm(config.dictionary); + fift::init_words_vm(config.dictionary, true); // enable vm debug fift::init_words_ton(config.dictionary); if (script_mode) { diff --git a/crypto/fift/lib/Asm.fif b/crypto/fift/lib/Asm.fif index f0035a00..92ceab6d 100644 --- a/crypto/fift/lib/Asm.fif +++ b/crypto/fift/lib/Asm.fif @@ -1,21 +1,28 @@ library TVM_Asm // simple TVM Assembler +namespace Asm +Asm definitions +"0.4.5" constant asm-fif-version + variable @atend -'nop @atend ! +variable @was-split +false @was-split ! +{ "not in asm context" abort } @atend ! { `normal eq? not abort"must be terminated by }>" } : @normal? -{ @atend @ 1 { @atend ! @normal? } does @atend ! } : @pushatend -{ @pushatend +{ false @was-split ! `normal @endblk } : }> { }> b> } : }>c { }>c s -{ @atend @ 2 { @atend ! rot b> ref, swap @endblk } does @atend ! ref, swap @endblk } does @atend ! = -rot <= and } : 2x<= { 2 pick brembitrefs 1- 2x<= } : @havebitrefs -{ @havebits not ' @| if } : @ensurebits -{ @havebitrefs not ' @| if } : @ensurebitrefs +{ @havebits ' @| ifnot } : @ensurebits +{ @havebitrefs ' @| ifnot } : @ensurebitrefs { rot over @ensurebits -rot u, } : @simpleuop { tuck sbitrefs @ensurebitrefs swap s, } : @addop { tuck bbitrefs @ensurebitrefs swap b+ } : @addopb @@ -25,8 +32,10 @@ variable @atend { 1 { } : si() // x mi ma -- ? { rot tuck >= -rot <= and } : @range @@ -165,6 +174,8 @@ x{68} @Defop DEPTH x{69} @Defop CHKDEPTH x{6A} @Defop ONLYTOPX x{6B} @Defop ONLYX +{ over 0= abort"first argument must be non-zero" + = or abort"invalid slice padding" swap 1 1 u, 0 rot u, } : @scomplete -{ tuck sbitrefs swap 17 + swap @havebitrefs not +{ tuck sbitrefs swap 26 + swap @havebitrefs not { PUSHREFSLICE } { over sbitrefs 2dup 123 0 2x<= { drop tuck 4 + 3 >> swap x{8B} s, over 4 u, 3 roll s, @@ -266,10 +281,27 @@ x{8A} @Defop(ref) PUSHREFCONT } cond } cond } dup : PUSHSLICE : SLICE -{ tuck bbitrefs swap 16 + dup 7 and 3 -roll swap @havebitrefs - not rot or - { swap b> PUSHREFCONT } - { over bbitrefs 2dup 120 0 2x<= +// ( b' -- ? ) +{ bbitrefs or 0= } : @cont-empty? +{ bbits 7 and 0= } : @cont-aligned? +// ( b b' -- ? ) +{ bbitrefs over 7 and { 2drop drop false } { + swap 16 + swap @havebitrefs nip + } cond +} : @cont-fits? +// ( b b' -- ? ) +{ bbitrefs over 7 and { 2drop drop false } { + 32 1 pair+ @havebitrefs nip + } cond +} : @cont-ref-fit? +// ( b b' b'' -- ? ) +{ over @cont-aligned? over @cont-aligned? and not { 2drop drop false } { + bbitrefs rot bbitrefs pair+ swap 32 + swap @havebitrefs nip + } cond +} : @two-cont-fit? +{ 2dup @cont-fits? not + { b> PUSHREFCONT } + { swap over bbitrefs 2dup 120 0 2x<= { drop swap x{9} s, swap 3 >> 4 u, swap b+ } { rot x{8F_} s, swap 2 u, swap 3 >> 7 u, swap b+ } cond } cond @@ -297,27 +329,109 @@ x{A7} x{A8} @Defop(8i,alt) MULCONST ' SUBCONST : SUBINT ' MULCONST : MULINT x{A8} @Defop MUL + x{A904} @Defop DIV x{A905} @Defop DIVR x{A906} @Defop DIVC x{A908} @Defop MOD +x{A909} @Defop MODR +x{A90A} @Defop MODC x{A90C} @Defop DIVMOD x{A90D} @Defop DIVMODR x{A90E} @Defop DIVMODC +x{A900} @Defop ADDDIVMOD +x{A901} @Defop ADDDIVMODR +x{A902} @Defop ADDDIVMODC + x{A925} @Defop RSHIFTR +x{A926} @Defop RSHIFTC +x{A928} @Defop MODPOW2 +x{A929} @Defop MODPOW2R +x{A92A} @Defop MODPOW2C +x{A92C} @Defop RSHIFTMOD +x{A92D} @Defop RSHIFTMODR +x{A92E} @Defop RSHIFTMODC +x{A920} @Defop ADDRSHIFTMOD +x{A921} @Defop ADDRSHIFTMODR +x{A922} @Defop ADDRSHIFTMODC + x{A935} @Defop(8u+1) RSHIFTR# +x{A936} @Defop(8u+1) RSHIFTC# x{A938} @Defop(8u+1) MODPOW2# +x{A939} @Defop(8u+1) MODPOW2R# +x{A93A} @Defop(8u+1) MODPOW2C# +x{A93C} @Defop(8u+1) RSHIFT#MOD +x{A93D} @Defop(8u+1) RSHIFTR#MOD +x{A93E} @Defop(8u+1) RSHIFTC#MOD +x{A930} @Defop(8u+1) ADDRSHIFT#MOD +x{A931} @Defop(8u+1) ADDRSHIFTR#MOD +x{A932} @Defop(8u+1) ADDRSHIFTC#MOD + x{A984} @Defop MULDIV x{A985} @Defop MULDIVR +x{A986} @Defop MULDIVC +x{A988} @Defop MULMOD +x{A989} @Defop MULMODR +x{A98A} @Defop MULMODC x{A98C} @Defop MULDIVMOD +x{A98D} @Defop MULDIVMODR +x{A98E} @Defop MULDIVMODC +x{A980} @Defop MULADDDIVMOD +x{A981} @Defop MULADDDIVMODR +x{A982} @Defop MULADDDIVMODC + x{A9A4} @Defop MULRSHIFT x{A9A5} @Defop MULRSHIFTR +x{A9A6} @Defop MULRSHIFTC +x{A9A8} @Defop MULMODPOW2 +x{A9A9} @Defop MULMODPOW2R +x{A9AA} @Defop MULMODPOW2C +x{A9AC} @Defop MULRSHIFTMOD +x{A9AD} @Defop MULRSHIFTRMOD +x{A9AE} @Defop MULRSHIFTCMOD +x{A9A0} @Defop MULADDRSHIFTMOD +x{A9A1} @Defop MULADDRSHIFTRMOD +x{A9A2} @Defop MULADDRSHIFTCMOD + x{A9B4} @Defop(8u+1) MULRSHIFT# x{A9B5} @Defop(8u+1) MULRSHIFTR# +x{A9B6} @Defop(8u+1) MULRSHIFTC# +x{A9B8} @Defop(8u+1) MULMODPOW2# +x{A9B9} @Defop(8u+1) MULMODPOW2R# +x{A9BA} @Defop(8u+1) MULMODPOW2C# +x{A9BC} @Defop(8u+1) MULRSHIFT#MOD +x{A9BD} @Defop(8u+1) MULRSHIFTR#MOD +x{A9BE} @Defop(8u+1) MULRSHIFTC#MOD +x{A9B0} @Defop(8u+1) MULADDRSHIFT#MOD +x{A9B1} @Defop(8u+1) MULADDRSHIFTR#MOD +x{A9B2} @Defop(8u+1) MULADDRSHIFTC#MOD + x{A9C4} @Defop LSHIFTDIV x{A9C5} @Defop LSHIFTDIVR +x{A9C6} @Defop LSHIFTDIVC +x{A9C8} @Defop LSHIFTMOD +x{A9C9} @Defop LSHIFTMODR +x{A9CA} @Defop LSHIFTMODC +x{A9CC} @Defop LSHIFTDIVMOD +x{A9CD} @Defop LSHIFTDIVMODR +x{A9CE} @Defop LSHIFTDIVMODC +x{A9C0} @Defop LSHIFTADDDIVMOD +x{A9C1} @Defop LSHIFTADDDIVMODR +x{A9C2} @Defop LSHIFTADDDIVMODC + x{A9D4} @Defop(8u+1) LSHIFT#DIV x{A9D5} @Defop(8u+1) LSHIFT#DIVR +x{A9D6} @Defop(8u+1) LSHIFT#DIVC +x{A9D8} @Defop(8u+1) LSHIFT#MOD +x{A9D9} @Defop(8u+1) LSHIFT#MODR +x{A9DA} @Defop(8u+1) LSHIFT#MODC +x{A9DC} @Defop(8u+1) LSHIFT#DIVMOD +x{A9DD} @Defop(8u+1) LSHIFT#DIVMODR +x{A9DE} @Defop(8u+1) LSHIFT#DIVMODC +x{A9D0} @Defop(8u+1) LSHIFT#ADDDIVMOD +x{A9D1} @Defop(8u+1) LSHIFT#ADDDIVMODR +x{A9D2} @Defop(8u+1) LSHIFT#ADDDIVMODC + x{AA} @Defop(8u+1) LSHIFT# x{AB} @Defop(8u+1) RSHIFT# x{AC} @Defop LSHIFT @@ -347,15 +461,109 @@ x{B7A3} @Defop QNEGATE x{B7A4} @Defop QINC x{B7A5} @Defop QDEC x{B7A8} @Defop QMUL + x{B7A904} @Defop QDIV x{B7A905} @Defop QDIVR x{B7A906} @Defop QDIVC x{B7A908} @Defop QMOD +x{B7A909} @Defop QMODR +x{B7A90A} @Defop QMODC x{B7A90C} @Defop QDIVMOD x{B7A90D} @Defop QDIVMODR x{B7A90E} @Defop QDIVMODC +x{B7A900} @Defop QADDDIVMOD +x{B7A901} @Defop QADDDIVMODR +x{B7A902} @Defop QADDDIVMODC + +x{B7A925} @Defop QRSHIFTR +x{B7A926} @Defop QRSHIFTC +x{B7A928} @Defop QMODPOW2 +x{B7A929} @Defop QMODPOW2R +x{B7A92A} @Defop QMODPOW2C +x{B7A92C} @Defop QRSHIFTMOD +x{B7A92D} @Defop QRSHIFTMODR +x{B7A92E} @Defop QRSHIFTMODC +x{B7A920} @Defop QADDRSHIFTMOD +x{B7A921} @Defop QADDRSHIFTMODR +x{B7A922} @Defop QADDRSHIFTMODC + +x{B7A935} @Defop(8u+1) QRSHIFTR# +x{B7A936} @Defop(8u+1) QRSHIFTC# +x{B7A938} @Defop(8u+1) QMODPOW2# +x{B7A939} @Defop(8u+1) QMODPOW2R# +x{B7A93A} @Defop(8u+1) QMODPOW2C# +x{B7A93C} @Defop(8u+1) QRSHIFT#MOD +x{B7A93D} @Defop(8u+1) QRSHIFTR#MOD +x{B7A93E} @Defop(8u+1) QRSHIFTC#MOD +x{B7A930} @Defop(8u+1) QADDRSHIFT#MOD +x{B7A931} @Defop(8u+1) QADDRSHIFTR#MOD +x{B7A932} @Defop(8u+1) QADDRSHIFTC#MOD + +x{B7A984} @Defop QMULDIV x{B7A985} @Defop QMULDIVR +x{B7A986} @Defop QMULDIVC +x{B7A988} @Defop QMULMOD +x{B7A989} @Defop QMULMODR +x{B7A98A} @Defop QMULMODC x{B7A98C} @Defop QMULDIVMOD +x{B7A98D} @Defop QMULDIVMODR +x{B7A98E} @Defop QMULDIVMODC +x{B7A980} @Defop QMULADDDIVMOD +x{B7A981} @Defop QMULADDDIVMODR +x{B7A982} @Defop QMULADDDIVMODC + +x{B7A9A4} @Defop QMULRSHIFT +x{B7A9A5} @Defop QMULRSHIFTR +x{B7A9A6} @Defop QMULRSHIFTC +x{B7A9A8} @Defop QMULMODPOW2 +x{B7A9A9} @Defop QMULMODPOW2R +x{B7A9AA} @Defop QMULMODPOW2C +x{B7A9AC} @Defop QMULRSHIFTMOD +x{B7A9AD} @Defop QMULRSHIFTRMOD +x{B7A9AE} @Defop QMULRSHIFTCMOD +x{B7A9A0} @Defop QMULADDRSHIFTMOD +x{B7A9A1} @Defop QMULADDRSHIFTRMOD +x{B7A9A2} @Defop QMULADDRSHIFTCMOD + +x{B7A9B4} @Defop(8u+1) QMULRSHIFT# +x{B7A9B5} @Defop(8u+1) QMULRSHIFTR# +x{B7A9B6} @Defop(8u+1) QMULRSHIFTC# +x{B7A9B8} @Defop(8u+1) QMULMODPOW2# +x{B7A9B9} @Defop(8u+1) QMULMODPOW2R# +x{B7A9BA} @Defop(8u+1) QMULMODPOW2C# +x{B7A9BC} @Defop(8u+1) QMULRSHIFT#MOD +x{B7A9BD} @Defop(8u+1) QMULRSHIFTR#MOD +x{B7A9BE} @Defop(8u+1) QMULRSHIFTC#MOD +x{B7A9B0} @Defop(8u+1) QMULADDRSHIFT#MOD +x{B7A9B1} @Defop(8u+1) QMULADDRSHIFTR#MOD +x{B7A9B2} @Defop(8u+1) QMULADDRSHIFTC#MOD + +x{B7A9C4} @Defop QLSHIFTDIV +x{B7A9C5} @Defop QLSHIFTDIVR +x{B7A9C6} @Defop QLSHIFTDIVC +x{B7A9C8} @Defop QLSHIFTMOD +x{B7A9C9} @Defop QLSHIFTMODR +x{B7A9CA} @Defop QLSHIFTMODC +x{B7A9CC} @Defop QLSHIFTDIVMOD +x{B7A9CD} @Defop QLSHIFTDIVMODR +x{B7A9CE} @Defop QLSHIFTDIVMODC +x{B7A9C0} @Defop QLSHIFTADDDIVMOD +x{B7A9C1} @Defop QLSHIFTADDDIVMODR +x{B7A9C2} @Defop QLSHIFTADDDIVMODC + +x{B7A9D4} @Defop(8u+1) QLSHIFT#DIV +x{B7A9D5} @Defop(8u+1) QLSHIFT#DIVR +x{B7A9D6} @Defop(8u+1) QLSHIFT#DIVC +x{B7A9D8} @Defop(8u+1) QLSHIFT#MOD +x{B7A9D9} @Defop(8u+1) QLSHIFT#MODR +x{B7A9DA} @Defop(8u+1) QLSHIFT#MODC +x{B7A9DC} @Defop(8u+1) QLSHIFT#DIVMOD +x{B7A9DD} @Defop(8u+1) QLSHIFT#DIVMODR +x{B7A9DE} @Defop(8u+1) QLSHIFT#DIVMODC +x{B7A9D0} @Defop(8u+1) QLSHIFT#ADDDIVMOD +x{B7A9D1} @Defop(8u+1) QLSHIFT#ADDDIVMODR +x{B7A9D2} @Defop(8u+1) QLSHIFT#ADDDIVMODC + x{B7AC} @Defop QLSHIFT x{B7AD} @Defop QRSHIFT x{B7AE} @Defop QPOW2 @@ -368,6 +576,18 @@ x{B7B5} @Defop(8u+1) QUFITS x{B7B600} @Defop QFITSX x{B7B601} @Defop QUFITSX +// advanced integer constants +{ 0 { over 1 and 0= } { 1+ swap 2/ swap } while } : pow2decomp +{ dup 8 fits { PUSHINT } { + dup pow2decomp over 1 = { nip nip PUSHPOW2 } { + over -1 = { nip nip PUSHNEGPOW2 } { + dup 20 >= { rot drop -rot PUSHINT swap LSHIFT# } { + { drop PUSHINT } { + not pow2decomp swap -1 = { nip PUSHPOW2DEC } { + drop PUSHINT + } cond } cond } cond } cond } cond } cond +} dup : PUSHINTX : INTX + // integer comparison x{B8} @Defop SGN x{B9} @Defop LESS @@ -454,10 +674,12 @@ x{CF1E} @Defop STSLICERQ x{CF1F} dup @Defop STBRQ @Defop BCONCATQ x{CF20} @Defop(ref) STREFCONST { > tuck 3 u, 3 roll s, @@ -485,6 +707,7 @@ x{CF42} @Defop STSAME } : STSLICECONST x{CF81} @Defop STZERO x{CF83} @Defop STONE + // cell deserialization (CellSlice primitives) x{D0} @Defop CTOS x{D1} @Defop ENDS @@ -554,6 +777,9 @@ x{D733} @Defop SSKIPLAST x{D734} @Defop SUBSLICE x{D736} @Defop SPLIT x{D737} @Defop SPLITQ +x{D739} @Defop XCTOS +x{D73A} @Defop XLOAD +x{D73B} @Defop XLOADQ x{D741} @Defop SCHKBITS x{D742} @Defop SCHKREFS x{D743} @Defop SCHKBITREFS @@ -585,6 +811,14 @@ x{D75F} @Defop PLDULE8Q x{D760} @Defop LDZEROES x{D761} @Defop LDONES x{D762} @Defop LDSAME +x{D764} @Defop SDEPTH +x{D765} @Defop CDEPTH +x{D766} @Defop CLEVEL +x{D767} @Defop CLEVELMASK +{ PUSHCONT IF } : }>IF x{DF} @Defop IFNOT -{ }> PUSHCONT IFNOT } : }>IFNOT ' IFNOTRET : IF: ' IFRET : IFNOT: x{E0} @Defop IFJMP -{ }> PUSHCONT IFJMP } : }>IFJMP -{ { @normal? PUSHCONT IFJMP } @doafter<{ } : IFJMP:<{ x{E1} @Defop IFNOTJMP -{ }> PUSHCONT IFNOTJMP } : }>IFNOTJMP -{ { @normal? PUSHCONT IFNOTJMP } @doafter<{ } : IFNOTJMP:<{ x{E2} @Defop IFELSE -{ `else @endblk } : }>ELSE<{ -{ `else: @endblk } : }>ELSE: -{ PUSHCONT { @normal? PUSHCONT IFELSE } @doafter<{ } : @doifelse -{ 1 { swap @normal? -rot PUSHCONT swap PUSHCONT IFELSE } does @doafter<{ } : @doifnotelse -{ - { dup `else eq? - { drop @doifelse } - { dup `else: eq? - { drop PUSHCONT IFJMP } - { @normal? PUSHCONT IF - } cond - } cond - } @doafter<{ -} : IF:<{ -{ - { dup `else eq? - { drop @doifnotelse } - { dup `else: eq? - { drop PUSHCONT IFNOTJMP } - { @normal? PUSHCONT IFNOT - } cond - } cond - } @doafter<{ -} : IFNOT:<{ + x{E300} @Defop(ref) IFREF x{E301} @Defop(ref) IFNOTREF x{E302} @Defop(ref) IFJMPREF x{E303} @Defop(ref) IFNOTJMPREF +x{E30D} @Defop(ref) IFREFELSE +x{E30E} @Defop(ref) IFELSEREF +x{E30F} @Defop(ref*2) IFREFELSEREF + +{ 16 1 @havebitrefs nip } : @refop-fits? +// b b1 [e0 e1 e2] -- b' +{ -rot dup @cont-empty? { drop swap 0 } { + 2dup @cont-fits? { rot 1 } { + over @refop-fits? { b> rot 2 } { + swap @| swap 2dup @cont-fits? { rot 1 } { + b> rot 2 + } cond } cond } cond } cond + [] execute +} : @run-cont-op +{ triple 1 ' @run-cont-op does create } : @def-cont-op +{ DROP } { PUSHCONT IF } { IFREF } @def-cont-op IF-cont +{ IFRET } { PUSHCONT IFJMP } { IFJMPREF } @def-cont-op IFJMP-cont +{ DROP } { PUSHCONT IFNOT } { IFNOTREF } @def-cont-op IFNOT-cont +{ IFNOTRET } { PUSHCONT IFNOTJMP } { IFNOTJMPREF } @def-cont-op IFNOTJMP-cont +{ dup 2over rot } : 3dup + +recursive IFELSE-cont2 { + dup @cont-empty? { drop IF-cont } { + over @cont-empty? { nip IFNOT-cont } { + 3dup @two-cont-fit? { -rot PUSHCONT swap PUSHCONT IFELSE } { + 3dup nip @cont-ref-fit? { rot swap PUSHCONT swap b> IFREFELSE } { + 3dup drop @cont-ref-fit? { -rot PUSHCONT swap b> IFELSEREF } { + rot 32 2 @havebitrefs { rot b> rot b> IFREFELSEREF } { + @| -rot IFELSE-cont2 + } cond } cond } cond } cond } cond } cond +} swap ! + +{ }> IF-cont } : }>IF +{ }> IFNOT-cont } : }>IFNOT +{ }> IFJMP-cont } : }>IFJMP +{ }> IFNOTJMP-cont } : }>IFNOTJMP +{ { @normal? IFJMP-cont } @doafter<{ } : IFJMP:<{ +{ { @normal? IFNOTJMP-cont } @doafter<{ } : IFNOTJMP:<{ +{ `else @endblk } : }>ELSE<{ +{ `else: @endblk } : }>ELSE: +{ 1 { swap @normal? swap IFELSE-cont2 } does @doafter<{ } : @doifelse +{ 1 { swap @normal? IFELSE-cont2 } does @doafter<{ } : @doifnotelse +{ + { dup `else eq? + { drop @doifelse } + { dup `else: eq? + { drop IFJMP-cont } + { @normal? IF-cont + } cond + } cond + } @doafter<{ +} : IF:<{ +{ + { dup `else eq? + { drop @doifnotelse } + { dup `else: eq? + { drop IFNOTJMP-cont } + { @normal? IFNOT-cont + } cond + } cond + } @doafter<{ +} : IFNOT:<{ + x{E304} @Defop CONDSEL x{E305} @Defop CONDSELCHK x{E308} @Defop IFRETALT x{E309} @Defop IFNOTRETALT -{ PUSHCONT REPEAT } : }>REPEAT -{ { @normal? PUSHCONT REPEAT } @doafter<{ } : REPEAT:<{ x{E5} dup @Defop REPEATEND @Defop REPEAT: x{E6} @Defop UNTIL -{ }> PUSHCONT UNTIL } : }>UNTIL -{ { @normal? PUSHCONT UNTIL } @doafter<{ } : UNTIL:<{ x{E7} dup @Defop UNTILEND @Defop UNTIL: x{E8} @Defop WHILE x{E9} @Defop WHILEEND +x{EA} @Defop AGAIN +x{EB} dup @Defop AGAINEND @Defop AGAIN: + { `do @endblk } : }>DO<{ { `do: @endblk } : }>DO: +{ }> PUSHCONT REPEAT } : }>REPEAT +{ { @normal? PUSHCONT REPEAT } @doafter<{ } : REPEAT:<{ +{ }> PUSHCONT UNTIL } : }>UNTIL +{ { @normal? PUSHCONT UNTIL } @doafter<{ } : UNTIL:<{ { PUSHCONT { @normal? PUSHCONT WHILE } @doafter<{ } : @dowhile { { dup `do eq? @@ -684,10 +958,34 @@ x{E9} @Defop WHILEEND } cond } @doafter<{ } : WHILE:<{ -x{EA} @Defop AGAIN { }> PUSHCONT AGAIN } : }>AGAIN { { @normal? PUSHCONT AGAIN } @doafter<{ } : AGAIN:<{ -x{EB} dup @Defop AGAINEND @Defop AGAIN: + +x{E314} @Defop REPEATBRK +x{E315} @Defop REPEATENDBRK +x{E316} @Defop UNTILBRK +x{E317} dup @Defop UNTILENDBRK @Defop UNTILBRK: +x{E318} @Defop WHILEBRK +x{E319} @Defop WHILEENDBRK +x{E31A} @Defop AGAINBRK +x{E31B} dup @Defop AGAINENDBRK @Defop AGAINBRK: + +{ }> PUSHCONT REPEATBRK } : }>REPEATBRK +{ { @normal? PUSHCONT REPEATBRK } @doafter<{ } : REPEATBRK:<{ +{ }> PUSHCONT UNTILBRK } : }>UNTILBRK +{ { @normal? PUSHCONT UNTILBRK } @doafter<{ } : UNTILBRK:<{ +{ PUSHCONT { @normal? PUSHCONT WHILEBRK } @doafter<{ } : @dowhile +{ + { dup `do eq? + { drop @dowhile } + { `do: eq? not abort"`}>DO<{` expected" PUSHCONT WHILEENDBRK + } cond + } @doafter<{ +} : WHILEBRK:<{ +{ }> PUSHCONT AGAINBRK } : }>AGAINBRK +{ { @normal? PUSHCONT AGAINBRK } @doafter<{ } : AGAINBRK:<{ + + // // continuation stack manipulation and continuation creation // @@ -733,6 +1031,8 @@ x{EDF6} @Defop THENRET x{EDF7} @Defop THENRETALT x{EDF8} @Defop INVERT x{EDF9} @Defop BOOLEVAL +x{EDFA} @Defop SAMEALT +x{EDFB} @Defop SAMEALTSAVE // x{EE} is BLESSARGS // // dictionary subroutine call/jump primitives @@ -757,9 +1057,15 @@ x{EDF9} @Defop BOOLEVAL } dup : PREPARE : PREPAREDICT // // inline support -{ dup sbits { @addop } { - dup srefs 1- abort"exactly one reference expected in inline" - ref@ CALLREF } cond +{ dup sbits + { @addop } + { + dup srefs // + { ref@ CALLREF } + { drop } + cond + } + cond } : INLINE // // throwing and handling exceptions @@ -965,11 +1271,17 @@ x{F4B5} @Defop SUBDICTRPGET x{F4B6} @Defop SUBDICTIRPGET x{F4B7} @Defop SUBDICTURPGET +x{F4BC} @Defop DICTIGETJMPZ +x{F4BD} @Defop DICTUGETJMPZ +x{F4BE} @Defop DICTIGETEXECZ +x{F4BF} @Defop DICTUGETEXECZ + // // blockchain-specific primitives x{F800} @Defop ACCEPT x{F801} @Defop SETGASLIMIT +x{F807} @Defop GASCONSUMED x{F80F} @Defop COMMIT x{F810} @Defop RANDU256 @@ -985,9 +1297,25 @@ x{F826} @Defop RANDSEED x{F827} @Defop BALANCE x{F828} @Defop MYADDR x{F829} @Defop CONFIGROOT +x{F82A} @Defop MYCODE +x{F82B} @Defop INCOMINGVALUE +x{F82C} @Defop STORAGEFEES +x{F82D} @Defop PREVBLOCKSINFOTUPLE +x{F82E} @Defop UNPACKEDCONFIGTUPLE +x{F82F} @Defop DUEPAYMENT x{F830} @Defop CONFIGDICT x{F832} @Defop CONFIGPARAM x{F833} @Defop CONFIGOPTPARAM +x{F83400} @Defop PREVMCBLOCKS +x{F83401} @Defop PREVKEYBLOCK +x{F835} @Defop GLOBALID +x{F836} @Defop GETGASFEE +x{F837} @Defop GETSTORAGEFEE +x{F838} @Defop GETFORWARDFEE +x{F839} @Defop GETPRECOMPILEDGAS +x{F83A} @Defop GETORIGINALFWDFEE +x{F83B} @Defop GETGASFEESIMPLE +x{F83C} @Defop GETFORWARDFEESIMPLE x{F840} @Defop GETGLOBVAR { dup 1 31 @rangechk idict! + not abort"cannot add key to procedure info dictionary" + @procinfo ! +} : @procinfo! +// ( x v1 v2 -- ) +{ not 2 pick @procinfo@ and xor swap @procinfo! } : @procinfo~! +// ( s i f -- ) +{ over @procdictkeylen fits not abort"procedure index out of range" + over swap dup @procinfo~! 2dup @proclistadd + 1 'nop does swap 0 (create) } : @declproc { 1 'nop does swap 0 (create) } : @declglobvar -{ @proccnt @ 1+ dup @proccnt ! @declproc } : @newproc +{ @proccnt @ 1+ dup @proccnt ! 1 @declproc } : @newproc { @gvarcnt @ 1+ dup @gvarcnt ! @declglobvar } : @newglobvar -{ 0 =: main @proclist null! @proccnt 0! @gvarcnt 0! +variable @oldcurrent variable @oldctx +Fift-wordlist dup @oldcurrent ! @oldctx ! +{ current@ @oldcurrent ! context@ @oldctx ! Asm definitions + @proccnt @ @proclist @ @procdict @ @procinfo @ @gvarcnt @ @parent-state @ current@ @oldcurrent @ @oldctx @ + 9 tuple @parent-state ! + hole current! + 0 =: main @proclist null! @proccnt 0! @gvarcnt 0! { bl word @newproc } : NEWPROC { bl word dup (def?) ' drop ' @newproc cond } : DECLPROC { bl word dup find { nip execute <> abort"method redefined with different id" } - { swap @declproc } + { swap 17 @declproc } cond } : DECLMETHOD { bl word @newglobvar } : DECLGLOBVAR - "main" @proclistadd - dictnew @procdict ! + "main" 0 @proclistadd + dictnew dup @procdict ! + @procinfo ! 16 0 @procinfo! } : PROGRAM{ { over sbits < { s>c } : }END> +{ }END <{ SETCP0 swap @procdictkeylen DICTPUSHCONST DICTIGETJMPZ 11 THROWARG }> } : }END> { }END> b> } : }END>c { }END>c s @@ -1127,9 +1595,58 @@ forget @proclist forget @proccnt -3 constant split_prepare -4 constant split_install +{ asm-mode 0 3 ~! } : asm-no-remove-unused +{ asm-mode 1 1 ~! } : asm-remove-unused // enabled by default +{ asm-mode 3 3 ~! } : asm-warn-remove-unused +{ asm-mode 4 4 ~! } : asm-warn-inline-mix +{ asm-mode 0 4 ~! } : asm-no-warn-inline-mix // disabled by default +{ asm-mode 8 8 ~! } : asm-warn-unused +{ asm-mode 0 8 ~! } : asm-no-warn-unused // disabled by default + // ( c -- ) add vm library for later use with runvmcode { spec } : hash>libref // ( c -- c' ) { hash hash>libref } : >libref + +{ dup "." $pos dup -1 = + { drop 0 } + { $| 1 $| nip swap (number) 1- abort"invalid version" + dup dup 0 < swap 999 > or abort"invalid version" + } + cond +} : parse-version-level + +{ + 0 swap + "." $+ + { swap 1000 * swap parse-version-level rot + swap } 3 times + "" $= not abort"invalid version" +} : parse-asm-fif-version + +{ + dup =: required-version parse-asm-fif-version + asm-fif-version parse-asm-fif-version + = 1+ { + "Required Asm.fif version: " @' required-version "; actual Asm.fif version: " asm-fif-version $+ $+ $+ abort + } if +} : require-asm-fif-version + +{ + dup =: required-version parse-asm-fif-version + asm-fif-version parse-asm-fif-version + swap + >= 1+ { + "Required Asm.fif version: " @' required-version "; actual Asm.fif version: " asm-fif-version $+ $+ $+ abort + } if +} : require-asm-fif-version>= + + +Fift definitions Asm +' <{ : <{ +' PROGRAM{ : PROGRAM{ +' asm-fif-version : asm-fif-version +' require-asm-fif-version : require-asm-fif-version +' require-asm-fif-version>= : require-asm-fif-version>= +Fift diff --git a/crypto/fift/lib/Disasm.fif b/crypto/fift/lib/Disasm.fif new file mode 100644 index 00000000..a46eb5b2 --- /dev/null +++ b/crypto/fift/lib/Disasm.fif @@ -0,0 +1,141 @@ +library TVM_Disasm +// simple TVM Disassembler +"Lists.fif" include + +variable 'disasm +{ 'disasm @ execute } : disasm // disassemble a slice +// usage: x{74B0} disasm + +variable @dismode @dismode 0! +{ rot over @ and rot xor swap ! } : andxor! +{ -2 0 @dismode andxor! } : stack-disasm // output 's1 s4 XCHG' +{ -2 1 @dismode andxor! } : std-disasm // output 'XCHG s1, s4' +{ -3 2 @dismode andxor! } : show-vm-code +{ -3 0 @dismode andxor! } : hide-vm-code +{ @dismode @ 1 and 0= } : stack-disasm? + +variable @indent @indent 0! +{ ' space @indent @ 2* times } : .indent +{ @indent 1+! } : +indent +{ @indent 1-! } : -indent + +{ " " $pos } : spc-pos +{ dup " " $pos swap "," $pos dup 0< { drop } { + over 0< { nip } { min } cond } cond +} : spc-comma-pos +{ { dup spc-pos 0= } { 1 $| nip } while } : -leading +{ -leading -trailing dup spc-pos dup 0< { + drop dup $len { atom single } { drop nil } cond } { + $| swap atom swap -leading 2 { over spc-comma-pos dup 0>= } { + swap 1+ -rot $| 1 $| nip -leading rot + } while drop tuple + } cond +} : parse-op +{ dup "s-1" $= { drop "s(-1)" true } { + dup "s-2" $= { drop "s(-2)" true } { + dup 1 $| swap "x" $= { nip "x{" swap $+ +"}" true } { + 2drop false } cond } cond } cond +} : adj-op-arg +{ over count over <= { drop } { 2dup [] adj-op-arg { swap []= } { drop } cond } cond } : adj-arg[] +{ 1 adj-arg[] 2 adj-arg[] 3 adj-arg[] + dup first + dup `XCHG eq? { + drop dup count 2 = { tpop swap "s0" , swap , } if } { + dup `LSHIFT eq? { + drop dup count 2 = stack-disasm? and { second `LSHIFT# swap pair } if } { + dup `RSHIFT eq? { + drop dup count 2 = stack-disasm? and { second `RSHIFT# swap pair } if } { + drop + } cond } cond } cond +} : adjust-op + +variable @cp @cp 0! +variable @curop +variable @contX variable @contY variable @cdict + +{ atom>$ type } : .atom +{ dup first .atom dup count 1 > { space 0 over count 2- { 1+ 2dup [] type .", " } swap times 1+ [] type } { drop } cond } : std-show-op +{ 0 over count 1- { 1+ 2dup [] type space } swap times drop first .atom } : stk-show-op +{ @dismode @ 2 and { .indent ."// " @curop @ csr. } if } : .curop? +{ .curop? .indent @dismode @ 1 and ' std-show-op ' stk-show-op cond cr +} : show-simple-op +{ dup 4 u@ 9 = { 8 u@+ swap 15 and 3 << s@ } { + dup 7 u@ 0x47 = { 7 u@+ nip 2 u@+ 7 u@+ -rot 3 << swap sr@ } { + dup 8 u@ 0x8A = { ref@ " cr } : show-cont-op +{ swap scont-swap ":<{" show-cont-bodyx scont-swap + "" show-cont-bodyx .indent ."}>" cr } : show-cont2-op + +{ @contX @ null? { "CONT" show-cont-op } ifnot +} : flush-contX +{ @contY @ null? { scont-swap "CONT" show-cont-op scont-swap } ifnot +} : flush-contY +{ flush-contY flush-contX } : flush-cont +{ @contX @ null? not } : have-cont? +{ @contY @ null? not } : have-cont2? +{ flush-contY @contY ! scont-swap } : save-cont-body + +{ @cdict ! } : save-const-dict +{ @cdict null! } : flush-dict +{ @cdict @ null? not } : have-dict? + +{ flush-cont .indent type .":<{" cr + @curop @ ref@ " cr +} : show-ref-op +{ flush-contY .indent rot type .":<{" cr + @curop @ ref@ " cr +} : show-cont-ref-op +{ flush-cont .indent swap type .":<{" cr + @curop @ ref@+ " cr +} : show-ref2-op + +{ flush-cont first atom>$ dup 5 $| drop "DICTI" $= swap + .indent type ." {" cr +indent @cdict @ @cdict null! unpair + rot { + swap .indent . ."=> <{" cr +indent disasm -indent .indent ."}>" cr true + } swap ' idictforeach ' dictforeach cond drop + -indent .indent ."}" cr +} : show-const-dict-op + +( `PUSHCONT `PUSHREFCONT ) constant @PushContL +( `REPEAT `UNTIL `IF `IFNOT `IFJMP `IFNOTJMP ) constant @CmdC1 +( `IFREF `IFNOTREF `IFJMPREF `IFNOTJMPREF `CALLREF `JMPREF ) constant @CmdR1 +( `DICTIGETJMP `DICTIGETJMPZ `DICTUGETJMP `DICTUGETJMPZ `DICTIGETEXEC `DICTUGETEXEC ) constant @JmpDictL +{ dup first `DICTPUSHCONST eq? { + flush-cont @curop @ get-const-dict save-const-dict show-simple-op } { + dup first @JmpDictL list-member? have-dict? and { + flush-cont show-const-dict-op } { + flush-dict + dup first @PushContL list-member? { + drop @curop @ get-cont-body save-cont-body } { + dup first @CmdC1 list-member? have-cont? and { + flush-contY first atom>$ .curop? show-cont-op } { + dup first @CmdR1 list-member? { + flush-cont first atom>$ dup $len 3 - $| drop .curop? show-ref-op } { + dup first `WHILE eq? have-cont2? and { + drop "WHILE" "}>DO<{" .curop? show-cont2-op } { + dup first `IFELSE eq? have-cont2? and { + drop "IF" "}>ELSE<{" .curop? show-cont2-op } { + dup first dup `IFREFELSE eq? swap `IFELSEREF eq? or have-cont? and { + first `IFREFELSE eq? "IF" "}>ELSE<{" rot .curop? show-cont-ref-op } { + dup first `IFREFELSEREF eq? { + drop "IF" "}>ELSE<{" .curop? show-ref2-op } { + flush-cont show-simple-op + } cond } cond } cond } cond } cond } cond } cond } cond } cond +} : show-op +{ dup @cp @ (vmoplen) dup 0> { 65536 /mod swap sr@+ swap dup @cp @ (vmopdump) parse-op swap s> true } { drop false } cond } : fetch-one-op +{ { fetch-one-op } { swap @curop ! adjust-op show-op } while } : disasm-slice +{ { disasm-slice dup sbitrefs 1- or 0= } { ref@ = -rot <= and } : s-fits? +// b s x -- ? +{ swap sbitrefs -rot + rot brembitrefs -rot <= -rot <= and } : s-fits-with? { 0 swap ! } : 0! { tuck @ + swap ! } : +! { tuck @ swap - swap ! } : -! { 1 swap +! } : 1+! { -1 swap +! } : 1-! { null swap ! } : null! +{ not 2 pick @ and xor swap ! } : ~! 0 tuple constant nil { 1 tuple } : single { 2 tuple } : pair @@ -108,6 +113,8 @@ variable base { cdr cdr } : cddr { cdr cdr car } : caddr { null ' cons rot times } : list +{ -rot pair swap ! } : 2! +{ @ unpair } : 2@ { true (atom) drop } : atom { bl word atom 1 'nop } ::_ ` { hole dup 1 { @ execute } does create } : recursive @@ -118,13 +125,18 @@ variable base { 0 word -trailing scan-until-word 1 'nop } ::_ $<< { 0x40 runvmx } : runvmcode { 0x48 runvmx } : gasrunvmcode +{ 0xc8 runvmx } : gas2runvmcode { 0x43 runvmx } : runvmdict { 0x4b runvmx } : gasrunvmdict +{ 0xcb runvmx } : gas2runvmdict { 0x45 runvmx } : runvm { 0x4d runvmx } : gasrunvm +{ 0xcd runvmx } : gas2runvm { 0x55 runvmx } : runvmctx { 0x5d runvmx } : gasrunvmctx +{ 0xdd runvmx } : gas2runvmctx { 0x75 runvmx } : runvmctxact { 0x7d runvmx } : gasrunvmctxact +{ 0xfd runvmx } : gas2runvmctxact { 0x35 runvmx } : runvmctxactq { 0x3d runvmx } : gasrunvmctxactq diff --git a/crypto/fift/lib/FiftExt.fif b/crypto/fift/lib/FiftExt.fif new file mode 100644 index 00000000..6ed677d7 --- /dev/null +++ b/crypto/fift/lib/FiftExt.fif @@ -0,0 +1,118 @@ +{ ?dup { 1+ { execute } { 0 swap } cond } + { (number) ?dup 0= abort"-?" 'nop } cond +} : (interpret-prepare) +{ { include-depth 0= (seekeof?) not } { + (word-prefix-find) (interpret-prepare) (execute) + } while +} : interpret +{ ({) + { 0 (seekeof?) abort"no }" (word-prefix-find) (interpret-prepare) (compile) over atom? not } until + (}) swap execute +} : begin-block +{ swap 0 'nop } : end-block +{ { 1 'nop } `{ begin-block } +{ { swap `{ eq? not abort"} without {" swap execute } end-block } +:: } :: { + +// if{ ... }then{ ... }elseif{ ... }then{ ... }else{ ... } +{ eq? not abort"unexpected" } : ?pairs +{ dup `if eq? swap `ifnot eq? over or not abort"without if{" } : if-ifnot? +// cond then ? -- exec +{ { ' if } { ' ifnot } cond rot ({) 0 rot (compile) -rot 1 swap (compile) (}) +} : (make-if) +// cond then else -- exec +{ rot ({) 0 rot (compile) -rot 2 ' cond (compile) (}) +} : (make-cond) +{ `noelse `if begin-block } :: if{ +{ `noelse `ifnot begin-block } :: ifnot{ +{ 1 ' end-block does } : end-block-does +{ { over `else eq? } { + nip rot if-ifnot? ' swap ifnot (make-cond) + } while + swap `noelse ?pairs 0 swap +} : finish-else-chain +{ swap dup if-ifnot? drop `then { + swap `then ?pairs + swap if-ifnot? (make-if) finish-else-chain + } `{ begin-block +} end-block-does :: }then{ +{ swap `{ ?pairs nip + swap `then eq? not abort"without }then{" `else +} : ?else-ok +{ ?else-ok { finish-else-chain } `{ begin-block } end-block-does :: }else{ +{ ?else-ok `if begin-block } end-block-does :: }elseif{ +{ ?else-ok `ifnot begin-block } end-block-does :: }elseifnot{ + +// while{ ... }do{ ... } +{ 2 ' while does } : (make-while) +{ `while begin-block } :: while{ +{ swap `while eq? not abort"without while{" `while-do { + swap `while-do ?pairs (make-while) 0 swap + } `{ begin-block +} end-block-does :: }do{ + +// repeat{ ... }until{ ... } +{ swap ({) 0 rot (compile) 0 rot (compile) (}) 1 ' until does } : (make-until) +{ `repeat begin-block } :: repeat{ +{ swap `repeat eq? not abort"without repeat{" `until { + swap `until ?pairs (make-until) 0 swap + } `{ begin-block +} end-block-does :: }until{ + +// def { ... } instead of { ... } : +{ bl word swap bl word "{" $cmp abort"{ expected" `def { + swap `def ?pairs -rot 3 ' (create) + } `{ begin-block +} : (def) +{ 0 (def) } :: def +{ 1 (def) } :: def:: + +// defrec { ... } instead of recursive { ... } swap ! +{ recursive bl word "{" $cmp abort"{ expected" `defrec { + swap `defrec ?pairs swap ! 0 'nop + } `{ begin-block +} :: defrec + +def .sgn { + if{ ?dup 0= }then{ + ."zero" + }elseif{ 0> }then{ + ."positive" + }else{ + ."negative" + } + cr +} +// equivalent to: { ?dup 0= { ."zero" } { 0> { ."positive" } { ."negative" } cond } cond cr } : .sgn + +defrec fact { + if{ dup }then{ + dup 1- fact * + }else{ + drop 1 + } +} +// equivalent to: recursive fact { dup { dup 1- fact * } { drop 1 } cond } swap ! + +// [[ ... ]] computes arbitrary constants inside definitions +// { [[ 5 dup * ]] + } : add25 +// is equivalent to +// { 25 + } : add25 +{ "without [[" abort } box constant ']] +{ ']] @ execute } : ]] +{ { ']] @ 2 { ']] ! call/cc } does ']] ! + interpret 'nop ']] ! "]] not found" abort + } call/cc + drop 1 'nop +} :: [[ + +{ { over @ swap 2 { call/cc } does swap ! + interpret "literal to eof" abort + } call/cc + drop execute 1 'nop +} : interpret-literal-to +// use next line only if Lists.fif is loaded (or move it to Lists.fif if FiftExt.fif becomes part of Fift.fif) +// { ( ') interpret-literal-to } :: '( +// then you can use list literals '( a b c ... ) inside definitions: +// { '( 1 2 3 ) } : test +// { '( ( `a { ."A" } ) ( `b { ."B" } ) ) assoc { cadr execute } { ."???" } cond } : test2 diff --git a/crypto/fift/lib/Lists.fif b/crypto/fift/lib/Lists.fif index 3d50ae0f..b59e40a0 100644 --- a/crypto/fift/lib/Lists.fif +++ b/crypto/fift/lib/Lists.fif @@ -111,6 +111,42 @@ recursive list-map { swap uncons -rot over execute -rot list-map cons } cond } swap ! + +variable ctxdump variable curctx +// (a1 .. an) e -- executes e for a1, ..., an +{ ctxdump @ curctx @ ctxdump 2! curctx 2! + { curctx 2@ over null? not } { swap uncons rot tuck curctx 2! execute } + while 2drop ctxdump 2@ curctx ! ctxdump ! +} : list-foreach +forget ctxdump forget curctx + +// +// Experimental implementation of `for` loops with index +// +variable loopdump variable curloop +{ curloop @ loopdump @ loopdump 2! } : push-loop-ctx +{ loopdump 2@ loopdump ! curloop ! } : pop-loop-ctx +// ilast i0 e -- executes e for i=i0,i0+1,...,ilast-1 +{ -rot 2dup > { + push-loop-ctx { + triple dup curloop ! first execute curloop @ untriple 1+ 2dup <= + } until pop-loop-ctx + } if 2drop drop +} : for +// ilast i0 e -- same as 'for', but pushes current index i before executing e +{ -rot 2dup > { + push-loop-ctx { + triple dup curloop ! untriple nip swap execute curloop @ untriple 1+ 2dup <= + } until pop-loop-ctx + } if 2drop drop +} : for-i +// ( -- i ) Returns innermost loop index +{ curloop @ third } : i +// ( -- j ) Returns outer loop index +{ loopdump @ car third } : j +{ loopdump @ cadr third } : k +forget curloop forget loopdump + // // create Lisp-style lists using words "(" and ")" // diff --git a/crypto/fift/lib/TonUtil.fif b/crypto/fift/lib/TonUtil.fif index 71624798..f31b591e 100644 --- a/crypto/fift/lib/TonUtil.fif +++ b/crypto/fift/lib/TonUtil.fif @@ -34,8 +34,11 @@ library TonUtil // TON Blockchain Fift Library 1 and 0= } : parse-smc-addr +// ( x -- ) Displays a 64-digit hex number +{ 64 0x. } : 64x. +{ 64 0X. } : 64X. // ( wc addr -- ) Show address in : form -{ swap ._ .":" 64 0x. } : .addr +{ swap ._ .":" 64x. } : .addr // ( wc addr flags -- ) Show address in base64url form { smca>$ type } : .Addr // ( wc addr fname -- ) Save address to file in 36-byte format @@ -67,20 +70,25 @@ library TonUtil // TON Blockchain Fift Library // ( b wc addr -- b' ) Serializes address into Builder b { -rot 8 i, swap 256 u, } : addr, +{ over 8 fits { rot b{100} s, -rot addr, } { + rot b{110} s, 256 9 u, rot 32 i, swap 256 u, } cond +} : Addr, // Gram utilities 1000000000 constant Gram { Gram swap */r } : Gram*/ { Gram * } : Gram* +{ (number) dup { 1- ' Gram*/ ' Gram* cond true } if +} : $>GR? // ( S -- nanograms ) -{ (number) ?dup 0= abort"not a valid Gram amount" -1- ' Gram*/ ' Gram* cond +{ $>GR? not abort"not a valid Gram amount" } : $>GR { bl word $>GR 1 'nop } ::_ GR$ // ( nanograms -- S ) { dup abs <# ' # 9 times char . hold #s rot sign #> nip -trailing0 } : (.GR) -{ (.GR) ."GR$" type space } : .GR +{ (.GR) ."GR$" type } : .GR_ +{ .GR_ space } : .GR // b x -- b' ( serializes a Gram amount ) { -1 { 1+ 2dup 8 * ufits } until @@ -100,19 +108,66 @@ nip -trailing0 } : (.GR) ' VarUInt32, : val, ' VarUInt32@ : val@ // d k v -- d' -{ cc -{ dup null? { ."(null) " drop } { val@ . } cond } dup : .maybeVarUInt32 : .val -{ cc-key-bits { swap 32 1<< rmod . ."-> " .val ."; " true } dictforeach drop cr } : .cc +{ dup null? { ."(null)" drop } { val@ ._ } cond } dup : .maybeVarUInt32 : .val +{ swap cc-key-bits { rot { ."+" } if .val ."*$" ._ true true } idictforeach drop } : (.cc) +{ false (.cc) { ."0" } ifnot } : .cc_ +{ .cc_ space } : .cc +{ true (.cc) drop } : .+cc_ +{ .+cc_ space } : .+cc { cc-key-bits { rot . ."-> " swap .val .val ."; " true } dictdiff drop cr } : show-cc-diff { cc-key-bits { val@ swap val@ + val, true } dictmerge } : cc+ -{ null swap cc-key-bits { val@ pair swap cons true } dictforeach drop } : cc>list-rev +{ null swap cc-key-bits { val@ pair swap cons true } idictforeach drop } : cc>list-rev { cc>list-rev list-reverse } : cc>list forget val, forget val@ forget .val +// ( S -- x -1 or 0 ) +{ (number) dup 2 = { -rot 2drop } if 1 = } : int? +{ int? dup { drop dup 0< { drop false } { true } cond } if } : pos-int? +// ( S -- k v -1 or 0 ) Parses expression * or *$ +{ dup "*" $pos dup 0< { 2drop false } { + $| dup $len 2 < { 2drop false } { + 1 $| nip dup 1 $| swap "$" $= { swap } if drop + int? dup { over 32 fits { 2drop false } ifnot } if + not { drop false } { + swap pos-int? not { drop false } { + true + } cond } cond } cond } cond +} : cc-key-value? +// ( S -- D -1 or 0 ) Parses an extra currency collection +// e.g. "10000*$3+7777*$-11" means "10000 units of currency #3 and 7777 units of currency #-11" +{ dictnew { // S D + swap dup "+" $pos dup 0< { drop null -rot } { $| 1 $| nip -rot } cond + cc-key-value? { +ccpair over null? dup { rot drop true } if } { 2drop false true } cond + } until +} : $>xcc? +{ $>xcc? not abort"invalid extra currency collection" } : $>xcc +{ char } word dup $len { $>xcc } { drop dictnew } cond 1 'nop } ::_ CX{ + +// complete currency collections +{ $>xcc? { true } { drop false } cond } : end-parse-cc +// ( S -- x D -1 or 0 ) Parses a currency collection +// e.g. "1.2+300*$2" means "1200000000ng plus 300 units of currency #2" +{ 0 swap dup "+" $pos dup 0< { drop dup + $>GR? { nip nip dictnew true } { end-parse-cc } cond + } { over swap $| swap $>GR? { 2swap 2drop swap 1 $| nip } { drop + } cond end-parse-cc } cond +} : $>cc? +{ $>cc? not abort"invalid currency collection" } : $>cc +{ char } word dup $len { $>cc } { drop 0 dictnew } cond 2 'nop } ::_ CC{ +// ( x D -- ) +{ swap ?dup { .GR_ .+cc_ } { .cc_ } cond } : .GR+cc_ +{ .GR+cc_ space } : .GR+cc +{ -rot Gram, swap dict, } : Gram+cc, + // Libraries // ( -- D ) New empty library collection ' dictnew : Libs{ @@ -150,3 +205,177 @@ recursive append-long-bytes { // ( x -- S ) serialize public key { 256 u>B B{3ee6} swap B+ dup crc16 16 u>B B+ B>base64 } : pubkey>$ { pubkey>$ type } : .pubkey + +// ( S -- x ) parse validator-encoded public key +{ base64>B dup Blen 36 <> abort"public key with magic must be 36 bytes long" + 4 B| swap 32 B>u@ 0xC6B41348 <> abort"unknown magic for public key (not Ed25519)" +} : parse-val-pubkey +{ bl word parse-val-pubkey 1 'nop } ::_ VPK' +{ char } word base64>B 1 'nop } ::_ B64{ + +// adnl address parser +{ 256 u>B B{2D} swap B+ dup crc16 16 u>B B+ } : adnl-preconv +{ swap 32 /mod dup 26 < { 65 } { 24 } cond + rot swap hold } : Base32# +{ <# ' Base32# 8 times #> } : Base32#*8 +{ "" over Blen 5 / { swap 40 B>u@+ Base32#*8 nip rot swap $+ } swap times nip } : B>Base32 + +// ( x -- S ) Converts an adnl-address from a 256-bit integer to a string +{ adnl-preconv B>Base32 1 $| nip } : adnl>$ + +{ 65 - dup 0>= { -33 and dup 26 < } { 41 + dup 25 > over 32 < and } cond ?dup nip } : Base32-digit? +{ Base32-digit? not abort"not a Base32 digit" } : Base32-digit +{ 0 { over $len } { swap 1 $| -rot (char) Base32-digit swap 5 << + } while nip } : Base32-number +{ B{} { over $len } { swap 8 $| -rot Base32-number 40 u>B B+ } while nip } : Base32>B + +// ( S -- x ) Converts an adnl address from a string to 256-bit integer +{ dup $len 55 <> abort"not 55 alphanumeric characters" "F" swap $+ Base32>B + 33 B| 16 B>u@ over crc16 <> abort"crc16 checksum mismatch" + 8 B>u@+ 0x2D <> abort"not a valid adnl address" 256 B>u@ } : $>adnl + +{ 65 - dup 0>= { -33 and 10 + dup 16 < } { 17 + dup 0>= over 10 < and } cond ?dup nip } : hex-digit? +// ( S -- x -1 or 0 ) Parses a hexadecimal integer +{ dup $len { + 0 { + 4 << swap 1 $| -rot (char) hex-digit? // S a d -1 or S a 0 + { + over $len 0= } { drop -1 true } cond + } until + dup 0< { 2drop false } { nip true } cond + } { drop false } cond +} : hex$>u? +// ( S -- x ) +{ hex$>u? not abort"not a hexadecimal number" } : hex$>u + +{ dup $len 64 = { hex$>u } { + dup $len 55 = { $>adnl } { + true abort"invalid adnl address" + } cond } cond +} : parse-adnl-addr +{ adnl>$ type } : .adnl +{ bl word parse-adnl-addr 1 'nop } ::_ adnl: + +// ( x a b -- a<=x<=b ) +{ 2 pick >= -rot >= and } : in-range? + +// ( c i -- ? ) Checks whether c is a valid value for config param #i +def? config-valid? { + { nip 0>= { ."warning: cannot check validity of configuration parameter value, use create-state instead of fift to check validity" cr } if + true } : config-valid? +} ifnot + +{ dup -1000 = { drop + { + // anycast_info$_ depth:(#<= 30) { depth >= 1 } + // rewrite_pfx:(bits depth) = Anycast; + 30 u@+ swap // get depth + + dup 1 > { + dup 2 roll swap u@+ // get rewrite_pfx + // return depth, rewrite_pfx, slice + } + { + drop // drop depth (<=1) + 0 0 2 roll // set anycast to none + } cond + } + { + 0 0 2 roll // set anycast to none + } cond +} : maybe-anycast + +// Rewrite first bits of addr with anycast info +{ // input: anycast depth, rewrite_pfx, workchain, slice, address length + 4 -roll + 3 roll dup dup 0 = { 2drop 2 roll drop } + { + rot swap u@+ swap drop + 3 roll + // Get addr: addr_none$00 / addr_extern$01 / addr_std$10 / addr_var$11 + { // if greater that zero + dup 1 > + { + 2 = + { + // if addr_std$10 + // anycast:(Maybe Anycast) + // workchain_id:int8 + // address:bits256 = MsgAddressInt; + maybe-anycast // get anycast depth, bits, slice + 8 i@+ // get workchain + 256 parse-address-with-anycast + `addr-std swap + } + + { + // if addr_var$11 + // anycast:(Maybe Anycast) + // addr_len:(## 9) + // workchain_id:int32 + // address:(bits addr_len) = MsgAddressInt; + maybe-anycast // get anycast depth, bits, slice + 9 u@+ // get addr_len + 32 i@+ // get workchain + swap 2 -roll // move workchain to neede position + swap parse-address-with-anycast + `addr-var swap + } cond + + } + { + drop // drop header (dup for statment upper) + // if addr_extern$01 + // addr_extern$01 len:(## 9) + // external_address:(bits len) + 9 u@+ swap // bit len + u@+ // external_address + `addr-extern swap + } cond + } + { + swap + // if addr_none$00 + `addr-none swap + } cond +} : addr@+ + +{ addr@+ drop } : addr@ + +// User-friendly prints output of addr@ +// (0 A or addr A or wc addr A -- ) +{ + dup `addr-none eq? + { 2drop ."addr_none" } + { + `addr-extern eq? + { (dump) type } + { (x.) swap (dump) ":" $+ swap $+ type } + cond + } + cond +} : print-addr // print addr with workchain + +forget maybe-anycast +forget parse-address-with-anycast diff --git a/crypto/fift/utils.cpp b/crypto/fift/utils.cpp index 3ed11c31..68fc18c0 100644 --- a/crypto/fift/utils.cpp +++ b/crypto/fift/utils.cpp @@ -14,12 +14,13 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "utils.h" #include "words.h" #include "td/utils/PathView.h" #include "td/utils/filesystem.h" +#include "td/utils/misc.h" #include "td/utils/port/path.h" namespace fift { @@ -28,6 +29,12 @@ namespace { std::string fift_dir(std::string dir) { return dir.size() > 0 ? dir : td::PathView(td::realpath(__FILE__).move_as_ok()).parent_dir().str() + "lib/"; } +std::string smartcont_dir(std::string dir) { + return dir.size() > 0 + ? dir + : td::PathView(td::PathView(td::realpath(__FILE__).move_as_ok()).parent_dir_noslash()).parent_dir().str() + + "smartcont/"; +} td::Result load_source(std::string name, std::string dir = "") { return td::read_file_str(fift_dir(dir) + name); } @@ -49,6 +56,15 @@ td::Result load_Lisp_fif(std::string dir = "") { td::Result load_GetOpt_fif(std::string dir = "") { return load_source("GetOpt.fif", dir); } +td::Result load_wallet3_code_fif(std::string dir = "") { + return td::read_file_str(smartcont_dir(dir) + "wallet-v3-code.fif"); +} +td::Result load_FiftExt_fif(std::string dir = "") { + return load_source("FiftExt.fif", dir); +} +td::Result load_Disasm_fif(std::string dir = "") { + return load_source("Disasm.fif", dir); +} class MemoryFileLoader : public fift::FileLoader { public: @@ -98,7 +114,8 @@ class MemoryFileLoader : public fift::FileLoader { td::Result create_source_lookup(std::string main, bool need_preamble = true, bool need_asm = true, bool need_ton_util = true, bool need_lisp = true, - std::string dir = "") { + bool need_w3_code = true, bool need_fift_ext = true, + bool need_disasm = true, std::string dir = "") { auto loader = std::make_unique(); loader->add_file("/main.fif", std::move(main)); if (need_preamble) { @@ -127,6 +144,18 @@ td::Result create_source_lookup(std::string main, bool need_ TRY_RESULT(f, load_Lisp_fif(dir)); loader->add_file("/Lisp.fif", std::move(f)); } + if (need_w3_code) { + TRY_RESULT(f, load_wallet3_code_fif(dir)); + loader->add_file("/wallet-v3-code.fif", std::move(f)); + } + if (need_fift_ext) { + TRY_RESULT(f, load_FiftExt_fif(dir)); + loader->add_file("/FiftExt.fif", std::move(f)); + } + if (need_disasm) { + TRY_RESULT(f, load_Disasm_fif(dir)); + loader->add_file("/Disasm.fif", std::move(f)); + } auto res = fift::SourceLookup(std::move(loader)); res.add_include_path("/"); return std::move(res); @@ -158,7 +187,7 @@ td::Result run_fift(fift::SourceLookup source_lookup, std::o } // namespace td::Result mem_run_fift(std::string source, std::vector args, std::string fift_dir) { std::stringstream ss; - TRY_RESULT(source_lookup, create_source_lookup(source, true, true, true, true, fift_dir)); + TRY_RESULT(source_lookup, create_source_lookup(source, true, true, true, true, true, true, true, fift_dir)); TRY_RESULT_ASSIGN(source_lookup, run_fift(std::move(source_lookup), &ss, true, std::move(args))); FiftOutput res; res.source_lookup = std::move(source_lookup); @@ -174,8 +203,10 @@ td::Result mem_run_fift(SourceLookup source_lookup, std::vector create_mem_source_lookup(std::string main, std::string fift_dir, bool need_preamble, - bool need_asm, bool need_ton_util, bool need_lisp) { - return create_source_lookup(main, need_preamble, need_asm, need_ton_util, need_lisp, fift_dir); + bool need_asm, bool need_ton_util, bool need_lisp, + bool need_w3_code) { + return create_source_lookup(main, need_preamble, need_asm, need_ton_util, need_lisp, need_w3_code, false, false, + fift_dir); } td::Result> compile_asm(td::Slice asm_code, std::string fift_dir, bool is_raw) { @@ -183,7 +214,7 @@ td::Result> compile_asm(td::Slice asm_code, std::string fift_d TRY_RESULT(source_lookup, create_source_lookup(PSTRING() << "\"Asm.fif\" include\n " << (is_raw ? "<{" : "") << asm_code << "\n" << (is_raw ? "}>c" : "") << " boc>B \"res\" B>file", - true, true, true, false, fift_dir)); + true, true, true, false, false, false, false, fift_dir)); TRY_RESULT(res, run_fift(std::move(source_lookup), &ss)); TRY_RESULT(boc, res.read_file("res")); return vm::std_boc_deserialize(std::move(boc.data)); diff --git a/crypto/fift/utils.h b/crypto/fift/utils.h index 0dcc629f..dd434fe0 100644 --- a/crypto/fift/utils.h +++ b/crypto/fift/utils.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -28,7 +28,8 @@ struct FiftOutput { }; td::Result create_mem_source_lookup(std::string main, std::string fift_dir = "", bool need_preamble = true, bool need_asm = true, - bool need_ton_util = true, bool need_lisp = true); + bool need_ton_util = true, bool need_lisp = true, + bool need_w3_code = true); td::Result mem_run_fift(std::string source, std::vector args = {}, std::string fift_dir = ""); td::Result mem_run_fift(SourceLookup source_lookup, std::vector args); td::Result> compile_asm(td::Slice asm_code, std::string fift_dir = "", bool is_raw = true); diff --git a/crypto/fift/words.cpp b/crypto/fift/words.cpp index 1fc12a2c..8d652afc 100644 --- a/crypto/fift/words.cpp +++ b/crypto/fift/words.cpp @@ -14,13 +14,14 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "words.h" #include "Dictionary.h" #include "IntCtx.h" #include "SourceLookup.h" +#include "HashMap.h" #include "common/refcnt.hpp" #include "common/bigint.hpp" @@ -28,11 +29,13 @@ #include "common/bitstring.h" #include "common/util.h" +#include "openssl/digest.hpp" + #include "Ed25519.h" #include "vm/cells.h" #include "vm/cellslice.h" -#include "vm/continuation.h" +#include "vm/vm.h" #include "vm/cp0.h" #include "vm/dict.h" #include "vm/boc.h" @@ -40,7 +43,10 @@ #include "vm/box.hpp" #include "vm/atom.h" +#include "vm/db/TonDb.h" // only for interpret_db_run_vm{,_parallel} + #include "block/block.h" +#include "common/global-version.h" #include "td/utils/filesystem.h" #include "td/utils/misc.h" @@ -56,12 +62,31 @@ namespace fift { -void show_total_cells(std::ostream& stream) { - stream << "total cells = " << vm::DataCell::get_total_data_cells() << std::endl; +const Ref nop_word_def = Ref{true}; + +// +// functions for wordef +// +Ref pop_exec_token(vm::Stack& stack) { + auto wd_ref = stack.pop_chk().as_object(); + if (wd_ref.is_null()) { + throw IntError{"execution token expected"}; + } + return wd_ref; } -void do_compile(vm::Stack& stack, Ref word_def); -void do_compile_literals(vm::Stack& stack, int count); +Ref pop_word_list(vm::Stack& stack) { + auto wl_ref = stack.pop_chk().as_object(); + if (wl_ref.is_null()) { + throw IntError{"word list expected"}; + } + return wl_ref; +} + +void push_argcount(vm::Stack& stack, int args) { + stack.push_smallint(args); + stack.push_object(nop_word_def); +} void interpret_dot(IntCtx& ctx, bool space_after) { *ctx.output_stream << dec_string2(ctx.stack.pop_int()) << (space_after ? " " : ""); @@ -119,7 +144,7 @@ void interpret_print_list(IntCtx& ctx) { } void interpret_dottc(IntCtx& ctx) { - show_total_cells(*ctx.output_stream); + *ctx.output_stream << "total cells = " << vm::DataCell::get_total_data_cells() << std::endl; } void interpret_dot_internal(vm::Stack& stack) { @@ -177,59 +202,31 @@ void interpret_divmod(vm::Stack& stack, int round_mode) { } void interpret_times_div(vm::Stack& stack, int round_mode) { - auto z = stack.pop_int(); - auto y = stack.pop_int(); - auto x = stack.pop_int(); - td::BigIntG<257 * 2> tmp{0}; - tmp.add_mul(*x, *y); - auto q = td::RefInt256{true}; - tmp.mod_div(*z, q.unique_write(), round_mode); - q.unique_write().normalize(); - stack.push_int(std::move(q)); + auto z = stack.pop_int(), y = stack.pop_int(), x = stack.pop_int(); + stack.push_int(muldiv(std::move(x), std::move(y), std::move(z), round_mode)); } void interpret_times_divmod(vm::Stack& stack, int round_mode) { - auto z = stack.pop_int(); - auto y = stack.pop_int(); - auto x = stack.pop_int(); - td::BigIntG<257 * 2> tmp{0}; - tmp.add_mul(*x, *y); - auto q = td::RefInt256{true}; - tmp.mod_div(*z, q.unique_write(), round_mode); - q.unique_write().normalize(); - auto r = td::RefInt256{true, tmp}; - stack.push_int(std::move(q)); - stack.push_int(std::move(r)); + auto z = stack.pop_int(), y = stack.pop_int(), x = stack.pop_int(); + auto dm = muldivmod(std::move(x), std::move(y), std::move(z)); + stack.push_int(std::move(dm.first)); + stack.push_int(std::move(dm.second)); } void interpret_times_mod(vm::Stack& stack, int round_mode) { auto z = stack.pop_int(); auto y = stack.pop_int(); auto x = stack.pop_int(); - td::BigIntG<257 * 2> tmp{0}; + typename td::BigInt256::DoubleInt tmp{0}, q; tmp.add_mul(*x, *y); - td::BigIntG<257 * 2> q; tmp.mod_div(*z, q, round_mode); - auto r = td::RefInt256{true, tmp}; - stack.push_int(std::move(r)); + stack.push_int(td::make_refint(tmp)); } void interpret_negate(vm::Stack& stack) { stack.push_int(-stack.pop_int()); } -void interpret_const(vm::Stack& stack, long long val) { - stack.push_smallint(val); -} - -void interpret_big_const(vm::Stack& stack, td::RefInt256 val) { - stack.push_int(std::move(val)); -} - -void interpret_literal(vm::Stack& stack, vm::StackEntry se) { - stack.push(std::move(se)); -} - void interpret_cmp(vm::Stack& stack, const char opt[3]) { auto y = stack.pop_int(); auto x = stack.pop_int(); @@ -239,7 +236,7 @@ void interpret_cmp(vm::Stack& stack, const char opt[3]) { } void interpret_sgn(vm::Stack& stack, const char opt[3]) { - auto x = stack.pop_int(); + auto x = stack.pop_int_finite(); int r = x->sgn(); assert((unsigned)(r + 1) <= 2); stack.push_smallint(((const signed char*)opt)[r + 1]); @@ -253,21 +250,21 @@ void interpret_fits(vm::Stack& stack, bool sgnd) { void interpret_pow2(vm::Stack& stack) { int x = stack.pop_smallint_range(255); - auto r = td::RefInt256{true}; + auto r = td::make_refint(); r.unique_write().set_pow2(x); stack.push_int(r); } void interpret_neg_pow2(vm::Stack& stack) { int x = stack.pop_smallint_range(256); - auto r = td::RefInt256{true}; + auto r = td::make_refint(); r.unique_write().set_pow2(x).negate().normalize(); stack.push_int(r); } void interpret_pow2_minus1(vm::Stack& stack) { int x = stack.pop_smallint_range(256); - auto r = td::RefInt256{true}; + auto r = td::make_refint(); r.unique_write().set_pow2(x).add_tiny(-1).normalize(); stack.push_int(r); } @@ -301,19 +298,18 @@ void interpret_times_rshift(vm::Stack& stack, int round_mode) { int z = stack.pop_smallint_range(256); auto y = stack.pop_int(); auto x = stack.pop_int(); - td::BigIntG<257 * 2> tmp{0}; + typename td::BigInt256::DoubleInt tmp{0}; tmp.add_mul(*x, *y).rshift(z, round_mode).normalize(); - auto q = td::RefInt256{true, tmp}; - stack.push_int(std::move(q)); + stack.push_int(td::make_refint(tmp)); } void interpret_lshift_div(vm::Stack& stack, int round_mode) { int z = stack.pop_smallint_range(256); auto y = stack.pop_int(); auto x = stack.pop_int(); - td::BigIntG<257 * 2> tmp{*x}; + typename td::BigInt256::DoubleInt tmp{*x}; tmp <<= z; - auto q = td::RefInt256{true}; + auto q = td::make_refint(); tmp.mod_div(*y, q.unique_write(), round_mode); q.unique_write().normalize(); stack.push_int(std::move(q)); @@ -488,7 +484,7 @@ void interpret_make_xchg(vm::Stack& stack) { if (x) { stack.push_object(td::Ref{true, std::bind(interpret_xchg, _1, x, y)}); } else if (y <= 1) { - stack.push_object(y ? swap_word_def : Dictionary::nop_word_def); + stack.push_object(y ? swap_word_def : nop_word_def); } else { stack.push_object(td::Ref{true, std::bind(interpret_xchg0, _1, y)}); } @@ -513,7 +509,7 @@ void interpret_make_pop(vm::Stack& stack) { } void interpret_is_string(vm::Stack& stack) { - stack.push_bool(stack.pop().type() == vm::StackEntry::t_string); + stack.push_bool(stack.pop_chk().type() == vm::StackEntry::t_string); } int make_utf8_char(char buffer[4], int x) { @@ -552,7 +548,8 @@ void interpret_hold(vm::Stack& stack) { stack.check_underflow(2); char buffer[8]; unsigned len = make_utf8_char(buffer, stack.pop_smallint_range(0x10ffff, -128)); - std::string s = stack.pop_string() + std::string{buffer, len}; + std::string s = stack.pop_string(); + s.append(buffer, len); stack.push_string(std::move(s)); } @@ -607,7 +604,7 @@ void interpret_str_split(vm::Stack& stack) { void interpret_str_pos(vm::Stack& stack) { auto s2 = stack.pop_string(), s1 = stack.pop_string(); auto pos = s1.find(s2); - stack.push_smallint(pos == std::string::npos ? -1 : pos); + stack.push_smallint(pos == std::string::npos ? -1 : static_cast(pos)); } void interpret_str_reverse(vm::Stack& stack) { @@ -947,6 +944,32 @@ void interpret_concat_builders(vm::Stack& stack) { stack.push_builder(std::move(cb1)); } +void interpret_cell_datasize(vm::Stack& stack, int mode) { + auto bound = (mode & 4 ? stack.pop_int() : td::make_refint(1 << 22)); + Ref cell; + Ref cs; + if (mode & 2) { + cs = stack.pop_cellslice(); + } else { + cell = stack.pop_maybe_cell(); + } + if (!bound->is_valid() || bound->sgn() < 0) { + throw IntError{"finite non-negative integer expected"}; + } + vm::VmStorageStat stat{bound->unsigned_fits_bits(63) ? bound->to_long() : (1ULL << 63) - 1}; + bool ok = (mode & 2 ? stat.add_storage(cs.write()) : stat.add_storage(std::move(cell))); + if (ok) { + stack.push_smallint(stat.cells); + stack.push_smallint(stat.bits); + stack.push_smallint(stat.refs); + } else if (!(mode & 1)) { + throw IntError{"scanned too many cells"}; + } + if (mode & 1) { + stack.push_bool(ok); + } +} + void interpret_slice_bitrefs(vm::Stack& stack, int mode) { auto cs = stack.pop_cellslice(); if (mode & 1) { @@ -1007,8 +1030,8 @@ void interpret_store_end(vm::Stack& stack, bool special) { void interpret_from_cell(vm::Stack& stack) { auto cell = stack.pop_cell(); - Ref cs{true}; - if (!cs.unique_write().load(vm::NoVmOrd(), std::move(cell))) { + Ref cs{true, vm::NoVmOrd(), std::move(cell)}; + if (!cs->is_valid()) { throw IntError{"deserializing a special cell as ordinary"}; } stack.push(cs); @@ -1080,6 +1103,31 @@ void interpret_fetch_bytes(vm::Stack& stack, int mode) { } } +void interpret_fetch_slice(vm::Stack& stack, int mode) { + unsigned refs = ((mode & 1) ? stack.pop_smallint_range(4) : 0); + unsigned bits = stack.pop_smallint_range(1023); + auto cs = stack.pop_cellslice(); + if (!cs->have(bits, refs)) { + if (mode & 2) { + stack.push(std::move(cs)); + } + stack.push_bool(false); + if (!(mode & 4)) { + throw IntError{"end of data while fetching subslice from cell"}; + } + } else { + if (mode & 2) { + stack.push(cs.write().fetch_subslice(bits, refs)); + stack.push(std::move(cs)); + } else { + stack.push(cs->prefetch_subslice(bits, refs)); + } + if (mode & 4) { + stack.push_bool(true); + } + } +} + void interpret_cell_empty(vm::Stack& stack) { auto cs = stack.pop_cellslice(); stack.push_bool(cs->empty_ext()); @@ -1117,7 +1165,10 @@ void interpret_fetch_ref(vm::Stack& stack, int mode) { stack.push(std::move(cs)); } if (mode & 1) { - Ref new_cs{true, vm::NoVm(), std::move(cell)}; + Ref new_cs{true, vm::NoVmOrd(), std::move(cell)}; + if (!new_cs->is_valid()) { + throw IntError{"cannot load ordinary cell"}; + } stack.push(std::move(new_cs)); } else { stack.push_cell(std::move(cell)); @@ -1282,7 +1333,7 @@ void interpret_atom_anon(vm::Stack& stack) { } void interpret_is_atom(vm::Stack& stack) { - stack.push_bool(stack.pop().is_atom()); + stack.push_bool(stack.pop_chk().is_atom()); } bool are_eqv(vm::StackEntry x, vm::StackEntry y) { @@ -1304,11 +1355,13 @@ bool are_eqv(vm::StackEntry x, vm::StackEntry y) { } void interpret_is_eqv(vm::Stack& stack) { + stack.check_underflow(2); auto y = stack.pop(), x = stack.pop(); stack.push_bool(are_eqv(std::move(x), std::move(y))); } void interpret_is_eq(vm::Stack& stack) { + stack.check_underflow(2); auto y = stack.pop(), x = stack.pop(); stack.push_bool(x == y); } @@ -1478,6 +1531,150 @@ void interpret_crc32c(vm::Stack& stack) { stack.push_smallint(td::crc32c(td::Slice{str})); } +// Fift hashmaps + +void push_hmap(vm::Stack& stack, Ref hmap) { + if (hmap.not_null()) { + stack.push_object(std::move(hmap)); + } else { + stack.push({}); + } +} + +void push_hmap(vm::Stack& stack, HashmapKeeper hmap_keep) { + push_hmap(stack, hmap_keep.extract()); +} + +Ref pop_hmap(vm::Stack& stack) { + stack.check_underflow(1); + auto se = stack.pop(); + if (se.is_null()) { + return {}; + } + auto hmap_ref = std::move(se).as_object(); + if (hmap_ref.is_null()) { + throw IntError{"hashmap expected"}; + } + return hmap_ref; +} + +HashmapKeeper pop_hmap_keeper(vm::Stack& stack) { + return HashmapKeeper{pop_hmap(stack)}; +} + +void interpret_hmap_new(vm::Stack& stack) { + stack.push({}); +} + +void interpret_hmap_fetch(vm::Stack& stack, int mode) { + auto hmap = pop_hmap(stack); + auto value = Hashmap::get(std::move(hmap), stack.pop_chk()); + bool found = !value.is_null(); + if ((mode & 8) && !found) { + throw IntError{"hashmap key not found"}; + } + if (mode & (2 << (int)found)) { + stack.push(std::move(value)); + } + if (mode & 1) { + stack.push_bool(found); + } +} + +void interpret_hmap_delete(vm::Stack& stack, int mode) { + auto hmap = pop_hmap(stack); + auto res = Hashmap::get_remove(std::move(hmap), stack.pop_chk()); + push_hmap(stack, std::move(res.first)); + bool found = !res.second.is_null(); + if ((mode & 8) && !found) { + throw IntError{"hashmap key not found"}; + } + if (mode & (2 << (int)found)) { + stack.push(std::move(res.second)); + } + if (mode & 1) { + stack.push_bool(found); + } +} + +void interpret_hmap_store(vm::Stack& stack, int mode) { + stack.check_underflow(3); + auto hmap = pop_hmap_keeper(stack); + auto key = stack.pop(), value = stack.pop(); + bool ok = true; + if (mode & 1) { + hmap.set(std::move(key), std::move(value)); + } else { + ok = hmap.replace(std::move(key), std::move(value)); + } + push_hmap(stack, std::move(hmap)); + if (mode & 2) { + stack.push_bool(ok); + } +} + +void interpret_hmap_is_empty(vm::Stack& stack) { + stack.push_bool(pop_hmap(stack).is_null()); +} + +void interpret_hmap_decompose(vm::Stack& stack, int mode) { + auto hmap = pop_hmap(stack); + if (hmap.is_null()) { + if (mode & 1) { + stack.push_bool(false); + } else { + throw IntError{"empty hmap"}; + } + return; + } + stack.push(hmap->key()); + stack.push(hmap->value()); + push_hmap(stack, hmap->left()); + push_hmap(stack, hmap->right()); + if (mode & 1) { + stack.push_bool(true); + } +} + +class HmapIterCont : public LoopCont { + HashmapIterator it; + bool ok; + + public: + HmapIterCont(Ref _func, Ref _after, HashmapIterator _it) + : LoopCont(std::move(_func), std::move(_after)), it(std::move(_it)), ok(true) { + } + HmapIterCont(const HmapIterCont&) = default; + HmapIterCont* make_copy() const override { + return new HmapIterCont(*this); + } + bool init(IntCtx& ctx) override { + return true; + } + bool pre_exec(IntCtx& ctx) override { + if (it.eof()) { + return false; + } else { + ctx.stack.push(it->key()); + ctx.stack.push(it->value()); + return true; + } + } + bool post_exec(IntCtx& ctx) override { + ok = ctx.stack.pop_bool(); + return ok && it.next(); + } + bool finalize(IntCtx& ctx) override { + ctx.stack.push_bool(ok); + return true; + } +}; + +Ref interpret_hmap_foreach(IntCtx& ctx, int mode) { + auto func = pop_exec_token(ctx); + return td::make_ref(std::move(func), std::move(ctx.next), pop_hmap_keeper(ctx).begin(mode & 1)); +} + // vm dictionaries void interpret_dict_new(vm::Stack& stack) { stack.push({}); @@ -1531,7 +1728,7 @@ void interpret_dict_add(vm::Stack& stack, vm::Dictionary::SetMode mode, bool add stack.push_bool(res); } -void interpret_dict_get(vm::Stack& stack, int sgnd) { +void interpret_dict_get(vm::Stack& stack, int sgnd, int mode) { int n = stack.pop_smallint_range(vm::Dictionary::max_key_bits); vm::Dictionary dict{stack.pop_maybe_cell(), n}; unsigned char buffer[vm::Dictionary::max_key_bytes]; @@ -1540,116 +1737,313 @@ void interpret_dict_get(vm::Stack& stack, int sgnd) { if (!key.is_valid()) { throw IntError{"not enough bits for a dictionary key"}; } - auto res = dict.lookup(std::move(key)); - if (res.not_null()) { + auto res = (mode & 4 ? dict.lookup_delete(std::move(key)) : dict.lookup(std::move(key))); + if (mode & 4) { + stack.push_maybe_cell(std::move(dict).extract_root_cell()); + } + bool found = res.not_null(); + if (found && (mode & 2)) { stack.push_cellslice(std::move(res)); - stack.push_bool(true); - } else { - stack.push_bool(false); + } + if (mode & 1) { + stack.push_bool(found); } } -void interpret_dict_map(IntCtx& ctx) { - auto func = pop_exec_token(ctx); - int n = ctx.stack.pop_smallint_range(vm::Dictionary::max_key_bits); - vm::Dictionary dict{ctx.stack.pop_maybe_cell(), n}; - vm::Dictionary::simple_map_func_t simple_map = [&ctx, func](vm::CellBuilder& cb, Ref cs_ref) -> bool { - ctx.stack.push_builder(Ref(cb)); - ctx.stack.push_cellslice(std::move(cs_ref)); - func->run(ctx); - assert(cb.is_unique()); - if (!ctx.stack.pop_bool()) { - return false; - } - Ref cb_ref = ctx.stack.pop_builder(); - cb = *cb_ref; - return true; - }; - dict.map(std::move(simple_map)); - ctx.stack.push_maybe_cell(std::move(dict).extract_root_cell()); -} +class DictMapCont : public LoopCont { + int n; + bool ext; + bool sgnd; + vm::Dictionary dict, dict2; + vm::DictIterator it; -void interpret_dict_map_ext(IntCtx& ctx) { - auto func = pop_exec_token(ctx); - int n = ctx.stack.pop_smallint_range(vm::Dictionary::max_key_bits); - vm::Dictionary dict{ctx.stack.pop_maybe_cell(), n}; - vm::Dictionary::map_func_t map_func = [&ctx, func](vm::CellBuilder& cb, Ref cs_ref, - td::ConstBitPtr key, int key_len) -> bool { - ctx.stack.push_builder(Ref(cb)); - td::RefInt256 x{true}; - x.unique_write().import_bits(key, key_len, false); - ctx.stack.push_int(std::move(x)); - ctx.stack.push_cellslice(std::move(cs_ref)); - func->run(ctx); - assert(cb.is_unique()); - if (!ctx.stack.pop_bool()) { - return false; - } - Ref cb_ref = ctx.stack.pop_builder(); - cb = *cb_ref; - return true; - }; - dict.map(std::move(map_func)); - ctx.stack.push_maybe_cell(std::move(dict).extract_root_cell()); -} - -void interpret_dict_foreach(IntCtx& ctx) { - auto func = pop_exec_token(ctx); - int n = ctx.stack.pop_smallint_range(vm::Dictionary::max_key_bits); - vm::Dictionary dict{ctx.stack.pop_maybe_cell(), n}; - vm::Dictionary::foreach_func_t foreach_func = [&ctx, func](Ref cs_ref, td::ConstBitPtr key, - int key_len) -> bool { - td::RefInt256 x{true}; - x.unique_write().import_bits(key, key_len, false); - ctx.stack.push_int(std::move(x)); - ctx.stack.push_cellslice(std::move(cs_ref)); - func->run(ctx); - return ctx.stack.pop_bool(); - }; - ctx.stack.push_bool(dict.check_for_each(std::move(foreach_func))); -} - -void interpret_dict_merge(IntCtx& ctx) { - auto func = pop_exec_token(ctx); - int n = ctx.stack.pop_smallint_range(vm::Dictionary::max_key_bits); - vm::Dictionary dict2{ctx.stack.pop_maybe_cell(), n}; - vm::Dictionary dict1{ctx.stack.pop_maybe_cell(), n}; - vm::Dictionary::simple_combine_func_t simple_combine = [&ctx, func](vm::CellBuilder& cb, Ref cs1_ref, - Ref cs2_ref) -> bool { - ctx.stack.push_builder(Ref(cb)); - ctx.stack.push_cellslice(std::move(cs1_ref)); - ctx.stack.push_cellslice(std::move(cs2_ref)); - func->run(ctx); - assert(cb.is_unique()); - if (!ctx.stack.pop_bool()) { - return false; - } - Ref cb_ref = ctx.stack.pop_builder(); - cb = *cb_ref; - return true; - }; - if (!dict1.combine_with(dict2, std::move(simple_combine))) { - throw IntError{"cannot combine dictionaries"}; + public: + DictMapCont(Ref _func, Ref _after, int _n, Ref dict_root, bool _ext, bool _sgnd) + : LoopCont(std::move(_func), std::move(_after)) + , n(_n) + , ext(_ext) + , sgnd(_sgnd) + , dict(std::move(dict_root), n) + , dict2(n) { } - ctx.stack.push_maybe_cell(std::move(dict1).extract_root_cell()); + DictMapCont(const DictMapCont&) = default; + DictMapCont* make_copy() const override { + return new DictMapCont(*this); + } + bool init(IntCtx& ctx) override { + it = dict.init_iterator(false, sgnd); + return true; + } + bool pre_exec(IntCtx& ctx) override; + bool post_exec(IntCtx& ctx) override; + bool finalize(IntCtx& ctx) override; +}; + +bool DictMapCont::pre_exec(IntCtx& ctx) { + if (it.eof()) { + return false; + } + ctx.stack.push_builder(td::make_ref()); + if (ext) { + ctx.stack.push_int(dict.key_as_integer(it.cur_pos(), sgnd)); + } + ctx.stack.push_cellslice(it.cur_value()); + return true; } -void interpret_dict_diff(IntCtx& ctx) { +bool DictMapCont::post_exec(IntCtx& ctx) { + if (ctx.stack.pop_bool()) { + if (!dict2.set_builder(it.cur_pos(), n, ctx.stack.pop_builder())) { + throw IntError{"cannot insert value into dictionary"}; + } + } + return !(++it).eof(); +} + +bool DictMapCont::finalize(IntCtx& ctx) { + ctx.stack.push_maybe_cell(std::move(dict2).extract_root_cell()); + return true; +} + +Ref interpret_dict_map(IntCtx& ctx, bool ext, bool sgnd) { auto func = pop_exec_token(ctx); int n = ctx.stack.pop_smallint_range(vm::Dictionary::max_key_bits); - vm::Dictionary dict2{ctx.stack.pop_maybe_cell(), n}; - vm::Dictionary dict1{ctx.stack.pop_maybe_cell(), n}; - vm::Dictionary::scan_diff_func_t scan_value_pair = - [&ctx, func](td::ConstBitPtr key, int key_len, Ref cs1_ref, Ref cs2_ref) -> bool { - td::RefInt256 x{true}; - x.unique_write().import_bits(key, key_len, false); - ctx.stack.push_int(std::move(x)); - ctx.stack.push_maybe_cellslice(std::move(cs1_ref)); - ctx.stack.push_maybe_cellslice(std::move(cs2_ref)); - func->run(ctx); - return ctx.stack.pop_bool(); - }; - ctx.stack.push_bool(dict1.scan_diff(dict2, std::move(scan_value_pair))); + return td::make_ref(std::move(func), std::move(ctx.next), n, ctx.stack.pop_maybe_cell(), ext, sgnd); +} + +class DictIterCont : public LoopCont { + int n; + bool reverse; + bool sgnd; + bool ok; + bool inited{false}; + vm::Dictionary dict; + vm::DictIterator it; + + public: + DictIterCont(Ref _func, Ref _after, int _n, Ref dict_root, bool _reverse, bool _sgnd) + : LoopCont(std::move(_func), std::move(_after)) + , n(_n) + , reverse(_reverse) + , sgnd(_sgnd) + , ok(true) + , dict(std::move(dict_root), n) { + } + DictIterCont(const DictIterCont&) = default; + DictIterCont* make_copy() const override { + return new DictIterCont(*this); + } + bool do_init(); + bool init(IntCtx& ctx) override { + return do_init(); + } + bool pre_exec(IntCtx& ctx) override; + bool post_exec(IntCtx& ctx) override; + bool finalize(IntCtx& ctx) override; + template + bool lookup(const T& key, bool strict, bool backw) { + return do_init() && it.lookup(key, strict, backw); + } +}; + +bool DictIterCont::do_init() { + if (!inited) { + it = dict.init_iterator(reverse, sgnd); + inited = true; + } + return true; +} + +bool DictIterCont::pre_exec(IntCtx& ctx) { + if (it.eof()) { + return false; + } + ctx.stack.push_int(dict.key_as_integer(it.cur_pos(), sgnd)); + ctx.stack.push_cellslice(it.cur_value()); + return true; +} + +bool DictIterCont::post_exec(IntCtx& ctx) { + ok = ctx.stack.pop_bool(); + return ok && !(++it).eof(); +} + +bool DictIterCont::finalize(IntCtx& ctx) { + ctx.stack.push_bool(ok); + return true; +} + +Ref interpret_dict_foreach(IntCtx& ctx, bool reverse, bool sgnd) { + auto func = pop_exec_token(ctx); + int n = ctx.stack.pop_smallint_range(vm::Dictionary::max_key_bits); + return td::make_ref(std::move(func), std::move(ctx.next), n, ctx.stack.pop_maybe_cell(), reverse, sgnd); +} + +// mode: +1 = reverse, +2 = signed, +4 = strict, +8 = lookup backwards, +16 = with hint +Ref interpret_dict_foreach_from(IntCtx& ctx, int mode) { + if (mode < 0) { + mode = ctx.stack.pop_smallint_range(31); + } + auto func = pop_exec_token(ctx); + int n = ctx.stack.pop_smallint_range(vm::Dictionary::max_key_bits); + auto it_cont = td::make_ref(std::move(func), std::move(ctx.next), n, ctx.stack.pop_maybe_cell(), + mode & 1, mode & 2); + for (int s = (mode >> 4) & 1; s >= 0; --s) { + unsigned char buffer[vm::Dictionary::max_key_bytes]; + auto key = vm::Dictionary::integer_key(ctx.stack.pop_int(), n, mode & 2, buffer); + if (!key.is_valid()) { + throw IntError{"not enough bits for a dictionary key"}; + } + it_cont.write().lookup(key, mode & 4, mode & 8); + } + return it_cont; +} + +class DictMergeCont : public LoopCont { + int n; + vm::Dictionary dict1, dict2, dict3; + vm::DictIterator it1, it2; + + public: + DictMergeCont(Ref _func, Ref _after, int _n, Ref dict1_root, Ref dict2_root) + : LoopCont(std::move(_func), std::move(_after)) + , n(_n) + , dict1(std::move(dict1_root), n) + , dict2(std::move(dict2_root), n) + , dict3(n) { + } + DictMergeCont(const DictMergeCont&) = default; + DictMergeCont* make_copy() const override { + return new DictMergeCont(*this); + } + bool init(IntCtx& ctx) override { + it1 = dict1.begin(); + it2 = dict2.begin(); + return true; + } + bool pre_exec(IntCtx& ctx) override; + bool post_exec(IntCtx& ctx) override; + bool finalize(IntCtx& ctx) override; +}; + +bool DictMergeCont::pre_exec(IntCtx& ctx) { + while (!it1.eof() || !it2.eof()) { + int c = it1.eof() ? 1 : (it2.eof() ? -1 : it1.cur_pos().compare(it2.cur_pos(), n)); + bool ok = true; + if (c < 0) { + ok = dict3.set(it1.cur_pos(), n, it1.cur_value()); + ++it1; + } else if (c > 0) { + ok = dict3.set(it2.cur_pos(), n, it2.cur_value()); + ++it2; + } else { + ctx.stack.push_builder(Ref{true}); + ctx.stack.push_cellslice(it1.cur_value()); + ctx.stack.push_cellslice(it2.cur_value()); + return true; + } + if (!ok) { + throw IntError{"cannot insert value into dictionary"}; + } + } + return false; +} + +bool DictMergeCont::post_exec(IntCtx& ctx) { + if (ctx.stack.pop_bool() && !dict3.set_builder(it1.cur_pos(), n, ctx.stack.pop_builder())) { + throw IntError{"cannot insert value into dictionary"}; + } + ++it1; + ++it2; + return true; +} + +bool DictMergeCont::finalize(IntCtx& ctx) { + ctx.stack.push_maybe_cell(std::move(dict3).extract_root_cell()); + return true; +} + +Ref interpret_dict_merge(IntCtx& ctx) { + auto func = pop_exec_token(ctx); + int n = ctx.stack.pop_smallint_range(vm::Dictionary::max_key_bits); + auto dict2_root = ctx.stack.pop_maybe_cell(); + return td::make_ref(std::move(func), std::move(ctx.next), n, ctx.stack.pop_maybe_cell(), + std::move(dict2_root)); +} + +class DictDiffCont : public LoopCont { + int n; + bool ok{true}; + vm::Dictionary dict1, dict2; + vm::DictIterator it1, it2; + + public: + DictDiffCont(Ref _func, Ref _after, int _n, Ref dict1_root, Ref dict2_root) + : LoopCont(std::move(_func), std::move(_after)) + , n(_n) + , dict1(std::move(dict1_root), n) + , dict2(std::move(dict2_root), n) { + } + DictDiffCont(const DictDiffCont&) = default; + DictDiffCont* make_copy() const override { + return new DictDiffCont(*this); + } + bool init(IntCtx& ctx) override { + it1 = dict1.begin(); + it2 = dict2.begin(); + return true; + } + bool pre_exec(IntCtx& ctx) override; + bool post_exec(IntCtx& ctx) override; + bool finalize(IntCtx& ctx) override; +}; + +bool DictDiffCont::pre_exec(IntCtx& ctx) { + while (!it1.eof() || !it2.eof()) { + int c = it1.eof() ? 1 : (it2.eof() ? -1 : it1.cur_pos().compare(it2.cur_pos(), n)); + if (c < 0) { + ctx.stack.push_int(dict1.key_as_integer(it1.cur_pos())); + ctx.stack.push_cellslice(it1.cur_value()); + ctx.stack.push_null(); + ++it1; + } else if (c > 0) { + ctx.stack.push_int(dict2.key_as_integer(it2.cur_pos())); + ctx.stack.push_null(); + ctx.stack.push_cellslice(it2.cur_value()); + ++it2; + } else { + if (!it1.cur_value()->contents_equal(*it2.cur_value())) { + ctx.stack.push_int(dict1.key_as_integer(it1.cur_pos())); + ctx.stack.push_cellslice(it1.cur_value()); + ctx.stack.push_cellslice(it2.cur_value()); + } else { + ++it1; + ++it2; + continue; + } + ++it1; + ++it2; + } + return true; + } + return false; +} + +bool DictDiffCont::post_exec(IntCtx& ctx) { + return (ok = ctx.stack.pop_bool()); +} + +bool DictDiffCont::finalize(IntCtx& ctx) { + ctx.stack.push_bool(ok); + return true; +} + +Ref interpret_dict_diff(IntCtx& ctx) { + auto func = pop_exec_token(ctx); + int n = ctx.stack.pop_smallint_range(vm::Dictionary::max_key_bits); + auto dict2_root = ctx.stack.pop_maybe_cell(); + return td::make_ref(std::move(func), std::move(ctx.next), n, ctx.stack.pop_maybe_cell(), + std::move(dict2_root)); } void interpret_pfx_dict_add(vm::Stack& stack, vm::Dictionary::SetMode mode, bool add_builder) { @@ -1680,7 +2074,7 @@ void interpret_pfx_dict_get(vm::Stack& stack) { } void interpret_bitstring_hex_literal(IntCtx& ctx) { - auto s = ctx.scan_word_to('}'); + auto s = ctx.parser->scan_word_to('}'); unsigned char buff[128]; int bits = (int)td::bitstring::parse_bitstring_hex_literal(buff, sizeof(buff), s.begin(), s.end()); if (bits < 0) { @@ -1688,11 +2082,11 @@ void interpret_bitstring_hex_literal(IntCtx& ctx) { } auto cs = Ref{true, vm::CellBuilder().store_bits(td::ConstBitPtr{buff}, bits).finalize()}; ctx.stack.push(std::move(cs)); - push_argcount(ctx.stack, 1); + push_argcount(ctx, 1); } void interpret_bitstring_binary_literal(IntCtx& ctx) { - auto s = ctx.scan_word_to('}'); + auto s = ctx.parser->scan_word_to('}'); unsigned char buff[128]; int bits = (int)td::bitstring::parse_bitstring_binary_literal(buff, sizeof(buff), s.begin(), s.end()); if (bits < 0) { @@ -1700,12 +2094,12 @@ void interpret_bitstring_binary_literal(IntCtx& ctx) { } auto cs = Ref{true, vm::CellBuilder().store_bits(td::ConstBitPtr{buff}, bits).finalize()}; ctx.stack.push(std::move(cs)); - push_argcount(ctx.stack, 1); + push_argcount(ctx, 1); } void interpret_word(IntCtx& ctx) { char sep = (char)ctx.stack.pop_smallint_range(127); - auto word = (sep != ' ' ? ctx.scan_word_to(sep, true) : ctx.scan_word()); + auto word = (sep != ' ' ? ctx.parser->scan_word_to(sep, true) : ctx.parser->scan_word()); ctx.stack.push_string(word); } @@ -1713,21 +2107,21 @@ void interpret_word_ext(IntCtx& ctx) { int mode = ctx.stack.pop_smallint_range(11); auto delims = ctx.stack.pop_string(); if (mode & 8) { - ctx.skipspc(mode & 4); + ctx.parser->skipspc(mode & 4); } - ctx.stack.push_string(ctx.scan_word_ext(CharClassifier{delims, mode & 3})); + ctx.stack.push_string(ctx.parser->scan_word_ext(CharClassifier{delims, mode & 3})); } void interpret_skipspc(IntCtx& ctx) { - ctx.skipspc(); + ctx.parser->skipspc(); } void interpret_wordlist_begin_aux(vm::Stack& stack) { - stack.push({vm::from_object, Ref{true}}); + stack.push_make_object(); } void interpret_wordlist_begin(IntCtx& ctx) { - check_not_int_exec(ctx); + ctx.check_not_int_exec(); interpret_wordlist_begin_aux(ctx.stack); push_argcount(ctx, 0); ++(ctx.state); @@ -1736,26 +2130,26 @@ void interpret_wordlist_begin(IntCtx& ctx) { void interpret_wordlist_end_aux(vm::Stack& stack) { Ref wordlist_ref = pop_word_list(stack); wordlist_ref.write().close(); - stack.push({vm::from_object, Ref{wordlist_ref}}); + stack.push_object(std::move(wordlist_ref)); } void interpret_wordlist_end(IntCtx& ctx) { - check_compile(ctx); + ctx.check_compile(); interpret_wordlist_end_aux(ctx.stack); push_argcount(ctx, 1); --(ctx.state); } void interpret_internal_interpret_begin(IntCtx& ctx) { - check_compile(ctx); + ctx.check_compile(); push_argcount(ctx, 0); ctx.state = -ctx.state; } void interpret_internal_interpret_end(IntCtx& ctx) { - check_int_exec(ctx); + ctx.check_int_exec(); ctx.state = -ctx.state; - ctx.stack.push({vm::from_object, Dictionary::nop_word_def}); + ctx.stack.push_object(nop_word_def); } // (create) @@ -1772,18 +2166,12 @@ void interpret_create_aux(IntCtx& ctx, int mode) { if (!(mode & 2)) { word += ' '; } - bool active = (mode & 1); - auto entry = ctx.dictionary->lookup(word); - if (entry) { - *entry = WordRef{wd_ref, active}; // redefine word - } else { - ctx.dictionary->def_word(std::move(word), {wd_ref, active}); - } + ctx.dictionary.def_word(std::move(word), {std::move(wd_ref), (bool)(mode & 1)}); } // { bl word 0 (create) } : create void interpret_create(IntCtx& ctx) { - auto word = ctx.scan_word(); + auto word = ctx.parser->scan_word(); if (!word.size()) { throw IntError{"non-empty word name expected"}; } @@ -1791,14 +2179,14 @@ void interpret_create(IntCtx& ctx) { interpret_create_aux(ctx, 0); } -Ref create_aux_wd{Ref{true, std::bind(interpret_create_aux, std::placeholders::_1, -1)}}; +Ref create_aux_wd{Ref{true, std::bind(interpret_create_aux, std::placeholders::_1, -1)}}; // { bl word 2 ' (create) } :: : void interpret_colon(IntCtx& ctx, int mode) { - ctx.stack.push_string(ctx.scan_word()); + ctx.stack.push_string(ctx.parser->scan_word()); ctx.stack.push_smallint(mode); ctx.stack.push_smallint(2); - ctx.stack.push({vm::from_object, create_aux_wd}); + ctx.stack.push_object(create_aux_wd); //push_argcount(ctx, 2, create_wd); } @@ -1806,27 +2194,27 @@ void interpret_colon(IntCtx& ctx, int mode) { void interpret_forget_aux(IntCtx& ctx) { std::string s = ctx.stack.pop_string(); auto s_copy = s; - auto entry = ctx.dictionary->lookup(s); + auto entry = ctx.dictionary.lookup(s); if (!entry) { s += " "; - entry = ctx.dictionary->lookup(s); + entry = ctx.dictionary.lookup(s); } if (!entry) { throw IntError{"`" + s_copy + "` not found"}; } else { - ctx.dictionary->undef_word(s); + ctx.dictionary.undef_word(s); } } // { bl word (forget) } : forget void interpret_forget(IntCtx& ctx) { - ctx.stack.push_string(ctx.scan_word()); + ctx.stack.push_string(ctx.parser->scan_word()); interpret_forget_aux(ctx); } void interpret_quote_str(IntCtx& ctx) { - ctx.stack.push_string(ctx.scan_word_to('"')); - push_argcount(ctx.stack, 1); + ctx.stack.push_string(ctx.parser->scan_word_to('"')); + push_argcount(ctx, 1); } int str_utf8_code(const char* str, int& len) { @@ -1854,7 +2242,7 @@ int str_utf8_code(const char* str, int& len) { } void interpret_char(IntCtx& ctx) { - auto s = ctx.scan_word(); + auto s = ctx.parser->scan_word(); int len = (s.size() < 10 ? (int)s.size() : 10); int code = str_utf8_code(s.data(), len); if (code < 0 || s.size() != (unsigned)len) { @@ -1888,7 +2276,7 @@ int parse_number(std::string s, td::RefInt256& num, td::RefInt256& denom, bool a const char* str = s.c_str(); int len = (int)s.size(); int frac = -1, base, *frac_ptr = allow_frac ? &frac : nullptr; - num = td::RefInt256{true}; + num = td::make_refint(); auto& x = num.unique_write(); if (len >= 4 && str[0] == '-' && str[1] == '0' && (str[2] == 'x' || str[2] == 'b')) { if (str[2] == 'x') { @@ -1930,7 +2318,7 @@ int parse_number(std::string s, td::RefInt256& num, td::RefInt256& denom, bool a if (frac < 0) { return 1; } else { - denom = td::RefInt256{true, 1}; + denom = td::make_refint(1); while (frac-- > 0) { if (!denom.unique_write().mul_tiny(base).normalize_bool()) { if (throw_error) { @@ -1972,39 +2360,49 @@ void interpret_parse_hex_number(vm::Stack& stack) { } void interpret_quit(IntCtx& ctx) { - throw Quit{0}; + // TODO: change to correct behavior + ctx.set_exit_code(0); + ctx.next.clear(); } void interpret_bye(IntCtx& ctx) { - throw Quit{-1}; + ctx.set_exit_code(-1); + ctx.next.clear(); } -void interpret_halt(vm::Stack& stack) { - int code = stack.pop_smallint_range(255); - throw Quit{~code}; +void interpret_halt(IntCtx& ctx) { + ctx.set_exit_code(~ctx.stack.pop_smallint_range(255)); + ctx.next.clear(); } void interpret_abort(IntCtx& ctx) { throw IntError{ctx.stack.pop_string()}; } -Ref interpret_execute(IntCtx& ctx) { +Ref interpret_execute(IntCtx& ctx) { return pop_exec_token(ctx); } -Ref interpret_execute_times(IntCtx& ctx) { +Ref interpret_call_cc(IntCtx& ctx) { + auto next = pop_exec_token(ctx); + ctx.stack.push_object(std::move(ctx.next)); + return next; +} + +Ref interpret_execute_times(IntCtx& ctx) { int count = ctx.stack.pop_smallint_range(1000000000); - auto wd_ref = pop_exec_token(ctx); - if (!count) { + auto body = pop_exec_token(ctx); + if (count <= 0) { return {}; } - while (--count > 0) { - wd_ref->run(ctx); + if (count == 1) { + return body; } - return wd_ref; + ctx.next = td::make_ref(body, std::move(ctx.next), count - 1); + return body; } -Ref interpret_if(IntCtx& ctx) { +Ref interpret_if(IntCtx& ctx) { auto true_ref = pop_exec_token(ctx); if (ctx.stack.pop_bool()) { return true_ref; @@ -2013,7 +2411,7 @@ Ref interpret_if(IntCtx& ctx) { } } -Ref interpret_ifnot(IntCtx& ctx) { +Ref interpret_ifnot(IntCtx& ctx) { auto false_ref = pop_exec_token(ctx); if (ctx.stack.pop_bool()) { return {}; @@ -2022,7 +2420,7 @@ Ref interpret_ifnot(IntCtx& ctx) { } } -Ref interpret_cond(IntCtx& ctx) { +Ref interpret_cond(IntCtx& ctx) { auto false_ref = pop_exec_token(ctx); auto true_ref = pop_exec_token(ctx); if (ctx.stack.pop_bool()) { @@ -2032,80 +2430,136 @@ Ref interpret_cond(IntCtx& ctx) { } } -void interpret_while(IntCtx& ctx) { - auto body_ref = pop_exec_token(ctx); - auto cond_ref = pop_exec_token(ctx); - while (true) { - cond_ref->run(ctx); - if (!ctx.stack.pop_bool()) { - break; +Ref interpret_while(IntCtx& ctx) { + auto body = pop_exec_token(ctx); + auto cond = pop_exec_token(ctx); + ctx.next = td::make_ref(cond, std::move(body), std::move(ctx.next), true); + return cond; +} + +Ref interpret_until(IntCtx& ctx) { + auto body = pop_exec_token(ctx); + ctx.next = td::make_ref(body, std::move(ctx.next)); + return body; +} + +DictEntry context_lookup(IntCtx& ctx, std::string word, bool append_space = true) { + if (append_space) { + auto entry = context_lookup(ctx, word, false); + if (!entry) { + entry = context_lookup(ctx, word + ' ', false); } - body_ref->run(ctx); + return entry; } -} - -void interpret_until(IntCtx& ctx) { - auto body_ref = pop_exec_token(ctx); - do { - body_ref->run(ctx); - } while (!ctx.stack.pop_bool()); + auto entry = ctx.context.lookup(word); + if (!entry && ctx.context != ctx.dictionary) { + entry = ctx.dictionary.lookup(word); + } + if (!entry && ctx.main_dictionary != ctx.context && ctx.main_dictionary != ctx.dictionary) { + entry = ctx.main_dictionary.lookup(word); + } + return entry; } void interpret_tick(IntCtx& ctx) { - std::string word = ctx.scan_word().str(); - auto entry = ctx.dictionary->lookup(word); + std::string word = ctx.parser->scan_word().str(); + auto entry = context_lookup(ctx, word); if (!entry) { - entry = ctx.dictionary->lookup(word + ' '); - if (!entry) { - throw IntError{"word `" + word + "` undefined"}; - } + throw IntError{"word `" + word + "` undefined"}; } - ctx.stack.push({vm::from_object, entry->get_def()}); + ctx.stack.push_object(entry.get_def()); push_argcount(ctx, 1); } -void interpret_find(IntCtx& ctx) { +void interpret_find(IntCtx& ctx, int mode) { std::string word = ctx.stack.pop_string(); - auto entry = ctx.dictionary->lookup(word); - if (!entry) { - entry = ctx.dictionary->lookup(word + ' '); - } + auto entry = context_lookup(ctx, word, !(mode & 2)); if (!entry) { ctx.stack.push_bool(false); } else { - ctx.stack.push({vm::from_object, entry->get_def()}); - ctx.stack.push_bool(true); + ctx.stack.push_object(entry.get_def()); + if (!(mode & 1) || !entry.is_active()) { + ctx.stack.push_bool(true); + } else { + ctx.stack.push_smallint(1); + } } } -void interpret_tick_nop(vm::Stack& stack) { - stack.push({vm::from_object, Dictionary::nop_word_def}); +void interpret_leave_source(IntCtx& ctx) { + if (!ctx.leave_ctx()) { + throw IntError{"cannot leave included file interpretation context"}; + } } -void interpret_include(IntCtx& ctx) { +void interpret_include_depth(IntCtx& ctx) { + ctx.stack.push_smallint(ctx.include_depth()); +} + +Ref interpret_include(IntCtx& ctx) { auto fname = ctx.stack.pop_string(); - auto r_file = ctx.source_lookup->lookup_source(fname, ctx.currentd_dir); + auto r_file = ctx.source_lookup->lookup_source(fname, ctx.parser->currentd_dir); if (r_file.is_error()) { throw IntError{"cannot locate file `" + fname + "`"}; } auto file = r_file.move_as_ok(); - std::stringstream ss(std::move(file.data)); - IntCtx::Savepoint save{ctx, td::PathView(file.path).file_name().str(), td::PathView(file.path).parent_dir().str(), - &ss}; - funny_interpret_loop(ctx); + auto ss = std::make_unique(std::move(file.data)); + if (!ctx.enter_ctx(td::PathView(file.path).file_name().str(), td::PathView(file.path).parent_dir().str(), + std::move(ss))) { + throw IntError{"cannot enter included file interpretation context"}; + } + ctx.next = SeqCont::seq(td::make_ref(interpret_leave_source), std::move(ctx.next)); + return td::make_ref(); } -void interpret_skip_source(vm::Stack& stack) { - throw SkipToEof(); +td::Ref exit_interpret{true}; + +Ref interpret_skip_source(IntCtx& ctx) { + auto cont = exit_interpret->get().as_object(); + ctx.next.clear(); + /* + if (cont.is_null()) { + throw IntError{"no interpreter exit point set"}; + } + */ + return cont; } void interpret_words(IntCtx& ctx) { - for (const auto& x : *ctx.dictionary) { - *ctx.output_stream << x.first << " "; + for (const auto& x : ctx.dictionary) { + *ctx.output_stream << vm::StackEntry(x.key()).as_string() << " "; } *ctx.output_stream << std::endl; } +void interpret_get_current(IntCtx& ctx) { + ctx.stack.push(ctx.dictionary.get_box()); +} + +void interpret_set_current(IntCtx& ctx) { + ctx.dictionary = ctx.stack.pop_box(); +} + +void interpret_get_context(IntCtx& ctx) { + ctx.stack.push(ctx.context.get_box()); +} + +void interpret_set_context(IntCtx& ctx) { + ctx.context = ctx.stack.pop_box(); +} + +void interpret_set_context_to(IntCtx& ctx, Ref box) { + ctx.context = std::move(box); +} + +void interpret_print_backtrace(IntCtx& ctx) { + ctx.print_backtrace(*ctx.output_stream, ctx.next); +} + +void interpret_print_continuation(IntCtx& ctx) { + ctx.print_backtrace(*ctx.output_stream, pop_exec_token(ctx)); +} + void interpret_pack_std_smc_addr(vm::Stack& stack) { block::StdAddress a; stack.check_underflow(3); @@ -2202,14 +2656,25 @@ std::vector> get_vm_libraries() { // +16 = load c7 (smart-contract context) // +32 = return c5 (actions) // +64 = log vm ops to stderr +// +128 = pop hard gas limit (enabled by ACCEPT) from stack as well +// +256 = enable stack trace +// +512 = enable debug instructions +// +1024 = load global_version from stack void interpret_run_vm(IntCtx& ctx, int mode) { if (mode < 0) { - mode = ctx.stack.pop_smallint_range(0xff); + mode = ctx.stack.pop_smallint_range(0x7ff); } bool with_data = mode & 4; Ref c7; Ref data, actions; + int global_version = (mode & 1024) ? ctx.stack.pop_smallint_range(ton::SUPPORTED_VERSION) : ton::SUPPORTED_VERSION; + long long gas_max = (mode & 128) ? ctx.stack.pop_long_range(vm::GasLimits::infty) : vm::GasLimits::infty; long long gas_limit = (mode & 8) ? ctx.stack.pop_long_range(vm::GasLimits::infty) : vm::GasLimits::infty; + if (!(mode & 128)) { + gas_max = gas_limit; + } else { + gas_max = std::max(gas_max, gas_limit); + } if (mode & 16) { c7 = ctx.stack.pop_tuple(); } @@ -2219,9 +2684,9 @@ void interpret_run_vm(IntCtx& ctx, int mode) { auto cs = ctx.stack.pop_cellslice(); OstreamLogger ostream_logger(ctx.error_stream); auto log = create_vm_log((mode & 64) && ctx.error_stream ? &ostream_logger : nullptr); - vm::GasLimits gas{gas_limit}; - int res = - vm::run_vm_code(cs, ctx.stack, mode & 3, &data, log, nullptr, &gas, get_vm_libraries(), std::move(c7), &actions); + vm::GasLimits gas{gas_limit, gas_max}; + int res = vm::run_vm_code(cs, ctx.stack, (mode & 3) | ((mode & 0x300) >> 6), &data, log, nullptr, &gas, + get_vm_libraries(), std::move(c7), &actions, global_version); ctx.stack.push_smallint(res); if (with_data) { ctx.stack.push_cell(std::move(data)); @@ -2234,6 +2699,28 @@ void interpret_run_vm(IntCtx& ctx, int mode) { } } +void interpret_vmop_len(vm::Stack& stack) { + int cp = stack.pop_smallint_range(0x7fffffff, -0x80000000); + auto cs = stack.pop_cellslice(); + auto dispatch = vm::DispatchTable::get_table(cp); + if (!dispatch) { + throw IntError{"unknown vm codepage"}; + } + stack.push_smallint(dispatch->instr_len(*cs)); +} + +void interpret_vmop_dump(vm::Stack& stack) { + int cp = stack.pop_smallint_range(0x7fffffff, -0x80000000); + auto cs = stack.pop_cellslice(); + auto dispatch = vm::DispatchTable::get_table(cp); + if (!dispatch) { + throw IntError{"unknown vm codepage"}; + } + auto dump = dispatch->dump_instr(cs.write()); + stack.push_cellslice(std::move(cs)); + stack.push_string(std::move(dump)); +} + void do_interpret_db_run_vm_parallel(std::ostream* stream, vm::Stack& stack, vm::TonDb* ton_db_ptr, int threads_n, int tasks_n) { if (!ton_db_ptr || !*ton_db_ptr) { @@ -2386,17 +2873,17 @@ void interpret_get_fixed_cmdline_arg(vm::Stack& stack, int n) { } // n -- executes $n -void interpret_get_cmdline_arg(IntCtx& ctx) { +Ref interpret_get_cmdline_arg(IntCtx& ctx) { int n = ctx.stack.pop_smallint_range(999999); if (n) { interpret_get_fixed_cmdline_arg(ctx.stack, n); - return; + return {}; } - auto entry = ctx.dictionary->lookup("$0 "); + auto entry = ctx.dictionary.lookup("$0 "); if (!entry) { throw IntError{"-?"}; } else { - (*entry)(ctx); + return entry.get_def(); } } @@ -2429,50 +2916,30 @@ void interpret_getenv_exists(vm::Stack& stack) { } // x1 .. xn n 'w --> -void interpret_execute_internal(IntCtx& ctx) { - Ref word_def = pop_exec_token(ctx); +Ref interpret_execute_internal(IntCtx& ctx) { + Ref word_def = pop_exec_token(ctx); int count = ctx.stack.pop_smallint_range(255); ctx.stack.check_underflow(count); - word_def->run(ctx); + return word_def; } -// wl x1 .. xn n 'w --> wl' -void interpret_compile_internal(vm::Stack& stack) { - Ref word_def = pop_exec_token(stack); - int count = stack.pop_smallint_range(255); - do_compile_literals(stack, count); - if (word_def != Dictionary::nop_word_def) { - do_compile(stack, word_def); - } -} - -void do_compile(vm::Stack& stack, Ref word_def) { +void do_compile(vm::Stack& stack, Ref word_def) { Ref wl_ref = pop_word_list(stack); - if (word_def != Dictionary::nop_word_def) { - if ((td::uint64)word_def->list_size() <= 1) { - // inline short definitions - wl_ref.write().append(*(word_def->get_list())); + if (word_def != nop_word_def) { + auto list_size = word_def->list_size(); + if (list_size >= 0 && (list_size <= 2 || word_def.is_unique())) { + // inline short and unique definitions + auto list = word_def->get_list(); + wl_ref.write().append(list, list + list_size); } else { - wl_ref.write().push_back(word_def); + wl_ref.write().push_back(std::move(word_def)); } } - stack.push({vm::from_object, wl_ref}); + stack.push_object(std::move(wl_ref)); } void compile_one_literal(WordList& wlist, vm::StackEntry val) { - using namespace std::placeholders; - if (val.type() == vm::StackEntry::t_int) { - auto x = std::move(val).as_int(); - if (!x->signed_fits_bits(257)) { - throw IntError{"invalid numeric literal"}; - } else if (x->signed_fits_bits(td::BigIntInfo::word_shift)) { - wlist.push_back(Ref{true, std::bind(interpret_const, _1, x->to_long())}); - } else { - wlist.push_back(Ref{true, std::bind(interpret_big_const, _1, std::move(x))}); - } - } else { - wlist.push_back(Ref{true, std::bind(interpret_literal, _1, std::move(val))}); - } + wlist.push_back(LitCont::literal(std::move(val))); } void do_compile_literals(vm::Stack& stack, int count) { @@ -2484,16 +2951,158 @@ void do_compile_literals(vm::Stack& stack, int count) { if (wl_ref.is_null()) { throw IntError{"list of words expected"}; } - for (int i = count - 1; i >= 0; i--) { - compile_one_literal(wl_ref.write(), std::move(stack[i])); + if (count >= 2) { + std::vector literals; + for (int i = count - 1; i >= 0; i--) { + literals.push_back(std::move(stack[i])); + } + wl_ref.write().push_back(td::make_ref(std::move(literals))); + } else { + for (int i = count - 1; i >= 0; i--) { + compile_one_literal(wl_ref.write(), std::move(stack[i])); + } } stack.pop_many(count + 1); - stack.push({vm::from_object, wl_ref}); + stack.push_object(std::move(wl_ref)); +} + +// wl x1 .. xn n 'w --> wl' +void interpret_compile_internal(vm::Stack& stack) { + Ref word_def = pop_exec_token(stack); + int count = stack.pop_smallint_range(255); + do_compile_literals(stack, count); + if (word_def != nop_word_def) { + do_compile(stack, std::move(word_def)); + } +} + +Ref interpret_compile_execute(IntCtx& ctx) { + if (ctx.state > 0) { + interpret_compile_internal(ctx.stack); + return {}; + } else { + return interpret_execute_internal(ctx); + } +} + +void interpret_seekeof(IntCtx& ctx, int mode) { + if (mode == -1) { + mode = ctx.stack.pop_smallint_range(3, -1); + } + bool eof = true; + if (ctx.parser && (ctx.parser->get_input() || ctx.parser->load_next_line())) { + while (true) { + if (!ctx.parser->is_sb()) { + ctx.parser->skipspc(); + if (*ctx.parser->get_input()) { + eof = false; + break; + } + } + if (mode & 1) { + *ctx.output_stream << " ok" << std::endl; + } + if (!ctx.parser->load_next_line()) { + break; + } + } + } + ctx.stack.push_bool(eof); +} + +void interpret_word_prefix_find(IntCtx& ctx, int mode) { + const char *ptr = ctx.parser->get_input(), *start = ptr; + if (!ptr) { + ctx.stack.push_string(std::string{}); + ctx.stack.push_bool(false); + return; + } + while (*ptr && *ptr != ' ' && *ptr != '\t') { + ptr++; + } + std::string Word{start, ptr}; + Word.push_back(' '); + auto entry = context_lookup(ctx, Word, false); + Word.pop_back(); + if (entry) { + ctx.parser->set_input(ptr); + ctx.parser->skipspc(); + } else { + const char* ptr_end = ptr; + while (true) { + entry = context_lookup(ctx, Word, false); + if (entry) { + ctx.parser->set_input(ptr); + break; + } + if (ptr == start) { + Word = std::string{start, ptr_end}; + ctx.parser->set_input(ptr_end); + ctx.parser->skipspc(); + break; + } + Word.pop_back(); + --ptr; + } + } + ctx.parser->word = Word; + if (!(mode & 2) || !entry) { + ctx.stack.push_string(std::move(Word)); + } + if (mode & 1) { + if (!entry) { + ctx.stack.push_bool(false); + } else { + ctx.stack.push_object(entry.get_def()); + ctx.stack.push_smallint(entry.is_active() ? 1 : -1); + } + } +} + +// equivalent to +// { ?dup { 1+ { execute } { 0 swap } cond } { (number) ?dup 0= abort"-?" 'nop } cond +// } : (interpret-prepare) +Ref interpret_prepare(IntCtx& ctx) { + int found = ctx.stack.pop_smallint_range(1, -1); + if (!found) { + // numbers + interpret_parse_number(ctx); // (number) + interpret_cond_dup(ctx); // ?dup + auto res = ctx.stack.pop_int(); // 0= abort"-?" + if (res == 0) { + throw IntError{"-?"}; + } + ctx.stack.push_object(nop_word_def); // 'nop + return {}; + } else if (found == -1) { + // ordinary word + ctx.stack.push_smallint(0); // 0 + interpret_swap(ctx); // swap + return {}; + } else { + // active word + return pop_exec_token(ctx); // execute + } +} + +Ref InterpretCont::run_tail(IntCtx& ctx) const { + static Ref interpret_prepare_ref = td::make_ref(interpret_prepare); + static Ref compile_exec_ref = td::make_ref(interpret_compile_execute); + interpret_seekeof(ctx, !ctx.state && !ctx.include_depth()); // seekeof + if (ctx.stack.pop_bool()) { + exit_interpret->clear(); + return {}; // exit loop + } + exit_interpret->set({vm::from_object, ctx.next}); // set 'exit-interpret to current continuation + interpret_word_prefix_find(ctx, 3); // (word-prefix-find) + // (interpet-prepare) (compile-execute) and schedule next loop iteration + ctx.next = SeqCont::seq(compile_exec_ref, SeqCont::seq(self(), std::move(ctx.next))); + return interpret_prepare_ref; // (interpret-prepare) } void init_words_common(Dictionary& d) { using namespace std::placeholders; - d.def_word("nop ", Dictionary::nop_word_def); + d.def_word("nop ", nop_word_def); // stack print/dump words d.def_ctx_word(". ", std::bind(interpret_dot, _1, true)); d.def_ctx_word("._ ", std::bind(interpret_dot, _1, false)); @@ -2590,13 +3199,13 @@ void init_words_common(Dictionary& d) { d.def_stack_word("or ", interpret_or); d.def_stack_word("xor ", interpret_xor); // integer constants - d.def_stack_word("false ", std::bind(interpret_const, _1, 0)); - d.def_stack_word("true ", std::bind(interpret_const, _1, -1)); - d.def_stack_word("0 ", std::bind(interpret_const, _1, 0)); - d.def_stack_word("1 ", std::bind(interpret_const, _1, 1)); - d.def_stack_word("2 ", std::bind(interpret_const, _1, 2)); - d.def_stack_word("-1 ", std::bind(interpret_const, _1, -1)); - d.def_stack_word("bl ", std::bind(interpret_const, _1, 32)); + d.def_word("false ", IntLitCont::literal(0)); + d.def_word("true ", IntLitCont::literal(-1)); + d.def_word("0 ", IntLitCont::literal(0)); + d.def_word("1 ", IntLitCont::literal(1)); + d.def_word("2 ", IntLitCont::literal(2)); + d.def_word("-1 ", IntLitCont::literal(-1)); + d.def_word("bl ", IntLitCont::literal(32)); // integer comparison d.def_stack_word("cmp ", std::bind(interpret_cmp, _1, "\xff\x00\x01")); d.def_stack_word("= ", std::bind(interpret_cmp, _1, "\x00\xff\x00")); @@ -2713,12 +3322,22 @@ void init_words_common(Dictionary& d) { d.def_stack_word("ref@+ ", std::bind(interpret_fetch_ref, _1, 2)); d.def_stack_word("ref@? ", std::bind(interpret_fetch_ref, _1, 4)); d.def_stack_word("ref@?+ ", std::bind(interpret_fetch_ref, _1, 6)); + d.def_stack_word("s@ ", std::bind(interpret_fetch_slice, _1, 0)); + d.def_stack_word("sr@ ", std::bind(interpret_fetch_slice, _1, 1)); + d.def_stack_word("s@+ ", std::bind(interpret_fetch_slice, _1, 2)); + d.def_stack_word("sr@+ ", std::bind(interpret_fetch_slice, _1, 3)); + d.def_stack_word("s@? ", std::bind(interpret_fetch_slice, _1, 4)); + d.def_stack_word("sr@? ", std::bind(interpret_fetch_slice, _1, 5)); + d.def_stack_word("s@?+ ", std::bind(interpret_fetch_slice, _1, 6)); + d.def_stack_word("sr@?+ ", std::bind(interpret_fetch_slice, _1, 7)); d.def_stack_word("s> ", interpret_cell_check_empty); d.def_stack_word("empty? ", interpret_cell_empty); d.def_stack_word("remaining ", interpret_cell_remaining); d.def_stack_word("sbits ", std::bind(interpret_slice_bitrefs, _1, 1)); d.def_stack_word("srefs ", std::bind(interpret_slice_bitrefs, _1, 2)); d.def_stack_word("sbitrefs ", std::bind(interpret_slice_bitrefs, _1, 3)); + d.def_stack_word("totalcsize ", std::bind(interpret_cell_datasize, _1, 0)); + d.def_stack_word("totalssize ", std::bind(interpret_cell_datasize, _1, 2)); // boc manipulation d.def_stack_word("B>boc ", interpret_boc_deserialize); d.def_stack_word("boc>B ", interpret_boc_serialize); @@ -2739,6 +3358,18 @@ void init_words_common(Dictionary& d) { d.def_stack_word("crc16 ", interpret_crc16); d.def_stack_word("crc32 ", interpret_crc32); d.def_stack_word("crc32c ", interpret_crc32c); + // hashmaps + d.def_stack_word("hmapnew ", interpret_hmap_new); + d.def_stack_word("hmap@ ", std::bind(interpret_hmap_fetch, _1, 6)); + d.def_stack_word("hmap@? ", std::bind(interpret_hmap_fetch, _1, 5)); + d.def_stack_word("hmap- ", std::bind(interpret_hmap_delete, _1, 0)); + d.def_stack_word("hmap-? ", std::bind(interpret_hmap_delete, _1, 1)); + d.def_stack_word("hmap@- ", std::bind(interpret_hmap_delete, _1, 6)); + d.def_stack_word("hmap! ", std::bind(interpret_hmap_store, _1, 0)); + d.def_stack_word("hmap!+ ", std::bind(interpret_hmap_store, _1, 1)); + d.def_stack_word("hmapempty? ", interpret_hmap_is_empty); + d.def_stack_word("hmapunpack ", std::bind(interpret_hmap_decompose, _1, 1)); + d.def_ctx_tail_word("hmapforeach ", std::bind(interpret_hmap_foreach, _1, 0)); // vm dictionaries d.def_stack_word("dictnew ", interpret_dict_new); d.def_stack_word("dict>s ", interpret_dict_to_slice); @@ -2749,25 +3380,36 @@ void init_words_common(Dictionary& d) { d.def_stack_word("sdict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, false, -1)); d.def_stack_word("b>sdict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, true, -1)); d.def_stack_word("b>sdict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, true, -1)); - d.def_stack_word("sdict@ ", std::bind(interpret_dict_get, _1, -1)); + d.def_stack_word("sdict@ ", std::bind(interpret_dict_get, _1, -1, 3)); + d.def_stack_word("sdict@- ", std::bind(interpret_dict_get, _1, -1, 7)); + d.def_stack_word("sdict- ", std::bind(interpret_dict_get, _1, -1, 5)); d.def_stack_word("udict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, false, 0)); d.def_stack_word("udict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, false, 0)); d.def_stack_word("b>udict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, true, 0)); d.def_stack_word("b>udict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, true, 0)); - d.def_stack_word("udict@ ", std::bind(interpret_dict_get, _1, 0)); + d.def_stack_word("udict@ ", std::bind(interpret_dict_get, _1, 0, 3)); + d.def_stack_word("udict@- ", std::bind(interpret_dict_get, _1, 0, 7)); + d.def_stack_word("udict- ", std::bind(interpret_dict_get, _1, 0, 5)); d.def_stack_word("idict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, false, 1)); d.def_stack_word("idict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, false, 1)); d.def_stack_word("b>idict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, true, 1)); d.def_stack_word("b>idict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, true, 1)); - d.def_stack_word("idict@ ", std::bind(interpret_dict_get, _1, 1)); + d.def_stack_word("idict@ ", std::bind(interpret_dict_get, _1, 1, 3)); + d.def_stack_word("idict@- ", std::bind(interpret_dict_get, _1, 1, 7)); + d.def_stack_word("idict- ", std::bind(interpret_dict_get, _1, 1, 5)); d.def_stack_word("pfxdict!+ ", std::bind(interpret_pfx_dict_add, _1, vm::Dictionary::SetMode::Add, false)); d.def_stack_word("pfxdict! ", std::bind(interpret_pfx_dict_add, _1, vm::Dictionary::SetMode::Set, false)); d.def_stack_word("pfxdict@ ", interpret_pfx_dict_get); - d.def_ctx_word("dictmap ", interpret_dict_map); - d.def_ctx_word("dictmapext ", interpret_dict_map_ext); - d.def_ctx_word("dictforeach ", interpret_dict_foreach); - d.def_ctx_word("dictmerge ", interpret_dict_merge); - d.def_ctx_word("dictdiff ", interpret_dict_diff); + d.def_ctx_tail_word("dictmap ", std::bind(interpret_dict_map, _1, false, false)); + d.def_ctx_tail_word("dictmapext ", std::bind(interpret_dict_map, _1, true, false)); + d.def_ctx_tail_word("idictmapext ", std::bind(interpret_dict_map, _1, true, true)); + d.def_ctx_tail_word("dictforeach ", std::bind(interpret_dict_foreach, _1, false, false)); + d.def_ctx_tail_word("idictforeach ", std::bind(interpret_dict_foreach, _1, false, true)); + d.def_ctx_tail_word("dictforeachrev ", std::bind(interpret_dict_foreach, _1, true, false)); + d.def_ctx_tail_word("idictforeachrev ", std::bind(interpret_dict_foreach, _1, true, true)); + d.def_ctx_tail_word("dictforeachfromx ", std::bind(interpret_dict_foreach_from, _1, -1)); + d.def_ctx_tail_word("dictmerge ", interpret_dict_merge); + d.def_ctx_tail_word("dictdiff ", interpret_dict_diff); // slice/bitstring constants d.def_active_word("x{", interpret_bitstring_hex_literal); d.def_active_word("b{", interpret_bitstring_binary_literal); @@ -2799,12 +3441,13 @@ void init_words_common(Dictionary& d) { d.def_stack_word("atom? ", interpret_is_atom); // execution control d.def_ctx_tail_word("execute ", interpret_execute); + d.def_ctx_tail_word("call/cc ", interpret_call_cc); d.def_ctx_tail_word("times ", interpret_execute_times); d.def_ctx_tail_word("if ", interpret_if); d.def_ctx_tail_word("ifnot ", interpret_ifnot); d.def_ctx_tail_word("cond ", interpret_cond); - d.def_ctx_word("while ", interpret_while); - d.def_ctx_word("until ", interpret_until); + d.def_ctx_tail_word("while ", interpret_while); + d.def_ctx_tail_word("until ", interpret_until); // compiler control d.def_active_word("[ ", interpret_internal_interpret_begin); d.def_active_word("] ", interpret_internal_interpret_end); @@ -2813,11 +3456,13 @@ void init_words_common(Dictionary& d) { d.def_stack_word("({) ", interpret_wordlist_begin_aux); d.def_stack_word("(}) ", interpret_wordlist_end_aux); d.def_stack_word("(compile) ", interpret_compile_internal); - d.def_ctx_word("(execute) ", interpret_execute_internal); + d.def_ctx_tail_word("(execute) ", interpret_execute_internal); + d.def_ctx_tail_word("(interpret-prepare) ", interpret_prepare); d.def_active_word("' ", interpret_tick); - d.def_stack_word("'nop ", interpret_tick_nop); + d.def_word("'nop ", LitCont::literal({vm::from_object, nop_word_def})); // dictionary manipulation - d.def_ctx_word("find ", interpret_find); + d.def_ctx_word("find ", std::bind(interpret_find, _1, 1)); + d.def_ctx_word("(word-prefix-find) ", std::bind(interpret_word_prefix_find, _1, 3)); d.def_ctx_word("create ", interpret_create); d.def_ctx_word("(create) ", std::bind(interpret_create_aux, _1, -1)); d.def_active_word(": ", std::bind(interpret_colon, _1, 0)); @@ -2827,20 +3472,32 @@ void init_words_common(Dictionary& d) { d.def_ctx_word("(forget) ", interpret_forget_aux); d.def_ctx_word("forget ", interpret_forget); d.def_ctx_word("words ", interpret_words); + d.def_word("Fift-wordlist ", LitCont::literal(d.get_box())); + d.def_ctx_word("Fift ", std::bind(interpret_set_context_to, _1, d.get_box())); + d.def_ctx_word("current@ ", interpret_get_current); + d.def_ctx_word("current! ", interpret_set_current); + d.def_ctx_word("context@ ", interpret_get_context); + d.def_ctx_word("context! ", interpret_set_context); + d.def_ctx_word(".bt ", interpret_print_backtrace); + d.def_ctx_word("cont. ", interpret_print_continuation); // input parse d.def_ctx_word("word ", interpret_word); d.def_ctx_word("(word) ", interpret_word_ext); d.def_ctx_word("skipspc ", interpret_skipspc); - d.def_ctx_word("include ", interpret_include); - d.def_stack_word("skip-to-eof ", interpret_skip_source); + d.def_ctx_word("seekeof? ", std::bind(interpret_seekeof, _1, 1)); + d.def_ctx_word("(seekeof?) ", std::bind(interpret_seekeof, _1, -1)); + d.def_ctx_word("include-depth ", interpret_include_depth); + d.def_ctx_tail_word("include ", interpret_include); + d.def_ctx_tail_word("skip-to-eof ", interpret_skip_source); + d.def_word("'exit-interpret ", LitCont::literal(exit_interpret)); d.def_ctx_word("abort ", interpret_abort); d.def_ctx_word("quit ", interpret_quit); d.def_ctx_word("bye ", interpret_bye); - d.def_stack_word("halt ", interpret_halt); + d.def_ctx_word("halt ", interpret_halt); // cmdline args - d.def_stack_word("$* ", std::bind(interpret_literal, _1, vm::StackEntry{cmdline_args})); + d.def_word("$* ", LitCont::literal(cmdline_args)); d.def_stack_word("$# ", interpret_get_cmdline_arg_count); - d.def_ctx_word("$() ", interpret_get_cmdline_arg); + d.def_ctx_tail_word("$() ", interpret_get_cmdline_arg); } void init_words_ton(Dictionary& d) { @@ -2853,11 +3510,11 @@ void init_words_ton(Dictionary& d) { d.def_stack_word("base64url>B ", std::bind(interpret_base64_to_bytes, _1, true, false)); } -void init_words_vm(Dictionary& d) { +void init_words_vm(Dictionary& d, bool enable_debug) { using namespace std::placeholders; - vm::init_op_cp0(); + vm::init_vm(enable_debug).ensure(); // vm run - d.def_stack_word("vmlibs ", std::bind(interpret_literal, _1, vm::StackEntry{vm_libraries})); + d.def_word("vmlibs ", LitCont::literal(vm_libraries)); // d.def_ctx_word("runvmcode ", std::bind(interpret_run_vm, _1, 0x40)); // d.def_ctx_word("runvm ", std::bind(interpret_run_vm, _1, 0x45)); d.def_ctx_word("runvmx ", std::bind(interpret_run_vm, _1, -1)); @@ -2865,12 +3522,14 @@ void init_words_vm(Dictionary& d) { d.def_ctx_word("dbrunvm-parallel ", interpret_db_run_vm_parallel); d.def_stack_word("vmcont, ", interpret_store_vm_cont); d.def_stack_word("vmcont@ ", interpret_fetch_vm_cont); + d.def_stack_word("(vmoplen) ", interpret_vmop_len); + d.def_stack_word("(vmopdump) ", interpret_vmop_dump); } void import_cmdline_args(Dictionary& d, std::string arg0, int n, const char* const argv[]) { using namespace std::placeholders; LOG(DEBUG) << "import_cmdlist_args(" << arg0 << "," << n << ")"; - d.def_stack_word("$0 ", std::bind(interpret_literal, _1, vm::StackEntry{arg0})); + d.def_word("$0 ", LitCont::literal(arg0)); vm::StackEntry list; for (int i = n - 1; i >= 0; i--) { list = vm::StackEntry::cons(vm::StackEntry{argv[i]}, list); @@ -2883,122 +3542,4 @@ void import_cmdline_args(Dictionary& d, std::string arg0, int n, const char* con } } -std::pair numeric_value_ext(std::string s, bool allow_frac = true) { - td::RefInt256 num, denom; - int res = parse_number(s, num, denom, allow_frac); - if (res <= 0) { - throw IntError{"-?"}; - } - return std::make_pair(std::move(num), res == 2 ? std::move(denom) : td::RefInt256{}); -} - -td::RefInt256 numeric_value(std::string s) { - td::RefInt256 num, denom; - int res = parse_number(s, num, denom, false); - if (res != 1) { - throw IntError{"-?"}; - } - return num; -} - -int funny_interpret_loop(IntCtx& ctx) { - while (ctx.load_next_line()) { - if (ctx.is_sb()) { - continue; - } - std::ostringstream errs; - bool ok = true; - while (ok) { - ctx.skipspc(); - const char* ptr = ctx.get_input(); - if (!*ptr) { - break; - } - std::string Word; - Word.reserve(128); - auto entry = ctx.dictionary->lookup(""); - std::string entry_word; - const char* ptr_end = ptr; - while (*ptr && *ptr != ' ' && *ptr != '\t') { - Word += *ptr++; - auto cur = ctx.dictionary->lookup(Word); - if (cur) { - entry = cur; - entry_word = Word; - ptr_end = ptr; - } - } - auto cur = ctx.dictionary->lookup(Word + " "); - if (cur || !entry) { - entry = std::move(cur); - ctx.set_input(ptr); - ctx.skipspc(); - } else { - Word = entry_word; - ctx.set_input(ptr_end); - } - try { - if (entry) { - if (entry->is_active()) { - (*entry)(ctx); - } else { - ctx.stack.push_smallint(0); - ctx.stack.push({vm::from_object, entry->get_def()}); - } - } else { - auto res = numeric_value_ext(Word); - ctx.stack.push(std::move(res.first)); - if (res.second.not_null()) { - ctx.stack.push(std::move(res.second)); - push_argcount(ctx, 2); - } else { - push_argcount(ctx, 1); - } - } - if (ctx.state > 0) { - interpret_compile_internal(ctx.stack); - } else { - interpret_execute_internal(ctx); - } - } catch (IntError& ab) { - errs << ctx << Word << ": " << ab.msg; - ok = false; - } catch (vm::VmError& ab) { - errs << ctx << Word << ": " << ab.get_msg(); - ok = false; - } catch (vm::CellBuilder::CellWriteError) { - errs << ctx << Word << ": Cell builder write error"; - ok = false; - } catch (vm::VmFatal) { - errs << ctx << Word << ": fatal vm error"; - ok = false; - } catch (Quit& q) { - if (ctx.include_depth) { - throw; - } - if (!q.res) { - ok = false; - } else { - return q.res; - } - } catch (SkipToEof) { - return 0; - } - }; - if (!ok) { - auto err_msg = errs.str(); - if (!err_msg.empty()) { - LOG(ERROR) << err_msg; - } - ctx.clear(); - if (ctx.include_depth) { - throw IntError{"error interpreting included file `" + ctx.filename + "` : " + err_msg}; - } - } else if (!ctx.state && !ctx.include_depth) { - *ctx.output_stream << " ok" << std::endl; - } - } - return 0; -} - } // namespace fift diff --git a/crypto/fift/words.h b/crypto/fift/words.h index eac182e4..65466b70 100644 --- a/crypto/fift/words.h +++ b/crypto/fift/words.h @@ -14,30 +14,17 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "Dictionary.h" namespace fift { -// thrown by 'quit', 'bye' and 'halt' for exiting to top level -struct Quit { - int res; - Quit() : res(0) { - } - Quit(int _res) : res(_res) { - } -}; - -struct SkipToEof {}; - void init_words_common(Dictionary& dictionary); -void init_words_vm(Dictionary& dictionary); +void init_words_vm(Dictionary& dictionary, bool debug_enabled = false); void init_words_ton(Dictionary& dictionary); void import_cmdline_args(Dictionary& d, std::string arg0, int n, const char* const argv[]); -int funny_interpret_loop(IntCtx& ctx); - } // namespace fift diff --git a/crypto/func/abscode.cpp b/crypto/func/abscode.cpp index 4a280b82..63ef3b90 100644 --- a/crypto/func/abscode.cpp +++ b/crypto/func/abscode.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "func.h" @@ -38,6 +38,9 @@ TmpVar::TmpVar(var_idx_t _idx, int _cls, TypeExpr* _type, SymDef* sym, const Src if (!_type) { v_type = TypeExpr::new_hole(); } + if (cls == _Named) { + undefined = true; + } } void TmpVar::set_location(const SrcLocation& loc) { @@ -138,7 +141,7 @@ void VarDescr::show(std::ostream& os, const char* name) const { } void VarDescr::set_const(long long value) { - return set_const(td::RefInt256{true, value}); + return set_const(td::make_refint(value)); } void VarDescr::set_const(td::RefInt256 value) { @@ -158,9 +161,9 @@ void VarDescr::set_const(td::RefInt256 value) { } else if (s > 0) { val |= _NonZero | _Pos | _Finite; } else if (!s) { - if (*int_const == 1) { - val |= _Bit; - } + //if (*int_const == 1) { + // val |= _Bit; + //} val |= _Zero | _Neg | _Pos | _Finite | _Bool | _Bit; } if (val & _Finite) { @@ -168,13 +171,18 @@ void VarDescr::set_const(td::RefInt256 value) { } } +void VarDescr::set_const(std::string value) { + str_const = value; + val = _Const; +} + void VarDescr::set_const_nan() { - set_const(td::RefInt256{true}); + set_const(td::make_refint()); } void VarDescr::operator|=(const VarDescr& y) { val &= y.val; - if (is_int_const() && cmp(int_const, y.int_const) != 0) { + if (is_int_const() && y.is_int_const() && cmp(int_const, y.int_const) != 0) { val &= ~_Const; } if (!(val & _Const)) { @@ -324,11 +332,30 @@ void Op::show(std::ostream& os, const std::vector& vars, std::string pfx show_var_list(os, right, vars); os << std::endl; break; + case _Tuple: + os << pfx << dis << "MKTUPLE "; + show_var_list(os, left, vars); + os << " := "; + show_var_list(os, right, vars); + os << std::endl; + break; + case _UnTuple: + os << pfx << dis << "UNTUPLE "; + show_var_list(os, left, vars); + os << " := "; + show_var_list(os, right, vars); + os << std::endl; + break; case _IntConst: os << pfx << dis << "CONST "; show_var_list(os, left, vars); os << " := " << int_const << std::endl; break; + case _SliceConst: + os << pfx << dis << "SCONST "; + show_var_list(os, left, vars); + os << " := " << str_const << std::endl; + break; case _Import: os << pfx << dis << "IMPORT "; show_var_list(os, left, vars); diff --git a/crypto/func/analyzer.cpp b/crypto/func/analyzer.cpp index 89e2e7b7..7911b9b4 100644 --- a/crypto/func/analyzer.cpp +++ b/crypto/func/analyzer.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "func.h" @@ -29,6 +29,7 @@ namespace funC { void CodeBlob::simplify_var_types() { for (TmpVar& var : vars) { TypeExpr::remove_indirect(var.v_type); + var.v_type->recompute_width(); } } @@ -41,14 +42,14 @@ int CodeBlob::split_vars(bool strict) { } std::vector comp_types; int k = var.v_type->extract_components(comp_types); - assert(k <= 254 && n <= 0x7fff00); - assert((unsigned)k == comp_types.size()); + func_assert(k <= 254 && n <= 0x7fff00); + func_assert((unsigned)k == comp_types.size()); if (k != 1) { var.coord = ~((n << 8) + k); for (int i = 0; i < k; i++) { auto v = create_var(vars[j].cls, comp_types[i], 0, vars[j].where.get()); - assert(v == n + i); - assert(vars[v].idx == v); + func_assert(v == n + i); + func_assert(vars[v].idx == v); vars[v].name = vars[j].name; vars[v].coord = ((int)j << 8) + i + 1; } @@ -74,9 +75,9 @@ bool CodeBlob::compute_used_code_vars() { } bool CodeBlob::compute_used_code_vars(std::unique_ptr& ops_ptr, const VarDescrList& var_info, bool edit) const { - assert(ops_ptr); + func_assert(ops_ptr); if (!ops_ptr->next) { - assert(ops_ptr->cl == Op::_Nop); + func_assert(ops_ptr->cl == Op::_Nop); return ops_ptr->set_var_info(var_info); } return compute_used_code_vars(ops_ptr->next, var_info, edit) | ops_ptr->compute_used_vars(*this, edit); @@ -214,7 +215,7 @@ VarDescrList& VarDescrList::operator-=(const std::vector& idx_list) { VarDescrList& VarDescrList::add_var(var_idx_t idx, bool unused, bool replaced) { auto it = std::lower_bound(list.begin(), list.end(), idx); if (it == list.end() || it->idx != idx) { - list.emplace(it, idx, VarDescr::_Last | (unused ? VarDescr::_Unused : 0) + list.emplace(it, idx, VarDescr::_Last | (unused ? VarDescr::_Unused : 0) | (replaced ? (VarDescr::_Unused | VarDescr::_Replaced) : 0)); } else if (it->is_unused() && !unused) { it->clear_unused(); @@ -359,16 +360,19 @@ bool Op::std_compute_used_vars(bool disabled, bool replaced) { } bool Op::compute_used_vars(const CodeBlob& code, bool edit) { - assert(next); + func_assert(next); const VarDescrList& next_var_info = next->var_info; if (cl == _Nop) { return set_var_info_except(next_var_info, left); } switch (cl) { case _IntConst: + case _SliceConst: case _GlobVar: case _Call: - case _CallInd: { + case _CallInd: + case _Tuple: + case _UnTuple: { // left = EXEC right; if (!next_var_info.count_used(left) && is_pure()) { // all variables in `left` are not needed @@ -392,8 +396,8 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) { case _Let: { // left = right std::size_t cnt = next_var_info.count_used(left); - std::size_t unr = next_var_info.count_unreplaced(left); - assert(left.size() == right.size()); + std::size_t unr = next_var_info.count_unreplaced(left); + func_assert(left.size() == right.size()); auto l_it = left.cbegin(), r_it = right.cbegin(); VarDescrList new_var_info{next_var_info}; new_var_info -= left; @@ -402,7 +406,7 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) { for (; l_it < left.cend(); ++l_it, ++r_it) { if (std::find(l_it + 1, left.cend(), *l_it) == left.cend()) { auto p = next_var_info[*l_it]; - new_var_info.add_var(*r_it, !p || p->is_unused(), p && p->is_replaced()); + new_var_info.add_var(*r_it, edit && (!p || p->is_unused()), p && p->is_replaced()); new_left.push_back(*l_it); new_right.push_back(*r_it); } @@ -516,7 +520,12 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) { } changes = (new_var_info.size() == n); } while (changes <= edit); + func_assert(left.size() == 1); + bool last = new_var_info.count_used(left) == 0; new_var_info += left; + if (last) { + new_var_info[left[0]]->flags |= VarDescr::_Last; + } return set_var_info(std::move(new_var_info)); } case _Again: { @@ -536,6 +545,14 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) { } while (changes <= edit); return set_var_info(std::move(new_var_info)); } + case _TryCatch: { + code.compute_used_code_vars(block0, next_var_info, edit); + code.compute_used_code_vars(block1, next_var_info, edit); + VarDescrList merge_info = block0->var_info + block1->var_info + next_var_info; + merge_info -= left; + merge_info.clear_last(); + return set_var_info(std::move(merge_info)); + } default: std::cerr << "fatal: unknown operation in compute_used_vars()\n"; throw src::ParseError{where, "unknown operation"}; @@ -557,10 +574,13 @@ bool prune_unreachable(std::unique_ptr& ops) { bool reach; switch (op.cl) { case Op::_IntConst: + case Op::_SliceConst: case Op::_GlobVar: case Op::_SetGlob: case Op::_Call: case Op::_CallInd: + case Op::_Tuple: + case Op::_UnTuple: case Op::_Import: reach = true; break; @@ -599,7 +619,7 @@ bool prune_unreachable(std::unique_ptr& ops) { // block1 never executed op.block0->last().next = std::move(op.next); ops = std::move(op.block0); - return false; + return prune_unreachable(ops); } else if (c_var && c_var->always_true()) { if (!prune_unreachable(op.block1)) { // block1 never returns @@ -655,7 +675,11 @@ bool prune_unreachable(std::unique_ptr& ops) { ops = std::move(op.block0); return false; } - reach = true; + reach = (op.cl != Op::_Again); + break; + } + case Op::_TryCatch: { + reach = prune_unreachable(op.block0) | prune_unreachable(op.block1); break; } default: @@ -680,7 +704,7 @@ void CodeBlob::prune_unreachable_code() { void CodeBlob::fwd_analyze() { VarDescrList values; - assert(ops && ops->cl == Op::_Import); + func_assert(ops && ops->cl == Op::_Import); for (var_idx_t i : ops->left) { values += i; if (vars[i].v_type->is_int()) { @@ -722,6 +746,10 @@ VarDescrList Op::fwd_analyze(VarDescrList values) { values.add_newval(left[0]).set_const(int_const); break; } + case _SliceConst: { + values.add_newval(left[0]).set_const(str_const); + break; + } case _Call: { prepare_args(values); auto func = dynamic_cast(fun_ref->value); @@ -732,7 +760,7 @@ VarDescrList Op::fwd_analyze(VarDescrList values) { res.emplace_back(i); } AsmOpList tmp; - func->compile(tmp, res, args); // abstract interpretation of res := f (args) + func->compile(tmp, res, args, where); // abstract interpretation of res := f (args) int j = 0; for (var_idx_t i : left) { values.add_newval(i).set_value(res[j++]); @@ -744,6 +772,8 @@ VarDescrList Op::fwd_analyze(VarDescrList values) { } break; } + case _Tuple: + case _UnTuple: case _GlobVar: case _CallInd: { for (var_idx_t i : left) { @@ -755,7 +785,7 @@ VarDescrList Op::fwd_analyze(VarDescrList values) { break; case _Let: { std::vector old_val; - assert(left.size() == right.size()); + func_assert(left.size() == right.size()); for (std::size_t i = 0; i < right.size(); i++) { const VarDescr* ov = values[right[i]]; if (!ov && verbosity >= 5) { @@ -770,7 +800,7 @@ VarDescrList Op::fwd_analyze(VarDescrList values) { } std::cerr << std::endl; } - // assert(ov); + // func_assert(ov); if (ov) { old_val.push_back(*ov); } else { @@ -805,6 +835,7 @@ VarDescrList Op::fwd_analyze(VarDescrList values) { break; } case _While: { + auto values0 = values; values = block0->fwd_analyze(values); if (values[left[0]] && values[left[0]]->always_false()) { // block1 never executed @@ -812,7 +843,7 @@ VarDescrList Op::fwd_analyze(VarDescrList values) { break; } while (true) { - VarDescrList next_values = values | block0->fwd_analyze(block1->fwd_analyze(values)); + VarDescrList next_values = values | block0->fwd_analyze(values0 | block1->fwd_analyze(values)); if (same_values(next_values, values)) { break; } @@ -832,6 +863,12 @@ VarDescrList Op::fwd_analyze(VarDescrList values) { values = block0->fwd_analyze(values); break; } + case _TryCatch: { + VarDescrList val1 = block0->fwd_analyze(values); + VarDescrList val2 = block1->fwd_analyze(std::move(values)); + values = val1 | val2; + break; + } default: std::cerr << "fatal: unknown operation \n"; throw src::ParseError{where, "unknown operation in fwd_analyze()"}; @@ -861,7 +898,10 @@ bool Op::mark_noreturn() { // fallthrough case _Import: case _IntConst: + case _SliceConst: case _Let: + case _Tuple: + case _UnTuple: case _SetGlob: case _GlobVar: case _CallInd: @@ -870,10 +910,11 @@ bool Op::mark_noreturn() { case _Return: return set_noreturn(true); case _If: + case _TryCatch: return set_noreturn((block0->mark_noreturn() & (block1 && block1->mark_noreturn())) | next->mark_noreturn()); case _Again: block0->mark_noreturn(); - return set_noreturn(false); + return set_noreturn(true); case _Until: return set_noreturn(block0->mark_noreturn() | next->mark_noreturn()); case _While: diff --git a/crypto/func/asmops.cpp b/crypto/func/asmops.cpp index df4443a4..71ee58f6 100644 --- a/crypto/func/asmops.cpp +++ b/crypto/func/asmops.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "parser/srcread.h" #include "func.h" @@ -55,10 +55,10 @@ std::ostream& operator<<(std::ostream& os, AsmOp::SReg stack_reg) { } } -AsmOp AsmOp::Const(int arg, std::string push_op) { +AsmOp AsmOp::Const(int arg, std::string push_op, td::RefInt256 origin) { std::ostringstream os; os << arg << ' ' << push_op; - return AsmOp::Const(os.str()); + return AsmOp::Const(os.str(), origin); } AsmOp AsmOp::make_stk2(int a, int b, const char* str, int delta) { @@ -121,32 +121,72 @@ AsmOp AsmOp::BlkDrop(int a) { return AsmOp::Custom(os.str(), a, 0); } +AsmOp AsmOp::BlkDrop2(int a, int b) { + if (!b) { + return BlkDrop(a); + } + std::ostringstream os; + os << a << " " << b << " BLKDROP2"; + return AsmOp::Custom(os.str(), a + b, b); +} + AsmOp AsmOp::BlkReverse(int a, int b) { std::ostringstream os; os << a << " " << b << " REVERSE"; return AsmOp::Custom(os.str(), a + b, a + b); } +AsmOp AsmOp::Tuple(int a) { + switch (a) { + case 1: + return AsmOp::Custom("SINGLE", 1, 1); + case 2: + return AsmOp::Custom("PAIR", 2, 1); + case 3: + return AsmOp::Custom("TRIPLE", 3, 1); + } + std::ostringstream os; + os << a << " TUPLE"; + return AsmOp::Custom(os.str(), a, 1); +} + +AsmOp AsmOp::UnTuple(int a) { + switch (a) { + case 1: + return AsmOp::Custom("UNSINGLE", 1, 1); + case 2: + return AsmOp::Custom("UNPAIR", 1, 2); + case 3: + return AsmOp::Custom("UNTRIPLE", 1, 3); + } + std::ostringstream os; + os << a << " UNTUPLE"; + return AsmOp::Custom(os.str(), 1, a); +} + AsmOp AsmOp::IntConst(td::RefInt256 x) { if (x->signed_fits_bits(8)) { - return AsmOp::Const(dec_string(std::move(x)) + " PUSHINT"); + return AsmOp::Const(dec_string(x) + " PUSHINT", x); } if (!x->is_valid()) { - return AsmOp::Const("PUSHNAN"); + return AsmOp::Const("PUSHNAN", x); } int k = is_pos_pow2(x); if (k >= 0) { - return AsmOp::Const(k, "PUSHPOW2"); + return AsmOp::Const(k, "PUSHPOW2", x); } k = is_pos_pow2(x + 1); if (k >= 0) { - return AsmOp::Const(k, "PUSHPOW2DEC"); + return AsmOp::Const(k, "PUSHPOW2DEC", x); } k = is_pos_pow2(-x); if (k >= 0) { - return AsmOp::Const(k, "PUSHNEGPOW2"); + return AsmOp::Const(k, "PUSHNEGPOW2", x); } - return AsmOp::Const(dec_string(std::move(x)) + " PUSHINT"); + if (!x->mod_pow2_short(23)) { + return AsmOp::Const(dec_string(x) + " PUSHINTX", x); + } + return AsmOp::Const(dec_string(x) + " PUSHINT", x); } AsmOp AsmOp::BoolConst(bool f) { @@ -325,6 +365,8 @@ bool apply_op(StackTransform& trans, const AsmOp& op) { return trans.apply_pop(op.a); case AsmOp::a_const: return !op.a && op.b == 1 && trans.apply_push_newconst(); + case AsmOp::a_custom: + return op.is_gconst() && trans.apply_push_newconst(); default: return false; } diff --git a/crypto/func/auto-tests/legacy_tester.js b/crypto/func/auto-tests/legacy_tester.js new file mode 100644 index 00000000..57092d68 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tester.js @@ -0,0 +1,27 @@ +const fs = require('fs/promises'); +const { compileWasm, compileFile } = require('./wasm_tests_common'); + +async function main() { + const tests = JSON.parse((await fs.readFile('../legacy_tests.json')).toString('utf-8')) + + for (const [filename, hashstr] of tests) { + if (filename.includes('storage-provider')) continue; + + const mod = await compileWasm() + + const response = await compileFile(mod, filename); + + if (response.status !== 'ok') { + console.error(response); + throw new Error('Could not compile ' + filename); + } + + if (BigInt('0x' + response.codeHashHex) !== BigInt(hashstr)) { + throw new Error('Compilation result is different for ' + filename); + } + + console.log(filename, 'ok'); + } +} + +main() \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tester.py b/crypto/func/auto-tests/legacy_tester.py new file mode 100644 index 00000000..9a990501 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tester.py @@ -0,0 +1,151 @@ +import os +import os.path +import subprocess +import sys +import tempfile +import shutil + +add_pragmas = [] #["allow-post-modification", "compute-asm-ltr"]; + +tests = [ + # note, that deployed version of elector,config and multisig differ since it is compilled with func-0.1.0. + # Newer compillators optimize arithmetic and logic expression that can be calculated at the compile time + ["elector/elector-code.fc", 115226404411715505328583639896096915745686314074575650766750648324043316883483], + ["config/config-code.fc", 10913070768607625342121305745084703121685937915388357634624451844356456145601], + ["eth-bridge-multisig/multisig-code.fc", 101509909129354488841890823627011033360100627957439967918234053299675481277954], + + ["bsc-bridge-collector/votes-collector.fc", 62190447221288642706570413295807615918589884489514159926097051017036969900417], + ["uni-lock-wallet/uni-lockup-wallet.fc", 61959738324779104851267145467044677651344601417998258530238254441977103654381], + ["nft-collection/nft-collection-editable.fc", 45561997735512210616567774035540357815786262097548276229169737015839077731274], + ["dns-collection/nft-collection.fc", 107999822699841936063083742021519765435859194241091312445235370766165379261859], + + + # note, that deployed version of tele-nft-item differs since it is compilled with func-0.3.0. + # After introducing of try/catch construction, c2 register is not always the default one. + # Thus it is necessary to save it upon jumps, differences of deployed and below compilled is that + # "c2 SAVE" is added to the beginning of recv_internal. It does not change behavior. + ["tele-nft-item/nft-item.fc", 69777543125381987786450436977742010705076866061362104025338034583422166453344], + + ["storage/storage-contract.fc", 91377830060355733016937375216020277778264560226873154627574229667513068328151], + ["storage/storage-provider.fc", 13618336676213331164384407184540461509022654507176709588621016553953760588122], + ["nominator-pool/pool.fc", 69767057279163099864792356875696330339149706521019810113334238732928422055375], + ["jetton-minter/jetton-minter.fc", 9028309926287301331466371999814928201427184114165428257502393474125007156494], + ["gg-marketplace/nft-marketplace-v2.fc", 92199806964112524639740773542356508485601908152150843819273107618799016205930], + ["jetton-wallet/jetton-wallet.fc", 86251125787443633057458168028617933212663498001665054651523310772884328206542], + ["whales-nominators/nominators.fc", 8941364499854379927692172316865293429893094891593442801401542636695127885153], + + + ["tact-examples/treasure_Treasure.code.fc", 13962538639825790677138656603323869918938565499584297120566680287245364723897], + ["tact-examples/jetton_SampleJetton.code.fc", 94076762218493729104783735200107713211245710256802265203823917715299139499110], + ["tact-examples/jetton_JettonDefaultWallet.code.fc", 29421313492520031238091587108198906058157443241743283101866538036369069620563], + ["tact-examples/maps_MapTestContract.code.fc", 22556550222249123835909180266811414538971143565993192846012583552876721649744], +] + +def getenv(name, default=None): + if name in os.environ: + return os.environ[name] + if default is None: + print("Environment variable", name, "is not set", file=sys.stderr) + exit(1) + return default + +FUNC_EXECUTABLE = getenv("FUNC_EXECUTABLE", "func") +FIFT_EXECUTABLE = getenv("FIFT_EXECUTABLE", "fift") +TMP_DIR = tempfile.mkdtemp() + +COMPILED_FIF = os.path.join(TMP_DIR, "compiled.fif") +RUNNER_FIF = os.path.join(TMP_DIR, "runner.fif") + +TESTS_DIR = "legacy_tests" + +class ExecutionError(Exception): + pass + +def pre_process_func(f): + shutil.copyfile(f, f+"_backup") + with open(f, "r") as src: + sources = src.read() + with open(f, "w") as src: + for pragma in add_pragmas: + src.write("#pragma %s;\n"%pragma) + src.write(sources) + +def post_process_func(f): + shutil.move(f+"_backup", f) + +def compile_func(f): + res = None + try: + pre_process_func(f) + if "storage-provider.fc" in f : + # This contract requires building of storage-contract to include it as ref + with open(f, "r") as src: + sources = src.read() + COMPILED_ST_BOC = os.path.join(TMP_DIR, "storage-contract-code.boc") + sources = sources.replace("storage-contract-code.boc", COMPILED_ST_BOC) + with open(f, "w") as src: + src.write(sources) + COMPILED_ST_FIF = os.path.join(TMP_DIR, "storage-contract.fif") + COMPILED_ST_BOC = os.path.join(TMP_DIR, "storage-contract-code.boc") + COMPILED_BUILD_BOC = os.path.join(TMP_DIR, "build-boc.fif") + res = subprocess.run([FUNC_EXECUTABLE, "-o", COMPILED_ST_FIF, "-SPA", f.replace("storage-provider.fc","storage-contract.fc")], capture_output=False, timeout=10) + with open(COMPILED_BUILD_BOC, "w") as scr: + scr.write("\"%s\" include boc>B \"%s\" B>file "%(COMPILED_ST_FIF, COMPILED_ST_BOC)) + res = subprocess.run([FIFT_EXECUTABLE, COMPILED_BUILD_BOC ], capture_output=True, timeout=10) + + + res = subprocess.run([FUNC_EXECUTABLE, "-o", COMPILED_FIF, "-SPA", f], capture_output=True, timeout=10) + except Exception as e: + post_process_func(f) + raise e + else: + post_process_func(f) + if res.returncode != 0: + raise ExecutionError(str(res.stderr, "utf-8")) + +def run_runner(): + res = subprocess.run([FIFT_EXECUTABLE, RUNNER_FIF], capture_output=True, timeout=10) + if res.returncode != 0: + raise ExecutionError(str(res.stderr, "utf-8")) + s = str(res.stdout, "utf-8") + s = s.strip() + return int(s) + +def get_version(): + res = subprocess.run([FUNC_EXECUTABLE, "-s"], capture_output=True, timeout=10) + if res.returncode != 0: + raise ExecutionError(str(res.stderr, "utf-8")) + s = str(res.stdout, "utf-8") + return s.strip() + +success = 0 +for ti, t in enumerate(tests): + tf, th = t + print(" Running test %d/%d: %s" % (ti + 1, len(tests), tf), file=sys.stderr) + tf = os.path.join(TESTS_DIR, tf) + try: + compile_func(tf) + except ExecutionError as e: + print(file=sys.stderr) + print("Compilation error", file=sys.stderr) + print(e, file=sys.stderr) + exit(2) + + with open(RUNNER_FIF, "w") as f: + print("\"%s\" include hash .s" % COMPILED_FIF , file=f) + + try: + func_out = run_runner() + if func_out != th: + raise ExecutionError("Error : expected '%d', found '%d'" % (th, func_out)) + success += 1 + except ExecutionError as e: + print(e, file=sys.stderr) + print("Compiled:", file=sys.stderr) + with open(COMPILED_FIF, "r") as f: + print(f.read(), file=sys.stderr) + exit(2) + print(" OK ", file=sys.stderr) + +print(get_version()) +print("Done: Success %d, Error: %d"%(success, len(tests)-success), file=sys.stderr) \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests.json b/crypto/func/auto-tests/legacy_tests.json new file mode 100644 index 00000000..61a433bb --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests.json @@ -0,0 +1 @@ +[["elector/elector-code.fc", "115226404411715505328583639896096915745686314074575650766750648324043316883483"], ["config/config-code.fc", "10913070768607625342121305745084703121685937915388357634624451844356456145601"], ["eth-bridge-multisig/multisig-code.fc", "101509909129354488841890823627011033360100627957439967918234053299675481277954"], ["bsc-bridge-collector/votes-collector.fc", "62190447221288642706570413295807615918589884489514159926097051017036969900417"], ["uni-lock-wallet/uni-lockup-wallet.fc", "61959738324779104851267145467044677651344601417998258530238254441977103654381"], ["nft-collection/nft-collection-editable.fc", "45561997735512210616567774035540357815786262097548276229169737015839077731274"], ["dns-collection/nft-collection.fc", "107999822699841936063083742021519765435859194241091312445235370766165379261859"], ["tele-nft-item/nft-item.fc", "69777543125381987786450436977742010705076866061362104025338034583422166453344"], ["storage/storage-contract.fc", "91377830060355733016937375216020277778264560226873154627574229667513068328151"], ["storage/storage-provider.fc", "13618336676213331164384407184540461509022654507176709588621016553953760588122"], ["nominator-pool/pool.fc", "69767057279163099864792356875696330339149706521019810113334238732928422055375"], ["jetton-minter/jetton-minter.fc", "9028309926287301331466371999814928201427184114165428257502393474125007156494"], ["gg-marketplace/nft-marketplace-v2.fc", "92199806964112524639740773542356508485601908152150843819273107618799016205930"], ["jetton-wallet/jetton-wallet.fc", "86251125787443633057458168028617933212663498001665054651523310772884328206542"], ["whales-nominators/nominators.fc", "8941364499854379927692172316865293429893094891593442801401542636695127885153"], ["tact-examples/treasure_Treasure.code.fc", "13962538639825790677138656603323869918938565499584297120566680287245364723897"], ["tact-examples/jetton_SampleJetton.code.fc", "94076762218493729104783735200107713211245710256802265203823917715299139499110"], ["tact-examples/jetton_JettonDefaultWallet.code.fc", "29421313492520031238091587108198906058157443241743283101866538036369069620563"], ["tact-examples/maps_MapTestContract.code.fc", "22556550222249123835909180266811414538971143565993192846012583552876721649744"]] \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/bridge-config.fc b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/bridge-config.fc new file mode 100644 index 00000000..b48d3ff0 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/bridge-config.fc @@ -0,0 +1,13 @@ +(int, int, cell) get_bridge_config() impure inline_ref { + cell bridge_config = config_param(72); + if (bridge_config.cell_null?()) { + bridge_config = config_param(-72); + } + throw_if(666, bridge_config.cell_null?()); + slice ds = bridge_config.begin_parse(); + ;; wc always equals to -1 + int bridge_address = ds~load_uint(256); + int oracles_address = ds~load_uint(256); + cell oracles = ds~load_dict(); + return (bridge_address, oracles_address, oracles); +} diff --git a/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/message_utils.fc b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/message_utils.fc new file mode 100644 index 00000000..184c5fbe --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/message_utils.fc @@ -0,0 +1,38 @@ +() send_receipt_message(addr, ans_tag, query_id, body, grams, mode) impure inline_ref { + ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_grams(grams) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(ans_tag, 32) + .store_uint(query_id, 64); + if (body >= 0) { + msg~store_uint(body, 256); + } + send_raw_message(msg.end_cell(), mode); +} + +() send_text_receipt_message(addr, grams, mode) impure inline_ref { + ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_grams(grams) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0, 32) + .store_uint(0x4f4b, 16); ;; "OK" + send_raw_message(msg.end_cell(), mode); +} + +() emit_log_simple (int event_id, slice data) impure inline { + var msg = begin_cell() + .store_uint (12, 4) ;; ext_out_msg_info$11 src:MsgAddressInt () + .store_uint (1, 2) + .store_uint (256, 9) + .store_uint(event_id, 256) + .store_uint(0, 64 + 32 + 2) ;; created_lt, created_at, init:Maybe, body:Either + .store_slice(data) + .end_cell(); + send_raw_message(msg, 0); +} diff --git a/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/stdlib.fc b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/stdlib.fc new file mode 100644 index 00000000..1d144040 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/stdlib.fc @@ -0,0 +1,209 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + diff --git a/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/votes-collector.fc b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/votes-collector.fc new file mode 100644 index 00000000..cec4ed21 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/bsc-bridge-collector/votes-collector.fc @@ -0,0 +1,118 @@ +#include "stdlib.fc"; +#include "bridge-config.fc"; +#include "message_utils.fc"; + +cell load_data() inline_ref { + var ds = get_data().begin_parse(); + return ds~load_dict(); +} + +() save_data(cell external_votings) impure inline_ref { + var st = begin_cell().store_dict(external_votings).end_cell(); + set_data(st); +} + +() vote_on_external_chain(slice s_addr, int query_id, int voting_id, slice signature) impure { + cell external_votings = load_data(); + (_, int oracles_address, cell oracles) = get_bridge_config(); + (int wc, int addr) = parse_std_addr(s_addr); + throw_if(301, wc + 1); + (slice key, int found?) = oracles.udict_get?(256, addr); + throw_unless(304, found?); + + (slice old_voting_data, int voting_found?) = external_votings.udict_get?(256, voting_id); + cell signatures = new_dict(); + if (voting_found?) { + (_, int old_oracles_address, signatures) = (old_voting_data~load_uint(32), + old_voting_data~load_uint(256), + old_voting_data~load_dict()); + if (old_oracles_address != oracles_address) { + signatures = new_dict(); + } + } + int secp_key = key~load_uint(256); + int success? = signatures~udict_add?(256, secp_key, signature); + throw_unless(324, success?); + builder new_voting_data = begin_cell() + .store_uint(now(), 32) + .store_uint(oracles_address, 256) + .store_dict(signatures); + external_votings~udict_set_builder(256, voting_id, new_voting_data); + + save_data(external_votings); + return send_receipt_message(s_addr, 0x10000 + 5, query_id, voting_id, 0, 64); +} + +() remove_outdated_votings(slice s_addr, int query_id, slice external_ids) impure { + cell external_votings = load_data(); + + int bound = now() - 60 * 60 * 24 * 7; + while (~ external_ids.slice_empty?()) { + if (external_ids.slice_data_empty?()) { + external_ids = external_ids.preload_ref().begin_parse(); + } + int voting_id = external_ids~load_uint(256); + (cell external_votings', slice voting, int voting_found?) = external_votings.udict_delete_get?(256, voting_id); + if (voting_found?) { + int last_update = voting~load_uint(32); + if (bound > last_update) { + ;; remove only old votings + external_votings = external_votings'; + } + } + } + + save_data(external_votings); + return send_receipt_message(s_addr, 0x10000 + 6, query_id, 0, 0, 64); ;; thanks +} + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + if (flags & 1) { + ;; ignore all bounced messages + return (); + } + slice s_addr = cs~load_msg_addr(); + if (in_msg.slice_empty?()) { + ;; inbound message has empty body + return (); + } + int op = in_msg~load_uint(32); + if (op == 0) { + return (); + } + int query_id = in_msg~load_uint(64); + + if (op == 5) { ;; submit signatures + int voting_id = in_msg~load_uint(256); + slice signature = in_msg~load_bits(520); + return vote_on_external_chain(s_addr, query_id, voting_id, signature); + } + + if (op == 6) { ;; remove old swaps + return remove_outdated_votings(s_addr, query_id, in_msg); + } +} + +(tuple) get_external_voting_data(int voting_id) method_id { + cell external_votings = load_data(); + (slice voting_data, int found?) = external_votings.udict_get?(256, voting_id); + throw_unless(309, found?); + (int time, int old_oracles_address, cell signatures) = (voting_data~load_uint(32), + voting_data~load_uint(256), + voting_data~load_dict()); + tuple list = null(); + + int secp_key = -1; + do { + (secp_key, slice sig, int found?) = signatures.udict_get_next?(256, secp_key); + if (found?) { + (int r, int s, int v) = (sig~load_uint(256), + sig~load_uint(256), + sig~load_uint(8)); + list = cons( pair( secp_key, triple(r,s,v)), list); + } + } until (~ found?); + return (list); +} diff --git a/crypto/func/auto-tests/legacy_tests/config/config-code.fc b/crypto/func/auto-tests/legacy_tests/config/config-code.fc new file mode 100644 index 00000000..85a4ee00 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/config/config-code.fc @@ -0,0 +1,643 @@ +;; Simple configuration smart contract + +#include "stdlib.fc"; + +() set_conf_param(int index, cell value) impure { + var cs = get_data().begin_parse(); + var cfg_dict = cs~load_ref(); + cfg_dict~idict_set_ref(32, index, value); + set_data(begin_cell().store_ref(cfg_dict).store_slice(cs).end_cell()); +} + +(cell, int, int, cell) load_data() inline { + var cs = get_data().begin_parse(); + var res = (cs~load_ref(), cs~load_uint(32), cs~load_uint(256), cs~load_dict()); + cs.end_parse(); + return res; +} + +() store_data(cfg_dict, stored_seqno, public_key, vote_dict) impure inline { + set_data(begin_cell() + .store_ref(cfg_dict) + .store_uint(stored_seqno, 32) + .store_uint(public_key, 256) + .store_dict(vote_dict) + .end_cell()); +} + +;; (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price) +_ parse_vote_config(cell c) inline { + var cs = c.begin_parse(); + throw_unless(44, cs~load_uint(8) == 0x36); + var res = (cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(32), cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + cs.end_parse(); + return res; +} + +;; cfg_vote_setup#91 normal_params:^ConfigProposalSetup critical_params:^ConfigProposalSetup = ConfigVotingSetup; +_ get_vote_config_internal(int critical?, cell cparam11) inline_ref { + var cs = cparam11.begin_parse(); + throw_unless(44, cs~load_uint(8) == 0x91); + if (critical?) { + cs~load_ref(); + } + return parse_vote_config(cs.preload_ref()); +} + +_ get_vote_config(int critical?) inline { + return get_vote_config_internal(critical?, config_param(11)); +} + +(int, int) check_validator_set(cell vset) { + var cs = vset.begin_parse(); + throw_unless(9, cs~load_uint(8) == 0x12); ;; validators_ext#12 only + int utime_since = cs~load_uint(32); + int utime_until = cs~load_uint(32); + int total = cs~load_uint(16); + int main = cs~load_uint(16); + throw_unless(9, main > 0); + throw_unless(9, total >= main); + return (utime_since, utime_until); +} + +() send_answer(addr, query_id, ans_tag, mode) impure { + ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 + send_raw_message(begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_uint(0, 5 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(ans_tag, 32) + .store_uint(query_id, 64) + .end_cell(), mode); +} + +() send_confirmation(addr, query_id, ans_tag) impure inline { + return send_answer(addr, query_id, ans_tag, 64); +} + +() send_error(addr, query_id, ans_tag) impure inline { + return send_answer(addr, query_id, ans_tag, 64); +} + +;; forward a message to elector smart contract to make it upgrade its code +() change_elector_code(slice cs) impure { + var dest_addr = config_param(1).begin_parse().preload_uint(256); + var query_id = now(); + send_raw_message(begin_cell() + .store_uint(0xc4ff, 17) + .store_uint(dest_addr, 256) + .store_grams(1 << 30) ;; ~ 1 Gram (will be returned back) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x4e436f64, 32) ;; action + .store_uint(query_id, 64) + .store_slice(cs) + .end_cell(), 0); +} + +() after_code_upgrade(slice param, cont old_code) impure method_id(1666) { +} + +_ perform_action(cfg_dict, public_key, action, cs) inline_ref { + if (action == 0x43665021) { + ;; change one configuration parameter + var param_index = cs~load_int(32); + var param_value = cs~load_ref(); + cs.end_parse(); + cfg_dict~idict_set_ref(32, param_index, param_value); + return (cfg_dict, public_key); + } elseif (action == 0x4e436f64) { + ;; change configuration smart contract code + var new_code = cs~load_ref(); + set_code(new_code); + var old_code = get_c3(); + set_c3(new_code.begin_parse().bless()); + after_code_upgrade(cs, old_code); + throw(0); + return (cfg_dict, public_key); + } elseif (action == 0x50624b21) { + ;; change configuration master public key + public_key = cs~load_uint(256); + cs.end_parse(); + return (cfg_dict, public_key); + } elseif (action == 0x4e43ef05) { + ;; change election smart contract code + change_elector_code(cs); + return (cfg_dict, public_key); + } else { + throw_if(32, action); + return (cfg_dict, public_key); + } +} + +(cell, int, cell) get_current_vset() inline_ref { + var vset = config_param(34); + var cs = begin_parse(vset); + ;; validators_ext#12 utime_since:uint32 utime_until:uint32 + ;; total:(## 16) main:(## 16) { main <= total } { main >= 1 } + ;; total_weight:uint64 + throw_unless(40, cs~load_uint(8) == 0x12); + cs~skip_bits(32 + 32 + 16 + 16); + var (total_weight, dict) = (cs~load_uint(64), cs~load_dict()); + cs.end_parse(); + return (vset, total_weight, dict); +} + +(slice, int) get_validator_descr(int idx) inline_ref { + var (vset, total_weight, dict) = get_current_vset(); + var (value, _) = dict.udict_get?(16, idx); + return (value, total_weight); +} + +(int, int) unpack_validator_descr(slice cs) inline { + ;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey; + ;; validator#53 public_key:SigPubKey weight:uint64 = ValidatorDescr; + ;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr; + throw_unless(41, (cs~load_uint(8) & ~ 0x20) == 0x53); + throw_unless(41, cs~load_uint(32) == 0x8e81278a); + return (cs~load_uint(256), cs~load_uint(64)); +} + +;; cfg_proposal#f3 param_id:int32 param_value:(Maybe ^Cell) if_hash_equal:(Maybe uint256) +;; c -> (param-id param-cell maybe-hash) +(int, cell, int) parse_config_proposal(cell c) inline_ref { + var cs = c.begin_parse(); + throw_unless(44, cs~load_int(8) == 0xf3 - 0x100); + var (id, val, hash) = (cs~load_int(32), cs~load_maybe_ref(), cs~load_int(1)); + if (hash) { + hash = cs~load_uint(256); + } else { + hash = -1; + } + cs.end_parse(); + return (id, val, hash); +} + +(cell, int, cell) accept_proposal(cell cfg_dict, cell proposal, int critical?) inline_ref { + var (param_id, param_val, req_hash) = parse_config_proposal(proposal); + cell cur_val = cfg_dict.idict_get_ref(32, param_id); + int cur_hash = null?(cur_val) ? 0 : cell_hash(cur_val); + if ((cur_hash != req_hash) & (req_hash >= 0)) { + ;; current value has incorrect hash, do not apply changes + return (cfg_dict, 0, null()); + } + cell mparams = cfg_dict.idict_get_ref(32, 9); ;; mandatory parameters + var (_, found?) = mparams.idict_get?(32, param_id); + if (found? & param_val.null?()) { + ;; cannot set a mandatory parameter to (null) + return (cfg_dict, 0, null()); + } + cell cparams = cfg_dict.idict_get_ref(32, 10); ;; critical parameters + (_, found?) = cparams.idict_get?(32, param_id); + if (found? < critical?) { + ;; trying to set a critical parameter after a non-critical voting + return (cfg_dict, 0, null()); + } + ;; CHANGE ONE CONFIGURATION PARAMETER (!) + cfg_dict~idict_set_ref(32, param_id, param_val); + return (cfg_dict, param_id, param_val); +} + +(cell, int) perform_proposed_action(cell cfg_dict, int public_key, int param_id, cell param_val) inline_ref { + if (param_id == -999) { + ;; appoint or depose dictator + return (cfg_dict, param_val.null?() ? 0 : param_val.begin_parse().preload_uint(256)); + } + if (param_val.null?()) { + return (cfg_dict, public_key); + } + if (param_id == -1000) { + ;; upgrade code + var cs = param_val.begin_parse(); + var new_code = cs~load_ref(); + set_code(new_code); + var old_code = get_c3(); + set_c3(new_code.begin_parse().bless()); + after_code_upgrade(cs, old_code); + throw(0); + return (cfg_dict, public_key); + } + if (param_id == -1001) { + ;; update elector code + var cs = param_val.begin_parse(); + change_elector_code(cs); + } + return (cfg_dict, public_key); +} + +;; cfg_proposal_status#ce expires:uint32 proposal:^ConfigProposal is_critical:Bool +;; voters:(HashmapE 16 True) remaining_weight:int64 validator_set_id:uint256 +;; rounds_remaining:uint8 wins:uint8 losses:uint8 = ConfigProposalStatus; +(int, cell, int, cell, int, int, slice) unpack_proposal_status(slice cs) inline_ref { + throw_unless(44, cs~load_int(8) == 0xce - 0x100); + return (cs~load_uint(32), cs~load_ref(), cs~load_int(1), cs~load_dict(), cs~load_int(64), cs~load_uint(256), cs); +} + +slice update_proposal_status(slice rest, int weight_remaining, int critical?) inline_ref { + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, _, _, _, _) = get_vote_config(critical?); + var (rounds_remaining, wins, losses) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8)); + losses -= (weight_remaining >= 0); + if (losses > max_losses) { + ;; lost too many times + return null(); + } + rounds_remaining -= 1; + if (rounds_remaining < 0) { + ;; existed for too many rounds + return null(); + } + return begin_cell() + .store_uint(rounds_remaining, 8) + .store_uint(wins, 8) + .store_uint(losses, 8) + .end_cell().begin_parse(); +} + +builder begin_pack_proposal_status(int expires, cell proposal, int critical?, cell voters, int weight_remaining, int vset_id) inline { + return begin_cell() + .store_int(0xce - 0x100, 8) + .store_uint(expires, 32) + .store_ref(proposal) + .store_int(critical?, 1) + .store_dict(voters) + .store_int(weight_remaining, 64) + .store_uint(vset_id, 256); +} + +(cell, cell, int) register_vote(vote_dict, phash, idx, weight) inline_ref { + var (pstatus, found?) = vote_dict.udict_get?(256, phash); + ifnot (found?) { + ;; config proposal not found + return (vote_dict, null(), -1); + } + var (cur_vset, total_weight, _) = get_current_vset(); + int cur_vset_id = cur_vset.cell_hash(); + var (expires, proposal, critical?, voters, weight_remaining, vset_id, rest) = unpack_proposal_status(pstatus); + if (expires <= now()) { + ;; config proposal expired, delete and report not found + vote_dict~udict_delete?(256, phash); + return (vote_dict, null(), -1); + } + if (vset_id != cur_vset_id) { + ;; config proposal belongs to a previous validator set + vset_id = cur_vset_id; + rest = update_proposal_status(rest, weight_remaining, critical?); + voters = null(); + weight_remaining = muldiv(total_weight, 3, 4); + } + if (rest.null?()) { + ;; discard proposal (existed for too many rounds, or too many losses) + vote_dict~udict_delete?(256, phash); + return (vote_dict, null(), -1); + } + var (_, found?) = voters.udict_get?(16, idx); + if (found?) { + ;; already voted for this proposal, ignore vote + return (vote_dict, null(), -2); + } + ;; register vote + voters~udict_set_builder(16, idx, begin_cell().store_uint(now(), 32)); + int old_wr = weight_remaining; + weight_remaining -= weight; + if ((weight_remaining ^ old_wr) >= 0) { + ;; not enough votes, or proposal already accepted in this round + ;; simply update weight_remaining + vote_dict~udict_set_builder(256, phash, begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id).store_slice(rest)); + return (vote_dict, null(), 2); + } + ;; proposal wins in this round + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, _, _, _, _) = get_vote_config(critical?); + var (rounds_remaining, wins, losses) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8)); + wins += 1; + if (wins >= min_wins) { + ;; proposal is accepted, remove and process + vote_dict~udict_delete?(256, phash); + return (vote_dict, proposal, 6 - critical?); + } + ;; update proposal info + vote_dict~udict_set_builder(256, phash, + begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id) + .store_uint(rounds_remaining, 8) + .store_uint(wins, 8) + .store_uint(losses, 8)); + return (vote_dict, null(), 2); +} + +int proceed_register_vote(phash, idx, weight) impure inline_ref { + var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data(); + (vote_dict, var accepted_proposal, var status) = register_vote(vote_dict, phash, idx, weight); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + ifnot (accepted_proposal.null?()) { + var critical? = 6 - status; + (cfg_dict, var param_id, var param_val) = accept_proposal(cfg_dict, accepted_proposal, critical?); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + if (param_id) { + commit(); + (cfg_dict, public_key) = perform_proposed_action(cfg_dict, public_key, param_id, param_val); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + } + } + return status; +} + +(slice, int) scan_proposal(int phash, slice pstatus) inline_ref { + var (cur_vset, total_weight, _) = get_current_vset(); + int cur_vset_id = cur_vset.cell_hash(); + var (expires, proposal, critical?, voters, weight_remaining, vset_id, rest) = unpack_proposal_status(pstatus); + if (expires <= now()) { + ;; config proposal expired, delete + return (null(), true); + } + if (vset_id == cur_vset_id) { + ;; config proposal already processed or voted for in this round, change nothing + return (pstatus, false); + } + ;; config proposal belongs to a previous validator set + vset_id = cur_vset_id; + rest = update_proposal_status(rest, weight_remaining, critical?); + voters = null(); + weight_remaining = muldiv(total_weight, 3, 4); + if (rest.null?()) { + ;; discard proposal (existed for too many rounds, or too many losses) + return (null(), true); + } + ;; return updated proposal + return (begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id).store_slice(rest).end_cell().begin_parse(), true); +} + +cell scan_random_proposal(cell vote_dict) inline_ref { + var (phash, pstatus, found?) = vote_dict.udict_get_nexteq?(256, random()); + ifnot (found?) { + return vote_dict; + } + (pstatus, var changed?) = scan_proposal(phash, pstatus); + if (changed?) { + if (pstatus.null?()) { + vote_dict~udict_delete?(256, phash); + } else { + vote_dict~udict_set(256, phash, pstatus); + } + } + return vote_dict; +} + +int register_voting_proposal(slice cs, int msg_value) impure inline_ref { + var (expire_at, proposal, critical?) = (cs~load_uint(32), cs~load_ref(), cs~load_int(1)); + if (expire_at >> 30) { + expire_at -= now(); + } + var (param_id, param_val, hash) = parse_config_proposal(proposal); + if (hash >= 0) { + cell cur_val = config_param(param_id); + int cur_hash = null?(cur_val) ? 0 : cell_hash(cur_val); + if (cur_hash != hash) { + hash = -0xe2646356; ;; bad current value + } + } else { + var m_params = config_param(9); + var (_, found?) = m_params.idict_get?(32, param_id); + if (found?) { + hash = -0xcd506e6c; ;; cannot set mandatory parameter to null + } + } + if (param_val.cell_depth() >= 256) { + hash = -0xc2616456; ;; bad value + } + if (hash < -1) { + return hash; ;; return error if any + } + ifnot (critical?) { + var crit_params = config_param(10); + var (_, found?) = crit_params.idict_get?(32, param_id); + if (found?) { + hash = -0xc3726954; ;; trying to set a critical parameter without critical flag + } + } + if (hash < -1) { + return hash; + } + ;; obtain vote proposal configuration + var vote_cfg = get_vote_config(critical?); + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price) = vote_cfg; + if (expire_at < min_store_sec) { + return -0xc5787069; ;; expired + } + expire_at = min(expire_at, max_store_sec); + ;; compute price + var (_, bits, refs) = compute_data_size(param_val, 1024); + var pps = bit_price * (bits + 1024) + cell_price * (refs + 2); + var price = pps * expire_at; + expire_at += now(); + var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data(); + int phash = proposal.cell_hash(); + var (pstatus, found?) = vote_dict.udict_get?(256, phash); + if (found?) { + ;; proposal already exists; we can only extend it + var (expires, r_proposal, r_critical?, voters, weight_remaining, vset_id, rest) = unpack_proposal_status(pstatus); + if (r_critical? != critical?) { + return -0xc3726955; ;; cannot upgrade critical parameter to non-critical... + } + if (expires >= expire_at) { + return -0xc16c7245; ;; proposal already exists + } + ;; recompute price + price = pps * (expire_at - expires + 16384); + if (msg_value - price < (1 << 30)) { + return -0xf0617924; ;; need more money + } + ;; update expiration time + vote_dict~udict_set_builder(256, phash, begin_pack_proposal_status(expire_at, r_proposal, r_critical?, voters, weight_remaining, vset_id).store_slice(rest)); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + return price; + } + if (msg_value - price < (1 << 30)) { + return -0xf0617924; ;; need more money + } + ;; obtain current validator set data + var (vset, total_weight, _) = get_current_vset(); + int weight_remaining = muldiv(total_weight, 3, 4); + ;; create new proposal + vote_dict~udict_set_builder(256, phash, + begin_pack_proposal_status(expire_at, proposal, critical?, null(), weight_remaining, vset.cell_hash()) + .store_uint(max_tot_rounds, 8).store_uint(0, 16)); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + return price; +} + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + var s_addr = cs~load_msg_addr(); + (int src_wc, int src_addr) = s_addr.parse_std_addr(); + if ((src_wc + 1) | (flags & 1) | in_msg.slice_empty?()) { + ;; source not in masterchain, or a bounced message, or a simple transfer + return (); + } + int tag = in_msg~load_uint(32); + int query_id = in_msg~load_uint(64); + if (tag == 0x4e565354) { + ;; set next validator set + var vset = in_msg~load_ref(); + in_msg.end_parse(); + var elector_param = config_param(1); + var elector_addr = cell_null?(elector_param) ? -1 : elector_param.begin_parse().preload_uint(256); + var ok = false; + if (src_addr == elector_addr) { + ;; message from elector smart contract + ;; set next validator set + (var t_since, var t_until) = check_validator_set(vset); + var t = now(); + ok = (t_since > t) & (t_until > t_since); + } + if (ok) { + set_conf_param(36, vset); + ;; send confirmation + return send_confirmation(s_addr, query_id, 0xee764f4b); + } else { + return send_error(s_addr, query_id, 0xee764f6f); + } + } + if (tag == 0x6e565052) { + ;; new voting proposal + var price = register_voting_proposal(in_msg, msg_value); + int mode = 64; + int ans_tag = - price; + if (price >= 0) { + ;; ok, debit price + raw_reserve(price, 4); + ans_tag = 0xee565052; + mode = 128; + } + return send_answer(s_addr, query_id, ans_tag, mode); + } + if (tag == 0x566f7465) { + ;; vote for a configuration proposal + var signature = in_msg~load_bits(512); + var msg_body = in_msg; + var (sign_tag, idx, phash) = (in_msg~load_uint(32), in_msg~load_uint(16), in_msg~load_uint(256)); + in_msg.end_parse(); + throw_unless(37, sign_tag == 0x566f7445); + var (vdescr, total_weight) = get_validator_descr(idx); + var (val_pubkey, weight) = unpack_validator_descr(vdescr); + throw_unless(34, check_data_signature(msg_body, signature, val_pubkey)); + int res = proceed_register_vote(phash, idx, weight); + return send_confirmation(s_addr, query_id, res + 0xd6745240); + } + ;; if tag is non-zero and its higher bit is zero, throw an exception (the message is an unsupported query) + ;; to bounce message back to sender + throw_unless(37, (tag == 0) | (tag & (1 << 31))); + ;; do nothing for other internal messages +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + int action = cs~load_uint(32); + int msg_seqno = cs~load_uint(32); + var valid_until = cs~load_uint(32); + throw_if(35, valid_until < now()); + throw_if(39, slice_depth(cs) > 128); + var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data(); + throw_unless(33, msg_seqno == stored_seqno); + if (action == 0x566f7465) { + ;; vote for a configuration proposal + var (idx, phash) = (cs~load_uint(16), cs~load_uint(256)); + cs.end_parse(); + var (vdescr, total_weight) = get_validator_descr(idx); + var (val_pubkey, weight) = unpack_validator_descr(vdescr); + throw_unless(34, check_data_signature(in_msg, signature, val_pubkey)); + accept_message(); + stored_seqno = (stored_seqno + 1) % (1 << 32); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + commit(); + proceed_register_vote(phash, idx, weight); + return (); + } + throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + stored_seqno = (stored_seqno + 1) % (1 << 32); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + commit(); + (cfg_dict, public_key) = perform_action(cfg_dict, public_key, action, cs); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); +} + +() run_ticktock(int is_tock) impure { + var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data(); + int kl = 32; + var next_vset = cfg_dict.idict_get_ref(kl, 36); + var updated? = false; + ifnot (next_vset.null?()) { + ;; check whether we have to set next_vset as the current validator set + var ds = next_vset.begin_parse(); + if (ds.slice_bits() >= 40) { + var tag = ds~load_uint(8); + var since = ds.preload_uint(32); + if ((since <= now()) & (tag == 0x12)) { + ;; next validator set becomes active! + var cur_vset = cfg_dict~idict_set_get_ref(kl, 34, next_vset); ;; next_vset -> cur_vset + cfg_dict~idict_set_get_ref(kl, 32, cur_vset); ;; cur_vset -> prev_vset + cfg_dict~idict_delete?(kl, 36); ;; (null) -> next_vset + updated? = true; + } + } + } + ifnot (updated?) { + ;; if nothing has been done so far, scan a random voting proposal instead + vote_dict = scan_random_proposal(vote_dict); + } + ;; save data and return + return store_data(cfg_dict, stored_seqno, public_key, vote_dict); +} + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +_ unpack_proposal(slice pstatus) inline_ref { + (int expires, cell proposal, int critical?, cell voters, int weight_remaining, int vset_id, slice rest) = unpack_proposal_status(pstatus); + var voters_list = null(); + var voter_id = (1 << 32); + do { + (voter_id, _, var f) = voters.udict_get_prev?(16, voter_id); + if (f) { + voters_list = cons(voter_id, voters_list); + } + } until (~ f); + var (rounds_remaining, losses, wins) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8)); + rest.end_parse(); + var (param_id, param_val, param_hash) = parse_config_proposal(proposal); + return [expires, critical?, [param_id, param_val, param_hash], vset_id, voters_list, weight_remaining, rounds_remaining, losses, wins]; +} + +_ get_proposal(int phash) method_id { + (_, _, _, var vote_dict) = load_data(); + var (pstatus, found?) = vote_dict.udict_get?(256, phash); + ifnot (found?) { + return null(); + } + return unpack_proposal(pstatus); +} + +_ list_proposals() method_id { + (_, _, _, var vote_dict) = load_data(); + var phash = (1 << 255) + ((1 << 255) - 1); + var list = null(); + do { + (phash, var pstatus, var f) = vote_dict.udict_get_prev?(256, phash); + if (f) { + list = cons([phash, unpack_proposal(pstatus)], list); + } + } until (~ f); + return list; +} + +_ proposal_storage_price(int critical?, int seconds, int bits, int refs) method_id { + var cfg_dict = get_data().begin_parse().preload_ref(); + var cparam11 = cfg_dict.idict_get_ref(32, 11); + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price) = get_vote_config_internal(critical?, cparam11); + if (seconds < min_store_sec) { + return -1; + } + seconds = min(seconds, max_store_sec); + return (bit_price * (bits + 1024) + cell_price * (refs + 2)) * seconds; +} diff --git a/crypto/func/auto-tests/legacy_tests/config/stdlib.fc b/crypto/func/auto-tests/legacy_tests/config/stdlib.fc new file mode 100644 index 00000000..0b98eeb4 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/config/stdlib.fc @@ -0,0 +1,208 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; diff --git a/crypto/func/auto-tests/legacy_tests/dns-collection/dns-utils.fc b/crypto/func/auto-tests/legacy_tests/dns-collection/dns-utils.fc new file mode 100644 index 00000000..2b1061e9 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/dns-collection/dns-utils.fc @@ -0,0 +1,110 @@ +const int one_month = 2592000; ;; 1 month in seconds = 60 * 60 * 24 * 30 +const int one_year = 31622400; ;; 1 year in seconds = 60 * 60 * 24 * 366 +const int auction_start_time = 1659171600; ;; GMT: Monday, 30 July 2022 г., 09:00:00 +const int one_ton = 1000000000; +const int dns_next_resolver_prefix = 0xba93; ;; dns_next_resolver prefix - https://github.com/ton-blockchain/ton/blob/7e3df93ca2ab336716a230fceb1726d81bac0a06/crypto/block/block.tlb#L819 + +const int dns_config_id = 80; ;; dns black list config param; in testnet -80 + +const int op::fill_up = 0x370fec51; +const int op::outbid_notification = 0x557cea20; +const int op::change_dns_record = 0x4eb1f0f9; +const int op::process_governance_decision = 0x44beae41; +const int op::dns_balance_release = 0x4ed14b65; + +int mod(int x, int y) asm "MOD"; + +slice zero_address() { + return begin_cell().store_uint(0, 2).end_cell().begin_parse(); +} + +;; "ton\0test\0" -> "ton" +int get_top_domain_bits(slice domain) { + int i = 0; + int need_break = 0; + do { + int char = domain~load_uint(8); ;; we do not check domain.length because it MUST contains \0 character + need_break = char == 0; + if (~ need_break) { + i += 8; + } + } until (need_break); + throw_if(201, i == 0); ;; starts with \0 + return i; +} + +slice read_domain_from_comment(slice in_msg_body) { + int need_break = 0; + builder result = begin_cell(); + do { + result = result.store_slice(in_msg_body~load_bits(in_msg_body.slice_bits())); + int refs_len = in_msg_body.slice_refs(); + need_break = refs_len == 0; + if (~ need_break) { + throw_unless(202, refs_len == 1); + in_msg_body = in_msg_body~load_ref().begin_parse(); + } + } until (need_break); + return result.end_cell().begin_parse(); +} + +int check_domain_string(slice domain) { + int i = 0; + int len = slice_bits(domain); + int need_break = 0; + do { + need_break = i == len; + if (~ need_break) { + int char = domain~load_uint(8); + ;; we can do it because additional UTF-8 character's octets >= 128 -- https://www.ietf.org/rfc/rfc3629.txt + int is_hyphen = (char == 45); + int valid_char = (is_hyphen & (i > 0) & (i < len - 8)) | ((char >= 48) & (char <= 57)) | ((char >= 97) & (char <= 122)); ;; '-' or 0-9 or a-z + + need_break = ~ valid_char; + if (~ need_break) { + i += 8; + } + } + } until (need_break); + return i == len; +} + +(int, int) get_min_price_config(int domain_char_count) { + if (domain_char_count == 4) { + return (1000, 100); + } + if (domain_char_count == 5) { + return (500, 50); + } + if (domain_char_count == 6) { + return (400, 40); + } + if (domain_char_count == 7) { + return (300, 30); + } + if (domain_char_count == 8) { + return (200, 20); + } + if (domain_char_count == 9) { + return (100, 10); + } + if (domain_char_count == 10) { + return (50, 5); + } + return (10, 1); +} + +int get_min_price(int domain_bits_length, int now_time) { + (int start_min_price, int end_min_price) = get_min_price_config(domain_bits_length / 8); + start_min_price *= one_ton; + end_min_price *= one_ton; + int seconds = now_time - auction_start_time; + int months = seconds / one_month; + if (months > 21) { + return end_min_price; + } + repeat (months) { + start_min_price = start_min_price * 90 / 100; + } + return start_min_price; +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/dns-collection/nft-collection.fc b/crypto/func/auto-tests/legacy_tests/dns-collection/nft-collection.fc new file mode 100644 index 00000000..4e142587 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/dns-collection/nft-collection.fc @@ -0,0 +1,144 @@ +;; DNS resolver smart contract (implements NFT Collection interface) + + +#include "stdlib.fc"; +#include "params.fc"; +#include "dns-utils.fc"; + +;; storage scheme +;; storage#_ collection_content:^Cell +;; nft_item_code:^Cell +;; = Storage; + +(cell, cell) load_data() inline { + var ds = get_data().begin_parse(); + return ( + ds~load_ref(), ;; content + ds~load_ref() ;; nft_item_code + ); +} + +() save_data(cell content, cell nft_item_code) impure inline { + set_data(begin_cell() + .store_ref(content) + .store_ref(nft_item_code) + .end_cell()); +} + +cell calculate_nft_item_state_init(int item_index, cell nft_item_code) { + cell data = begin_cell().store_uint(item_index, 256).store_slice(my_address()).end_cell(); + return begin_cell().store_uint(0, 2).store_dict(nft_item_code).store_dict(data).store_uint(0, 1).end_cell(); +} + +slice calculate_nft_item_address(int wc, cell state_init) { + return begin_cell() + .store_uint(4, 3) + .store_int(wc, 8) + .store_uint(cell_hash(state_init), 256) + .end_cell() + .begin_parse(); +} + +() deploy_nft_item(int item_index, cell nft_item_code, cell nft_content) impure { + cell state_init = calculate_nft_item_state_init(item_index, nft_item_code); + slice nft_address = calculate_nft_item_address(workchain(), state_init); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(nft_address) + .store_coins(0) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(nft_content); + send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message, fee deducted from amount +} + +() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { + if (in_msg_body.slice_empty?()) { ;; bounce back empty messages + throw(0xffff); + } + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if (flags & 1) { ;; ignore all bounced messages + return (); + } + slice sender_address = cs~load_msg_addr(); + + int op = in_msg_body~load_uint(32); + + var (content, nft_item_code) = load_data(); + + if (op == 0) { ;; deploy new nft + int now_time = now(); + throw_unless(199, now_time > auction_start_time); ;; start of auction + slice domain = read_domain_from_comment(in_msg_body); + int len = slice_bits(domain); + throw_unless(200, len > 3 * 8); ;; minimum 4 characters + throw_unless(201, len <= 126 * 8); ;; maxmimum 126 characters + throw_unless(202, mod(len, 8) == 0); + throw_unless(203, check_domain_string(domain)); + int min_price = get_min_price(len, now_time); + throw_unless(204, msg_value >= min_price); + + int item_index = slice_hash(domain); + + cell config_cell = config_param(dns_config_id); + if (~ cell_null?(config_cell)) { + slice config_cs = config_cell.begin_parse(); + cell config = config_cs~load_dict(); + (slice config_value, int found) = config.udict_get?(256, item_index); + throw_if(205, found); + } + + cell nft_content = begin_cell() + .store_slice(sender_address) + .store_ref(begin_cell().store_slice(domain).end_cell()) + .end_cell(); + deploy_nft_item(item_index, nft_item_code, nft_content); + return (); + } + + if (op == op::fill_up) { ;; just fill-up balance + return (); + } + throw(0xffff); +} + +;; Get methods + +(int, cell, slice) get_collection_data() method_id { + var (content, nft_item_code) = load_data(); + return (-1, content, zero_address()); +} + +slice get_nft_address_by_index(int index) method_id { + var (content, nft_item_code) = load_data(); + cell state_init = calculate_nft_item_state_init(index, nft_item_code); + return calculate_nft_item_address(workchain(), state_init); +} + +cell get_nft_content(int index, cell individual_nft_content) method_id { + return individual_nft_content; +} + +(int, cell) dnsresolve(slice subdomain, int category) method_id { + throw_unless(70, mod(slice_bits(subdomain), 8) == 0); + + int starts_with_zero_byte = subdomain.preload_int(8) == 0; + + if (starts_with_zero_byte & (slice_bits(subdomain) == 8)) { ;; "." requested + return (8, null()); ;; resolved but no dns-records + } + if (starts_with_zero_byte) { + subdomain~load_uint(8); + } + + int top_subdomain_bits = get_top_domain_bits(subdomain); + slice top_subdomain = subdomain~load_bits(top_subdomain_bits); + int item_index = slice_hash(top_subdomain); + cell result = begin_cell() + .store_uint(dns_next_resolver_prefix, 16) + .store_slice(get_nft_address_by_index(item_index)) + .end_cell(); + return (top_subdomain_bits + (starts_with_zero_byte ? 8 : 0), result); +} diff --git a/crypto/func/auto-tests/legacy_tests/dns-collection/params.fc b/crypto/func/auto-tests/legacy_tests/dns-collection/params.fc new file mode 100644 index 00000000..e28eac45 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/dns-collection/params.fc @@ -0,0 +1,6 @@ +int workchain() asm "0 PUSHINT"; + +() force_chain(slice addr) impure { + (int wc, _) = parse_std_addr(addr); + throw_unless(333, wc == workchain()); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/dns-collection/stdlib.fc b/crypto/func/auto-tests/legacy_tests/dns-collection/stdlib.fc new file mode 100644 index 00000000..40bac583 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/dns-collection/stdlib.fc @@ -0,0 +1,216 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; + +int equal_slices (slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; +builder store_builder(builder to, builder from) asm "STBR"; + diff --git a/crypto/func/auto-tests/legacy_tests/elector/elector-code.fc b/crypto/func/auto-tests/legacy_tests/elector/elector-code.fc new file mode 100644 index 00000000..a0652442 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/elector/elector-code.fc @@ -0,0 +1,1189 @@ +;; Elector smartcontract + +#include "stdlib.fc"; + +;; cur_elect credits past_elections grams active_id active_hash +(cell, cell, cell, int, int, int) load_data() inline_ref { + var cs = get_data().begin_parse(); + var res = (cs~load_dict(), cs~load_dict(), cs~load_dict(), cs~load_grams(), cs~load_uint(32), cs~load_uint(256)); + cs.end_parse(); + return res; +} + +;; cur_elect credits past_elections grams active_id active_hash +() store_data(elect, credits, past_elections, grams, active_id, active_hash) impure inline_ref { + set_data(begin_cell() + .store_dict(elect) + .store_dict(credits) + .store_dict(past_elections) + .store_grams(grams) + .store_uint(active_id, 32) + .store_uint(active_hash, 256) + .end_cell()); +} + +;; elect -> elect_at elect_close min_stake total_stake members failed finished +_ unpack_elect(elect) inline_ref { + var es = elect.begin_parse(); + var res = (es~load_uint(32), es~load_uint(32), es~load_grams(), es~load_grams(), es~load_dict(), es~load_int(1), es~load_int(1)); + es.end_parse(); + return res; +} + +cell pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, finished) inline_ref { + return begin_cell() + .store_uint(elect_at, 32) + .store_uint(elect_close, 32) + .store_grams(min_stake) + .store_grams(total_stake) + .store_dict(members) + .store_int(failed, 1) + .store_int(finished, 1) + .end_cell(); +} + +;; slice -> unfreeze_at stake_held vset_hash frozen_dict total_stake bonuses complaints +_ unpack_past_election(slice fs) inline_ref { + var res = (fs~load_uint(32), fs~load_uint(32), fs~load_uint(256), fs~load_dict(), fs~load_grams(), fs~load_grams(), fs~load_dict()); + fs.end_parse(); + return res; +} + +builder pack_past_election(int unfreeze_at, int stake_held, int vset_hash, cell frozen_dict, int total_stake, int bonuses, cell complaints) inline_ref { + return begin_cell() + .store_uint(unfreeze_at, 32) + .store_uint(stake_held, 32) + .store_uint(vset_hash, 256) + .store_dict(frozen_dict) + .store_grams(total_stake) + .store_grams(bonuses) + .store_dict(complaints); +} + +;; complaint_status#2d complaint:^ValidatorComplaint voters:(HashmapE 16 True) +;; vset_id:uint256 weight_remaining:int64 = ValidatorComplaintStatus; +_ unpack_complaint_status(slice cs) inline_ref { + throw_unless(9, cs~load_uint(8) == 0x2d); + var res = (cs~load_ref(), cs~load_dict(), cs~load_uint(256), cs~load_int(64)); + cs.end_parse(); + return res; +} + +builder pack_complaint_status(cell complaint, cell voters, int vset_id, int weight_remaining) inline_ref { + return begin_cell() + .store_uint(0x2d, 8) + .store_ref(complaint) + .store_dict(voters) + .store_uint(vset_id, 256) + .store_int(weight_remaining, 64); +} + +;; validator_complaint#bc validator_pubkey:uint256 description:^ComplaintDescr +;; created_at:uint32 severity:uint8 reward_addr:uint256 paid:Grams suggested_fine:Grams +;; suggested_fine_part:uint32 = ValidatorComplaint; +_ unpack_complaint(slice cs) inline_ref { + throw_unless(9, cs~load_int(8) == 0xbc - 0x100); + var res = (cs~load_uint(256), cs~load_ref(), cs~load_uint(32), cs~load_uint(8), cs~load_uint(256), cs~load_grams(), cs~load_grams(), cs~load_uint(32)); + cs.end_parse(); + return res; +} + +builder pack_complaint(int validator_pubkey, cell description, int created_at, int severity, int reward_addr, int paid, int suggested_fine, int suggested_fine_part) inline_ref { + return begin_cell() + .store_int(0xbc - 0x100, 8) + .store_uint(validator_pubkey, 256) + .store_ref(description) + .store_uint(created_at, 32) + .store_uint(severity, 8) + .store_uint(reward_addr, 256) + .store_grams(paid) + .store_grams(suggested_fine) + .store_uint(suggested_fine_part, 32); +} + +;; complaint_prices#1a deposit:Grams bit_price:Grams cell_price:Grams = ComplaintPricing; +(int, int, int) parse_complaint_prices(cell info) inline { + var cs = info.begin_parse(); + throw_unless(9, cs~load_uint(8) == 0x1a); + var res = (cs~load_grams(), cs~load_grams(), cs~load_grams()); + cs.end_parse(); + return res; +} + +;; deposit bit_price cell_price +(int, int, int) get_complaint_prices() inline_ref { + var info = config_param(13); + return info.null?() ? (1 << 36, 1, 512) : info.parse_complaint_prices(); +} + +;; elected_for elections_begin_before elections_end_before stake_held_for +(int, int, int, int) get_validator_conf() { + var cs = config_param(15).begin_parse(); + return (cs~load_int(32), cs~load_int(32), cs~load_int(32), cs.preload_int(32)); +} + +;; next three functions return information about current validator set (config param #34) +;; they are borrowed from config-code.fc +(cell, int, cell) get_current_vset() inline_ref { + var vset = config_param(34); + var cs = begin_parse(vset); + ;; validators_ext#12 utime_since:uint32 utime_until:uint32 + ;; total:(## 16) main:(## 16) { main <= total } { main >= 1 } + ;; total_weight:uint64 + throw_unless(40, cs~load_uint(8) == 0x12); + cs~skip_bits(32 + 32 + 16 + 16); + var (total_weight, dict) = (cs~load_uint(64), cs~load_dict()); + cs.end_parse(); + return (vset, total_weight, dict); +} + +(slice, int) get_validator_descr(int idx) inline_ref { + var (vset, total_weight, dict) = get_current_vset(); + var (value, _) = dict.udict_get?(16, idx); + return (value, total_weight); +} + +(int, int) unpack_validator_descr(slice cs) inline { + ;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey; + ;; validator#53 public_key:SigPubKey weight:uint64 = ValidatorDescr; + ;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr; + throw_unless(41, (cs~load_uint(8) & ~ 0x20) == 0x53); + throw_unless(41, cs~load_uint(32) == 0x8e81278a); + return (cs~load_uint(256), cs~load_uint(64)); +} + +() send_message_back(addr, ans_tag, query_id, body, grams, mode) impure inline_ref { + ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_grams(grams) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(ans_tag, 32) + .store_uint(query_id, 64); + if (body >= 0) { + msg~store_uint(body, 32); + } + send_raw_message(msg.end_cell(), mode); +} + +() return_stake(addr, query_id, reason) impure inline_ref { + return send_message_back(addr, 0xee6f454c, query_id, reason, 0, 64); +} + +() send_confirmation(addr, query_id, comment) impure inline_ref { + return send_message_back(addr, 0xf374484c, query_id, comment, 1000000000, 2); +} + +() send_validator_set_to_config(config_addr, vset, query_id) impure inline_ref { + var msg = begin_cell() + .store_uint(0xc4ff, 17) ;; 0 11000100 0xff + .store_uint(config_addr, 256) + .store_grams(1 << 30) ;; ~1 gram of value to process and obtain answer + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x4e565354, 32) + .store_uint(query_id, 64) + .store_ref(vset); + send_raw_message(msg.end_cell(), 1); +} + +;; credits 'amount' to 'addr' inside credit dictionary 'credits' +_ ~credit_to(credits, addr, amount) inline_ref { + var (val, f) = credits.udict_get?(256, addr); + if (f) { + amount += val~load_grams(); + } + credits~udict_set_builder(256, addr, begin_cell().store_grams(amount)); + return (credits, ()); +} + +() process_new_stake(s_addr, msg_value, cs, query_id) impure inline_ref { + var (src_wc, src_addr) = parse_std_addr(s_addr); + var ds = get_data().begin_parse(); + var elect = ds~load_dict(); + if (elect.null?() | (src_wc + 1)) { + ;; no elections active, or source is not in masterchain + ;; bounce message + return return_stake(s_addr, query_id, 0); + } + ;; parse the remainder of new stake message + var validator_pubkey = cs~load_uint(256); + var stake_at = cs~load_uint(32); + var max_factor = cs~load_uint(32); + var adnl_addr = cs~load_uint(256); + var signature = cs~load_ref().begin_parse().preload_bits(512); + cs.end_parse(); + ifnot (check_data_signature(begin_cell() + .store_uint(0x654c5074, 32) + .store_uint(stake_at, 32) + .store_uint(max_factor, 32) + .store_uint(src_addr, 256) + .store_uint(adnl_addr, 256) + .end_cell().begin_parse(), signature, validator_pubkey)) { + ;; incorrect signature, return stake + return return_stake(s_addr, query_id, 1); + } + if (max_factor < 0x10000) { + ;; factor must be >= 1. = 65536/65536 + return return_stake(s_addr, query_id, 6); + } + ;; parse current election data + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + ;; elect_at~dump(); + msg_value -= 1000000000; ;; deduct GR$1 for sending confirmation + if ((msg_value << 12) < total_stake) { + ;; stake smaller than 1/4096 of the total accumulated stakes, return + return return_stake(s_addr, query_id, 2); + } + total_stake += msg_value; ;; (provisionally) increase total stake + if (stake_at != elect_at) { + ;; stake for some other elections, return + return return_stake(s_addr, query_id, 3); + } + if (finished) { + ;; elections already finished, return stake + return return_stake(s_addr, query_id, 0); + } + var (mem, found) = members.udict_get?(256, validator_pubkey); + if (found) { + ;; entry found, merge stakes + msg_value += mem~load_grams(); + mem~load_uint(64); ;; skip timestamp and max_factor + found = (src_addr != mem~load_uint(256)); + } + if (found) { + ;; can make stakes for a public key from one address only + return return_stake(s_addr, query_id, 4); + } + if (msg_value < min_stake) { + ;; stake too small, return it + return return_stake(s_addr, query_id, 5); + } + throw_unless(44, msg_value); + accept_message(); + ;; store stake in the dictionary + members~udict_set_builder(256, validator_pubkey, begin_cell() + .store_grams(msg_value) + .store_uint(now(), 32) + .store_uint(max_factor, 32) + .store_uint(src_addr, 256) + .store_uint(adnl_addr, 256)); + ;; gather and save election data + elect = pack_elect(elect_at, elect_close, min_stake, total_stake, members, false, false); + set_data(begin_cell().store_dict(elect).store_slice(ds).end_cell()); + ;; return confirmation message + if (query_id) { + return send_confirmation(s_addr, query_id, 0); + } + return (); +} + +(cell, int) unfreeze_without_bonuses(credits, freeze_dict, tot_stakes) inline_ref { + var total = var recovered = 0; + var pubkey = -1; + do { + (pubkey, var cs, var f) = freeze_dict.udict_get_next?(256, pubkey); + if (f) { + var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1)); + cs.end_parse(); + if (banned) { + recovered += stake; + } else { + credits~credit_to(addr, stake); + } + total += stake; + } + } until (~ f); + throw_unless(59, total == tot_stakes); + return (credits, recovered); +} + +(cell, int) unfreeze_with_bonuses(credits, freeze_dict, tot_stakes, tot_bonuses) inline_ref { + var total = var recovered = var returned_bonuses = 0; + var pubkey = -1; + do { + (pubkey, var cs, var f) = freeze_dict.udict_get_next?(256, pubkey); + if (f) { + var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1)); + cs.end_parse(); + if (banned) { + recovered += stake; + } else { + var bonus = muldiv(tot_bonuses, stake, tot_stakes); + returned_bonuses += bonus; + credits~credit_to(addr, stake + bonus); + } + total += stake; + } + } until (~ f); + throw_unless(59, (total == tot_stakes) & (returned_bonuses <= tot_bonuses)); + return (credits, recovered + tot_bonuses - returned_bonuses); +} + +int stakes_sum(frozen_dict) inline_ref { + var total = 0; + var pubkey = -1; + do { + (pubkey, var cs, var f) = frozen_dict.udict_get_next?(256, pubkey); + if (f) { + cs~skip_bits(256 + 64); + total += cs~load_grams(); + } + } until (~ f); + return total; +} + +_ unfreeze_all(credits, past_elections, elect_id) inline_ref { + var (fs, f) = past_elections~udict_delete_get?(32, elect_id); + ifnot (f) { + ;; no elections with this id + return (credits, past_elections, 0); + } + var (unfreeze_at, stake_held, vset_hash, fdict, tot_stakes, bonuses, complaints) = fs.unpack_past_election(); + ;; tot_stakes = fdict.stakes_sum(); ;; TEMP BUGFIX + var unused_prizes = (bonuses > 0) ? + credits~unfreeze_with_bonuses(fdict, tot_stakes, bonuses) : + credits~unfreeze_without_bonuses(fdict, tot_stakes); + return (credits, past_elections, unused_prizes); +} + +() config_set_confirmed(s_addr, cs, query_id, ok) impure inline_ref { + var (src_wc, src_addr) = parse_std_addr(s_addr); + var config_addr = config_param(0).begin_parse().preload_uint(256); + var ds = get_data().begin_parse(); + var elect = ds~load_dict(); + if ((src_wc + 1) | (src_addr != config_addr) | elect.null?()) { + ;; not from config smc, somebody's joke? + ;; or no elections active (or just completed) + return (); + } + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + if ((elect_at != query_id) | ~ finished) { + ;; not these elections, or elections not finished yet + return (); + } + accept_message(); + ifnot (ok) { + ;; cancel elections, return stakes + var (credits, past_elections, grams) = (ds~load_dict(), ds~load_dict(), ds~load_grams()); + (credits, past_elections, var unused_prizes) = unfreeze_all(credits, past_elections, elect_at); + set_data(begin_cell() + .store_int(false, 1) + .store_dict(credits) + .store_dict(past_elections) + .store_grams(grams + unused_prizes) + .store_slice(ds) + .end_cell()); + } + ;; ... do not remove elect until we see this set as the next elected validator set +} + +() process_simple_transfer(s_addr, msg_value) impure inline_ref { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + (int src_wc, int src_addr) = parse_std_addr(s_addr); + if (src_addr | (src_wc + 1) | (active_id == 0)) { + ;; simple transfer to us (credit "nobody's" account) + ;; (or no known active validator set) + grams += msg_value; + return store_data(elect, credits, past_elections, grams, active_id, active_hash); + } + ;; zero source address -1:00..00 (collecting validator fees) + var (fs, f) = past_elections.udict_get?(32, active_id); + ifnot (f) { + ;; active validator set not found (?) + grams += msg_value; + } else { + ;; credit active validator set bonuses + var (unfreeze_at, stake_held, hash, dict, total_stake, bonuses, complaints) = fs.unpack_past_election(); + bonuses += msg_value; + past_elections~udict_set_builder(32, active_id, + pack_past_election(unfreeze_at, stake_held, hash, dict, total_stake, bonuses, complaints)); + } + return store_data(elect, credits, past_elections, grams, active_id, active_hash); +} + +() recover_stake(op, s_addr, cs, query_id) impure inline_ref { + (int src_wc, int src_addr) = parse_std_addr(s_addr); + if (src_wc + 1) { + ;; not from masterchain, return error + return send_message_back(s_addr, 0xfffffffe, query_id, op, 0, 64); + } + var ds = get_data().begin_parse(); + var (elect, credits) = (ds~load_dict(), ds~load_dict()); + var (cs, f) = credits~udict_delete_get?(256, src_addr); + ifnot (f) { + ;; no credit for sender, return error + return send_message_back(s_addr, 0xfffffffe, query_id, op, 0, 64); + } + var amount = cs~load_grams(); + cs.end_parse(); + ;; save data + set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell()); + ;; send amount to sender in a new message + send_raw_message(begin_cell() + .store_uint(0x18, 6) + .store_slice(s_addr) + .store_grams(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0xf96f7324, 32) + .store_uint(query_id, 64) + .end_cell(), 64); +} + +() after_code_upgrade(slice s_addr, slice cs, int query_id) impure method_id(1666) { + var op = 0x4e436f64; + return send_message_back(s_addr, 0xce436f64, query_id, op, 0, 64); +} + +int upgrade_code(s_addr, cs, query_id) inline_ref { + var c_addr = config_param(0); + if (c_addr.null?()) { + ;; no configuration smart contract known + return false; + } + var config_addr = c_addr.begin_parse().preload_uint(256); + var (src_wc, src_addr) = parse_std_addr(s_addr); + if ((src_wc + 1) | (src_addr != config_addr)) { + ;; not from configuration smart contract, return error + return false; + } + accept_message(); + var code = cs~load_ref(); + set_code(code); + ifnot(cs.slice_empty?()) { + set_c3(code.begin_parse().bless()); + after_code_upgrade(s_addr, cs, query_id); + throw(0); + } + return true; +} + +int register_complaint(s_addr, complaint, msg_value) { + var (src_wc, src_addr) = parse_std_addr(s_addr); + if (src_wc + 1) { ;; not from masterchain, return error + return -1; + } + if (complaint.slice_depth() >= 128) { + return -3; ;; invalid complaint + } + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var election_id = complaint~load_uint(32); + var (fs, f) = past_elections.udict_get?(32, election_id); + ifnot (f) { ;; election not found + return -2; + } + var expire_in = fs.preload_uint(32) - now(); + if (expire_in <= 0) { ;; already expired + return -4; + } + var (validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part) = unpack_complaint(complaint); + reward_addr = src_addr; + created_at = now(); + ;; compute complaint storage/creation price + var (deposit, bit_price, cell_price) = get_complaint_prices(); + var (_, bits, refs) = slice_compute_data_size(complaint, 4096); + var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price; + paid = pps * expire_in + deposit; + if (msg_value < paid + (1 << 30)) { ;; not enough money + return -5; + } + ;; re-pack modified complaint + cell complaint = pack_complaint(validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part).end_cell(); + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + var (fs, f) = frozen_dict.udict_get?(256, validator_pubkey); + ifnot (f) { ;; no such validator, cannot complain + return -6; + } + fs~skip_bits(256 + 64); ;; addr weight + var validator_stake = fs~load_grams(); + int fine = suggested_fine + muldiv(validator_stake, suggested_fine_part, 1 << 32); + if (fine > validator_stake) { ;; validator's stake is less than suggested fine + return -7; + } + if (fine <= paid) { ;; fine is less than the money paid for creating complaint + return -8; + } + ;; create complaint status + var cstatus = pack_complaint_status(complaint, null(), 0, 0); + ;; save complaint status into complaints + var cpl_id = complaint.cell_hash(); + ifnot (complaints~udict_add_builder?(256, cpl_id, cstatus)) { + return -9; ;; complaint already exists + } + ;; pack past election info + past_elections~udict_set_builder(32, election_id, pack_past_election(unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints)); + ;; pack persistent data + ;; next line can be commented, but it saves a lot of stack manipulations + var (elect, credits, _, grams, active_id, active_hash) = load_data(); + store_data(elect, credits, past_elections, grams, active_id, active_hash); + return paid; +} + +(cell, cell, int, int) punish(credits, frozen, complaint) inline_ref { + var (validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part) = complaint.begin_parse().unpack_complaint(); + var (cs, f) = frozen.udict_get?(256, validator_pubkey); + ifnot (f) { + ;; no validator to punish + return (credits, frozen, 0, 0); + } + var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1)); + cs.end_parse(); + int fine = min(stake, suggested_fine + muldiv(stake, suggested_fine_part, 1 << 32)); + stake -= fine; + frozen~udict_set_builder(256, validator_pubkey, begin_cell() + .store_uint(addr, 256) + .store_uint(weight, 64) + .store_grams(stake) + .store_int(banned, 1)); + int reward = min(fine >> 3, paid * 8); + credits~credit_to(reward_addr, reward); + return (credits, frozen, fine - reward, fine); +} + +(cell, cell, int) register_vote(complaints, chash, idx, weight) inline_ref { + var (cstatus, found?) = complaints.udict_get?(256, chash); + ifnot (found?) { + ;; complaint not found + return (complaints, null(), -1); + } + var (cur_vset, total_weight, _) = get_current_vset(); + int cur_vset_id = cur_vset.cell_hash(); + var (complaint, voters, vset_id, weight_remaining) = unpack_complaint_status(cstatus); + int vset_old? = (vset_id != cur_vset_id); + if ((weight_remaining < 0) & vset_old?) { + ;; previous validator set already collected 2/3 votes, skip new votes + return (complaints, null(), -3); + } + if (vset_old?) { + ;; complaint votes belong to a previous validator set, reset voting + vset_id = cur_vset_id; + voters = null(); + weight_remaining = muldiv(total_weight, 2, 3); + } + var (_, found?) = voters.udict_get?(16, idx); + if (found?) { + ;; already voted for this proposal, ignore vote + return (complaints, null(), 0); + } + ;; register vote + voters~udict_set_builder(16, idx, begin_cell().store_uint(now(), 32)); + int old_wr = weight_remaining; + weight_remaining -= weight; + old_wr ^= weight_remaining; + ;; save voters and weight_remaining + complaints~udict_set_builder(256, chash, pack_complaint_status(complaint, voters, vset_id, weight_remaining)); + if (old_wr >= 0) { + ;; not enough votes or already accepted + return (complaints, null(), 1); + } + ;; complaint wins, prepare punishment + return (complaints, complaint, 2); +} + +int proceed_register_vote(election_id, chash, idx, weight) impure inline_ref { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var (fs, f) = past_elections.udict_get?(32, election_id); + ifnot (f) { ;; election not found + return -2; + } + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + (complaints, var accepted_complaint, var status) = register_vote(complaints, chash, idx, weight); + if (status <= 0) { + return status; + } + ifnot (accepted_complaint.null?()) { + (credits, frozen_dict, int fine_unalloc, int fine_collected) = punish(credits, frozen_dict, accepted_complaint); + grams += fine_unalloc; + total_stake -= fine_collected; + } + past_elections~udict_set_builder(32, election_id, pack_past_election(unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints)); + store_data(elect, credits, past_elections, grams, active_id, active_hash); + return status; +} + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + ;; do nothing for internal messages + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + if (flags & 1) { + ;; ignore all bounced messages + return (); + } + var s_addr = cs~load_msg_addr(); + if (in_msg.slice_empty?()) { + ;; inbound message has empty body + return process_simple_transfer(s_addr, msg_value); + } + int op = in_msg~load_uint(32); + if (op == 0) { + ;; simple transfer with comment, return + return process_simple_transfer(s_addr, msg_value); + } + int query_id = in_msg~load_uint(64); + if (op == 0x4e73744b) { + ;; new stake message + return process_new_stake(s_addr, msg_value, in_msg, query_id); + } + if (op == 0x47657424) { + ;; recover stake request + return recover_stake(op, s_addr, in_msg, query_id); + } + if (op == 0x4e436f64) { + ;; upgrade code (accepted only from configuration smart contract) + var ok = upgrade_code(s_addr, in_msg, query_id); + return send_message_back(s_addr, ok ? 0xce436f64 : 0xffffffff, query_id, op, 0, 64); + } + var cfg_ok = (op == 0xee764f4b); + if (cfg_ok | (op == 0xee764f6f)) { + ;; confirmation from configuration smart contract + return config_set_confirmed(s_addr, in_msg, query_id, cfg_ok); + } + if (op == 0x52674370) { + ;; new complaint + var price = register_complaint(s_addr, in_msg, msg_value); + int mode = 64; + int ans_tag = - price; + if (price >= 0) { + ;; ok, debit price + raw_reserve(price, 4); + ans_tag = 0; + mode = 128; + } + return send_message_back(s_addr, ans_tag + 0xf2676350, query_id, op, 0, mode); + } + if (op == 0x56744370) { + ;; vote for a complaint + var signature = in_msg~load_bits(512); + var msg_body = in_msg; + var (sign_tag, idx, elect_id, chash) = (in_msg~load_uint(32), in_msg~load_uint(16), in_msg~load_uint(32), in_msg~load_uint(256)); + in_msg.end_parse(); + throw_unless(37, sign_tag == 0x56744350); + var (vdescr, total_weight) = get_validator_descr(idx); + var (val_pubkey, weight) = unpack_validator_descr(vdescr); + throw_unless(34, check_data_signature(msg_body, signature, val_pubkey)); + int res = proceed_register_vote(elect_id, chash, idx, weight); + return send_message_back(s_addr, res + 0xd6745240, query_id, op, 0, 64); + } + + ifnot (op & (1 << 31)) { + ;; unknown query, return error + return send_message_back(s_addr, 0xffffffff, query_id, op, 0, 64); + } + ;; unknown answer, ignore + return (); +} + +int postpone_elections() impure { + return false; +} + +;; computes the total stake out of the first n entries of list l +_ compute_total_stake(l, n, m_stake) inline_ref { + int tot_stake = 0; + repeat (n) { + (var h, l) = uncons(l); + var stake = h.at(0); + var max_f = h.at(1); + stake = min(stake, (max_f * m_stake) >> 16); + tot_stake += stake; + } + return tot_stake; +} + +(cell, cell, int, cell, int, int) try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor) { + var cs = 16.config_param().begin_parse(); + var (max_validators, _, min_validators) = (cs~load_uint(16), cs~load_uint(16), cs~load_uint(16)); + cs.end_parse(); + min_validators = max(min_validators, 1); + int n = 0; + var sdict = new_dict(); + var pubkey = -1; + do { + (pubkey, var cs, var f) = members.udict_get_next?(256, pubkey); + if (f) { + var (stake, time, max_factor, addr, adnl_addr) = (cs~load_grams(), cs~load_uint(32), cs~load_uint(32), cs~load_uint(256), cs~load_uint(256)); + cs.end_parse(); + var key = begin_cell() + .store_uint(stake, 128) + .store_int(- time, 32) + .store_uint(pubkey, 256) + .end_cell().begin_parse(); + sdict~dict_set_builder(128 + 32 + 256, key, begin_cell() + .store_uint(min(max_factor, max_stake_factor), 32) + .store_uint(addr, 256) + .store_uint(adnl_addr, 256)); + n += 1; + } + } until (~ f); + n = min(n, max_validators); + if (n < min_validators) { + return (credits, new_dict(), 0, new_dict(), 0, 0); + } + var l = nil; + do { + var (key, cs, f) = sdict~dict::delete_get_min(128 + 32 + 256); + if (f) { + var (stake, _, pubkey) = (min(key~load_uint(128), max_stake), key~load_uint(32), key.preload_uint(256)); + var (max_f, _, adnl_addr) = (cs~load_uint(32), cs~load_uint(256), cs.preload_uint(256)); + l = cons([stake, max_f, pubkey, adnl_addr], l); + } + } until (~ f); + ;; l is the list of all stakes in decreasing order + int i = min_validators - 1; + var l1 = l; + repeat (i) { + l1 = cdr(l1); + } + var (best_stake, m) = (0, 0); + do { + var stake = l1~list_next().at(0); + i += 1; + if (stake >= min_stake) { + var tot_stake = compute_total_stake(l, i, stake); + if (tot_stake > best_stake) { + (best_stake, m) = (tot_stake, i); + } + } + } until (i >= n); + if ((m == 0) | (best_stake < min_total_stake)) { + return (credits, new_dict(), 0, new_dict(), 0, 0); + } + ;; we have to select first m validators from list l + l1 = touch(l); + ;; l1~dump(); ;; DEBUG + repeat (m - 1) { + l1 = cdr(l1); + } + var m_stake = car(l1).at(0); ;; minimal stake + ;; create both the new validator set and the refund set + int i = 0; + var tot_stake = 0; + var tot_weight = 0; + var vset = new_dict(); + var frozen = new_dict(); + do { + var [stake, max_f, pubkey, adnl_addr] = l~list_next(); + ;; lookup source address first + var (val, f) = members.udict_get?(256, pubkey); + throw_unless(61, f); + (_, _, var src_addr) = (val~load_grams(), val~load_uint(64), val.preload_uint(256)); + if (i < m) { + ;; one of the first m members, include into validator set + var true_stake = min(stake, (max_f * m_stake) >> 16); + stake -= true_stake; + ;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey; // 288 bits + ;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr; + var weight = (true_stake << 60) / best_stake; + tot_stake += true_stake; + tot_weight += weight; + var vinfo = begin_cell() + .store_uint(adnl_addr ? 0x73 : 0x53, 8) ;; validator_addr#73 or validator#53 + .store_uint(0x8e81278a, 32) ;; ed25519_pubkey#8e81278a + .store_uint(pubkey, 256) ;; pubkey:bits256 + .store_uint(weight, 64); ;; weight:uint64 + if (adnl_addr) { + vinfo~store_uint(adnl_addr, 256); ;; adnl_addr:bits256 + } + vset~udict_set_builder(16, i, vinfo); + frozen~udict_set_builder(256, pubkey, begin_cell() + .store_uint(src_addr, 256) + .store_uint(weight, 64) + .store_grams(true_stake) + .store_int(false, 1)); + } + if (stake) { + ;; non-zero unused part of the stake, credit to the source address + credits~credit_to(src_addr, stake); + } + i += 1; + } until (l.null?()); + throw_unless(49, tot_stake == best_stake); + return (credits, vset, tot_weight, frozen, tot_stake, m); +} + +int conduct_elections(ds, elect, credits) impure { + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + if (now() < elect_close) { + ;; elections not finished yet + return false; + } + if (config_param(0).null?()) { + ;; no configuration smart contract to send result to + return postpone_elections(); + } + var cs = config_param(17).begin_parse(); + min_stake = cs~load_grams(); + var max_stake = cs~load_grams(); + var min_total_stake = cs~load_grams(); + var max_stake_factor = cs~load_uint(32); + cs.end_parse(); + if (total_stake < min_total_stake) { + ;; insufficient total stake, postpone elections + return postpone_elections(); + } + if (failed) { + ;; do not retry failed elections until new stakes arrive + return postpone_elections(); + } + if (finished) { + ;; elections finished + return false; + } + (credits, var vdict, var total_weight, var frozen, var total_stakes, var cnt) = try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor); + ;; pack elections; if cnt==0, set failed=true, finished=false. + failed = (cnt == 0); + finished = ~ failed; + elect = pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, finished); + ifnot (cnt) { + ;; elections failed, set elect_failed to true + set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell()); + return postpone_elections(); + } + ;; serialize a query to the configuration smart contract + ;; to install the computed validator set as the next validator set + var (elect_for, elect_begin_before, elect_end_before, stake_held) = get_validator_conf(); + var start = max(now() + elect_end_before - 60, elect_at); + var main_validators = config_param(16).begin_parse().skip_bits(16).preload_uint(16); + var vset = begin_cell() + .store_uint(0x12, 8) ;; validators_ext#12 + .store_uint(start, 32) ;; utime_since:uint32 + .store_uint(start + elect_for, 32) ;; utime_until:uint32 + .store_uint(cnt, 16) ;; total:(## 16) + .store_uint(min(cnt, main_validators), 16) ;; main:(## 16) + .store_uint(total_weight, 64) ;; total_weight:uint64 + .store_dict(vdict) ;; list:(HashmapE 16 ValidatorDescr) + .end_cell(); + var config_addr = config_param(0).begin_parse().preload_uint(256); + send_validator_set_to_config(config_addr, vset, elect_at); + ;; add frozen to the dictionary of past elections + var past_elections = ds~load_dict(); + past_elections~udict_set_builder(32, elect_at, pack_past_election( + start + elect_for + stake_held, stake_held, vset.cell_hash(), + frozen, total_stakes, 0, null())); + ;; store credits and frozen until end + set_data(begin_cell() + .store_dict(elect) + .store_dict(credits) + .store_dict(past_elections) + .store_slice(ds) + .end_cell()); + return true; +} + +int update_active_vset_id() impure { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var cur_hash = config_param(34).cell_hash(); + if (cur_hash == active_hash) { + ;; validator set unchanged + return false; + } + if (active_id) { + ;; active_id becomes inactive + var (fs, f) = past_elections.udict_get?(32, active_id); + if (f) { + ;; adjust unfreeze time of this validator set + var unfreeze_time = fs~load_uint(32); + var fs0 = fs; + var (stake_held, hash) = (fs~load_uint(32), fs~load_uint(256)); + throw_unless(57, hash == active_hash); + unfreeze_time = now() + stake_held; + past_elections~udict_set_builder(32, active_id, begin_cell() + .store_uint(unfreeze_time, 32) + .store_slice(fs0)); + } + } + ;; look up new active_id by hash + var id = -1; + do { + (id, var fs, var f) = past_elections.udict_get_next?(32, id); + if (f) { + var (tm, hash) = (fs~load_uint(64), fs~load_uint(256)); + if (hash == cur_hash) { + ;; parse more of this record + var (dict, total_stake, bonuses) = (fs~load_dict(), fs~load_grams(), fs~load_grams()); + ;; transfer 1/8 of accumulated everybody's grams to this validator set as bonuses + var amount = (grams >> 3); + grams -= amount; + bonuses += amount; + ;; serialize back + past_elections~udict_set_builder(32, id, begin_cell() + .store_uint(tm, 64) + .store_uint(hash, 256) + .store_dict(dict) + .store_grams(total_stake) + .store_grams(bonuses) + .store_slice(fs)); + ;; found + f = false; + } + } + } until (~ f); + active_id = (id.null?() ? 0 : id); + active_hash = cur_hash; + store_data(elect, credits, past_elections, grams, active_id, active_hash); + return true; +} + +int cell_hash_eq?(cell vset, int expected_vset_hash) inline_ref { + return vset.null?() ? false : cell_hash(vset) == expected_vset_hash; +} + +int validator_set_installed(ds, elect, credits) impure { + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + ifnot (finished) { + ;; elections not finished yet + return false; + } + var past_elections = ds~load_dict(); + var (fs, f) = past_elections.udict_get?(32, elect_at); + ifnot (f) { + ;; no election data in dictionary + return false; + } + ;; recover validator set hash + var vset_hash = fs.skip_bits(64).preload_uint(256); + if (config_param(34).cell_hash_eq?(vset_hash) | config_param(36).cell_hash_eq?(vset_hash)) { + ;; this validator set has been installed, forget elections + set_data(begin_cell() + .store_int(false, 1) ;; forget current elections + .store_dict(credits) + .store_dict(past_elections) + .store_slice(ds) + .end_cell()); + update_active_vset_id(); + return true; + } + return false; +} + +int check_unfreeze() impure { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + int id = -1; + do { + (id, var fs, var f) = past_elections.udict_get_next?(32, id); + if (f) { + var unfreeze_at = fs~load_uint(32); + if ((unfreeze_at <= now()) & (id != active_id)) { + ;; unfreeze! + (credits, past_elections, var unused_prizes) = unfreeze_all(credits, past_elections, id); + grams += unused_prizes; + ;; unfreeze only one at time, exit loop + store_data(elect, credits, past_elections, grams, active_id, active_hash); + ;; exit loop + f = false; + } + } + } until (~ f); + return ~ id.null?(); +} + +int announce_new_elections(ds, elect, credits) { + var next_vset = config_param(36); ;; next validator set + ifnot (next_vset.null?()) { + ;; next validator set exists, no elections needed + return false; + } + var elector_addr = config_param(1).begin_parse().preload_uint(256); + var (my_wc, my_addr) = my_address().parse_std_addr(); + if ((my_wc + 1) | (my_addr != elector_addr)) { + ;; this smart contract is not the elections smart contract anymore, no new elections + return false; + } + var cur_vset = config_param(34); ;; current validator set + if (cur_vset.null?()) { + return false; + } + var (elect_for, elect_begin_before, elect_end_before, stake_held) = get_validator_conf(); + var cur_valid_until = cur_vset.begin_parse().skip_bits(8 + 32).preload_uint(32); + var t = now(); + var t0 = cur_valid_until - elect_begin_before; + if (t < t0) { + ;; too early for the next elections + return false; + } + ;; less than elect_before_begin seconds left, create new elections + if (t - t0 < 60) { + ;; pretend that the elections started at t0 + t = t0; + } + ;; get stake parameters + (_, var min_stake) = config_param(17).begin_parse().load_grams(); + ;; announce new elections + var elect_at = t + elect_begin_before; + ;; elect_at~dump(); + var elect_close = elect_at - elect_end_before; + elect = pack_elect(elect_at, elect_close, min_stake, 0, new_dict(), false, false); + set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell()); + return true; +} + +() run_ticktock(int is_tock) impure { + ;; check whether an election is being conducted + var ds = get_data().begin_parse(); + var (elect, credits) = (ds~load_dict(), ds~load_dict()); + ifnot (elect.null?()) { + ;; have an active election + throw_if(0, conduct_elections(ds, elect, credits)); ;; elections conducted, exit + throw_if(0, validator_set_installed(ds, elect, credits)); ;; validator set installed, current elections removed + } else { + throw_if(0, announce_new_elections(ds, elect, credits)); ;; new elections announced, exit + } + throw_if(0, update_active_vset_id()); ;; active validator set id updated, exit + check_unfreeze(); +} + +;; Get methods + +;; returns active election id or 0 +int active_election_id() method_id { + var elect = get_data().begin_parse().preload_dict(); + return elect.null?() ? 0 : elect.begin_parse().preload_uint(32); +} + +;; checks whether a public key participates in current elections +int participates_in(int validator_pubkey) method_id { + var elect = get_data().begin_parse().preload_dict(); + if (elect.null?()) { + return 0; + } + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + var (mem, found) = members.udict_get?(256, validator_pubkey); + return found ? mem~load_grams() : 0; +} + +;; returns the list of all participants of current elections with their stakes +_ participant_list() method_id { + var elect = get_data().begin_parse().preload_dict(); + if (elect.null?()) { + return nil; + } + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + var l = nil; + var id = (1 << 255) + ((1 << 255) - 1); + do { + (id, var fs, var f) = members.udict_get_prev?(256, id); + if (f) { + l = cons([id, fs~load_grams()], l); + } + } until (~ f); + return l; +} + +;; returns the list of all participants of current elections with their data +_ participant_list_extended() method_id { + var elect = get_data().begin_parse().preload_dict(); + if (elect.null?()) { + return (0, 0, 0, 0, nil, 0, 0); + } + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + var l = nil; + var id = (1 << 255) + ((1 << 255) - 1); + do { + (id, var cs, var f) = members.udict_get_prev?(256, id); + if (f) { + var (stake, time, max_factor, addr, adnl_addr) = (cs~load_grams(), cs~load_uint(32), cs~load_uint(32), cs~load_uint(256), cs~load_uint(256)); + cs.end_parse(); + l = cons([id, [stake, max_factor, addr, adnl_addr]], l); + } + } until (~ f); + return (elect_at, elect_close, min_stake, total_stake, l, failed, finished); +} + +;; computes the return stake +int compute_returned_stake(int wallet_addr) method_id { + var cs = get_data().begin_parse(); + (_, var credits) = (cs~load_dict(), cs~load_dict()); + var (val, f) = credits.udict_get?(256, wallet_addr); + return f ? val~load_grams() : 0; +} + +;; returns the list of past election ids +tuple past_election_ids() method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var id = (1 << 32); + var list = null(); + do { + (id, var fs, var f) = past_elections.udict_get_prev?(32, id); + if (f) { + list = cons(id, list); + } + } until (~ f); + return list; +} + +tuple past_elections() method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var id = (1 << 32); + var list = null(); + do { + (id, var fs, var found) = past_elections.udict_get_prev?(32, id); + if (found) { + list = cons([id, unpack_past_election(fs)], list); + } + } until (~ found); + return list; +} + +tuple past_elections_list() method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var id = (1 << 32); + var list = null(); + do { + (id, var fs, var found) = past_elections.udict_get_prev?(32, id); + if (found) { + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + list = cons([id, unfreeze_at, vset_hash, stake_held], list); + } + } until (~ found); + return list; +} + +_ complete_unpack_complaint(slice cs) inline_ref { + var (complaint, voters, vset_id, weight_remaining) = cs.unpack_complaint_status(); + var voters_list = null(); + var voter_id = (1 << 32); + do { + (voter_id, _, var f) = voters.udict_get_prev?(16, voter_id); + if (f) { + voters_list = cons(voter_id, voters_list); + } + } until (~ f); + return [[complaint.begin_parse().unpack_complaint()], voters_list, vset_id, weight_remaining]; +} + +cell get_past_complaints(int election_id) inline_ref method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var (fs, found?) = past_elections.udict_get?(32, election_id); + ifnot (found?) { + return null(); + } + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + return complaints; +} + +_ show_complaint(int election_id, int chash) method_id { + var complaints = get_past_complaints(election_id); + var (cs, found) = complaints.udict_get?(256, chash); + return found ? complete_unpack_complaint(cs) : null(); +} + +tuple list_complaints(int election_id) method_id { + var complaints = get_past_complaints(election_id); + int id = (1 << 255) + ((1 << 255) - 1); + var list = null(); + do { + (id, var cs, var found?) = complaints.udict_get_prev?(256, id); + if (found?) { + list = cons(pair(id, complete_unpack_complaint(cs)), list); + } + } until (~ found?); + return list; +} + +int complaint_storage_price(int bits, int refs, int expire_in) method_id { + ;; compute complaint storage/creation price + var (deposit, bit_price, cell_price) = get_complaint_prices(); + var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price; + var paid = pps * expire_in + deposit; + return paid + (1 << 30); +} diff --git a/crypto/func/auto-tests/legacy_tests/elector/stdlib.fc b/crypto/func/auto-tests/legacy_tests/elector/stdlib.fc new file mode 100644 index 00000000..0b98eeb4 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/elector/stdlib.fc @@ -0,0 +1,208 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; diff --git a/crypto/func/auto-tests/legacy_tests/eth-bridge-multisig/multisig-code.fc b/crypto/func/auto-tests/legacy_tests/eth-bridge-multisig/multisig-code.fc new file mode 100644 index 00000000..0bfdfb72 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/eth-bridge-multisig/multisig-code.fc @@ -0,0 +1,382 @@ +;; Simple wallet smart contract + +#include "stdlib.fc"; + +(int, int) get_bridge_config() impure inline_ref { + cell bridge_config = config_param(71); + if (bridge_config.cell_null?()) { + bridge_config = config_param(-71); + } + if (bridge_config.cell_null?()) { + return (0, 0); + } + slice ds = bridge_config.begin_parse(); + if (ds.slice_bits() < 512) { + return (0, 0); + } + ;; wc always equals to -1 + int bridge_address = ds~load_uint(256); + int oracles_address = ds~load_uint(256); + return (bridge_address, oracles_address); +} + +_ unpack_state() inline_ref { + var ds = begin_parse(get_data()); + var res = (ds~load_uint(32), ds~load_uint(8), ds~load_uint(8), ds~load_uint(64), ds~load_dict(), ds~load_dict(), ds~load_uint(32)); + ds.end_parse(); + return res; +} + +_ pack_state(cell pending_queries, cell owner_infos, int last_cleaned, int k, int n, int wallet_id, int spend_delay) inline_ref { + return begin_cell() + .store_uint(wallet_id, 32) + .store_uint(n, 8) + .store_uint(k, 8) + .store_uint(last_cleaned, 64) + .store_dict(owner_infos) + .store_dict(pending_queries) + .store_uint(spend_delay,32) + .end_cell(); +} + +_ pack_owner_info(int public_key, int flood) inline_ref { + return begin_cell() + .store_uint(public_key, 256) + .store_uint(flood, 8); +} + +_ unpack_owner_info(slice cs) inline_ref { + return (cs~load_uint(256), cs~load_uint(8)); +} + +(int, int) check_signatures(cell public_keys, cell signatures, int hash, int cnt_bits) inline_ref { + int cnt = 0; + + do { + slice cs = signatures.begin_parse(); + slice signature = cs~load_bits(512); + + int i = cs~load_uint(8); + signatures = cs~load_dict(); + + (slice public_key, var found?) = public_keys.udict_get?(8, i); + throw_unless(37, found?); + throw_unless(38, check_signature(hash, signature, public_key.preload_uint(256))); + + int mask = (1 << i); + int old_cnt_bits = cnt_bits; + cnt_bits |= mask; + int should_check = cnt_bits != old_cnt_bits; + cnt -= should_check; + } until (cell_null?(signatures)); + + return (cnt, cnt_bits); +} + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +(int, cell, int, int) parse_msg(slice in_msg) inline_ref { + int mode = in_msg~load_uint(8); + var msg = in_msg~load_ref(); + var msg' = msg.begin_parse(); + msg'~load_uint(4); ;; flags + msg'~load_msg_addr(); ;; src + (int wc, int addr) = parse_std_addr(msg'~load_msg_addr()); ;; dest + return (mode, msg, wc, addr); +} + +() check_proposed_query(slice in_msg) impure inline { + throw_unless(43, (slice_refs(in_msg) == 1) & (slice_bits(in_msg) == 8)); + (_, _, int wc, _) = parse_msg(in_msg); + wc~impure_touch(); +} + +(int, int, int, slice) unpack_query_data(slice in_msg, int n, slice query, var found?, int root_i) inline_ref { + if (found?) { + throw_unless(35, query~load_int(1)); + (int creator_i, int cnt, int cnt_bits, slice msg) = (query~load_uint(8), query~load_uint(8), query~load_uint(n), query); + throw_unless(36, slice_hash(msg) == slice_hash(in_msg)); + return (creator_i, cnt, cnt_bits, msg); + } + check_proposed_query(in_msg); + + return (root_i, 0, 0, in_msg); +} + +(cell, ()) dec_flood(cell owner_infos, int creator_i) { + (slice owner_info, var found?) = owner_infos.udict_get?(8, creator_i); + (int public_key, int flood) = unpack_owner_info(owner_info); + owner_infos~udict_set_builder(8, creator_i, pack_owner_info(public_key, flood - 1)); + return (owner_infos, ()); +} + +() try_init() impure inline_ref { + ;; first query without signatures is always accepted + (int wallet_id, int n, int k, int last_cleaned, cell owner_infos, cell pending_queries, int spend_delay) = unpack_state(); + throw_if(37, last_cleaned); + accept_message(); + set_data(pack_state(pending_queries, owner_infos, 1, k, n, wallet_id, spend_delay)); +} + +(cell, cell) update_pending_queries(cell pending_queries, cell owner_infos, slice msg, int query_id, int creator_i, int cnt, int cnt_bits, int n, int k) impure inline_ref { + if (cnt >= k) { + accept_message(); + (int bridge_address, int oracles_address) = get_bridge_config(); + (_, int my_addr) = parse_std_addr(my_address()); + var (mode, msg', wc, addr) = parse_msg(msg); + if ( ((wc == -1) & (addr == bridge_address)) | (oracles_address != my_addr) ) { + send_raw_message(msg', mode); + } + pending_queries~udict_set_builder(64, query_id, begin_cell().store_int(0, 1)); + owner_infos~dec_flood(creator_i); + } else { + pending_queries~udict_set_builder(64, query_id, begin_cell() + .store_uint(1, 1) + .store_uint(creator_i, 8) + .store_uint(cnt, 8) + .store_uint(cnt_bits, n) + .store_slice(msg)); + } + return (pending_queries, owner_infos); +} + +(int, int) calc_boc_size(int cells, int bits, slice root) { + cells += 1; + bits += root.slice_bits(); + + while (root.slice_refs()) { + (cells, bits) = calc_boc_size(cells, bits, root~load_ref().begin_parse()); + } + + return (cells, bits); +} + +() recv_external(slice in_msg) impure { + ;; empty message triggers init + if (slice_empty?(in_msg)) { + return try_init(); + } + + ;; Check root signature + slice root_signature = in_msg~load_bits(512); + int root_hash = slice_hash(in_msg); + int root_i = in_msg~load_uint(8); + + (int wallet_id, int n, int k, int last_cleaned, cell owner_infos, cell pending_queries, int spend_delay) = unpack_state(); + + throw_unless(38, now() > spend_delay); + last_cleaned -= last_cleaned == 0; + + (slice owner_info, var found?) = owner_infos.udict_get?(8, root_i); + throw_unless(31, found?); + (int public_key, int flood) = unpack_owner_info(owner_info); + throw_unless(32, check_signature(root_hash, root_signature, public_key)); + + cell signatures = in_msg~load_dict(); + + var hash = slice_hash(in_msg); + int query_wallet_id = in_msg~load_uint(32); + throw_unless(42, query_wallet_id == wallet_id); + + int query_id = in_msg~load_uint(64); + + (int cnt, int bits) = calc_boc_size(0, 0, in_msg); + throw_if(40, (cnt > 8) | (bits > 2048)); + + (slice query, var found?) = pending_queries.udict_get?(64, query_id); + + ifnot (found?) { + flood += 1; + throw_if(39, flood > 10); + } + + var bound = (now() << 32); + throw_if(33, query_id < bound); + + (int creator_i, int cnt, int cnt_bits, slice msg) = unpack_query_data(in_msg, n, query, found?, root_i); + int mask = 1 << root_i; + throw_if(34, cnt_bits & mask); + cnt_bits |= mask; + cnt += 1; + + throw_if(41, ~ found? & (cnt < k) & (bound + ((60 * 60) << 32) > query_id)); + + set_gas_limit(100000); + + ifnot (found?) { + owner_infos~udict_set_builder(8, root_i, pack_owner_info(public_key, flood)); + } + + (pending_queries, owner_infos) = update_pending_queries(pending_queries, owner_infos, msg, query_id, creator_i, cnt, cnt_bits, n, k); + set_data(pack_state(pending_queries, owner_infos, last_cleaned, k, n, wallet_id, spend_delay)); + + commit(); + + int need_save = 0; + ifnot (cell_null?(signatures) | (cnt >= k)) { + (int new_cnt, cnt_bits) = check_signatures(owner_infos, signatures, hash, cnt_bits); + cnt += new_cnt; + (pending_queries, owner_infos) = update_pending_queries(pending_queries, owner_infos, msg, query_id, creator_i, cnt, cnt_bits, n, k); + need_save = -1; + } + + accept_message(); + bound -= (64 << 32); ;; clean up records expired more than 64 seconds ago + int old_last_cleaned = last_cleaned; + do { + var (pending_queries', i, query, f) = pending_queries.udict_delete_get_min(64); + f~touch(); + if (f) { + f = (i < bound); + } + if (f) { + if (query~load_int(1)) { + owner_infos~dec_flood(query~load_uint(8)); + } + pending_queries = pending_queries'; + last_cleaned = i; + need_save = -1; + } + } until (~ f); + + if (need_save) { + set_data(pack_state(pending_queries, owner_infos, last_cleaned, k, n, wallet_id, spend_delay)); + } +} + +;; Get methods +;; returns -1 for processed queries, 0 for unprocessed, 1 for unknown (forgotten) +(int, int) get_query_state(int query_id) method_id { + (_, int n, _, int last_cleaned, _, cell pending_queries, _) = unpack_state(); + (slice cs, var found) = pending_queries.udict_get?(64, query_id); + if (found) { + if (cs~load_int(1)) { + cs~load_uint(8 + 8); + return (0, cs~load_uint(n)); + } else { + return (-1, 0); + } + } else { + return (-(query_id <= last_cleaned), 0); + } +} + +int processed?(int query_id) method_id { + (int x, _) = get_query_state(query_id); + return x; +} + +cell create_init_state(int wallet_id, int n, int k, cell owners_info, int spend_delay) method_id { + return pack_state(new_dict(), owners_info, 0, k, n, wallet_id, spend_delay); +} + +cell merge_list(cell a, cell b) { + if (cell_null?(a)) { + return b; + } + if (cell_null?(b)) { + return a; + } + slice as = a.begin_parse(); + if (as.slice_refs() != 0) { + cell tail = merge_list(as~load_ref(), b); + return begin_cell().store_slice(as).store_ref(tail).end_cell(); + } + + as~skip_last_bits(1); + ;; as~skip_bits(1); + return begin_cell().store_slice(as).store_dict(b).end_cell(); + +} + +cell get_public_keys() method_id { + (_, _, _, _, cell public_keys, _, _) = unpack_state(); + return public_keys; +} + +(int, int) check_query_signatures(cell query) method_id { + slice cs = query.begin_parse(); + slice root_signature = cs~load_bits(512); + int root_hash = slice_hash(cs); + int root_i = cs~load_uint(8); + + cell public_keys = get_public_keys(); + (slice public_key, var found?) = public_keys.udict_get?(8, root_i); + throw_unless(31, found?); + throw_unless(32, check_signature(root_hash, root_signature, public_key.preload_uint(256))); + + int mask = 1 << root_i; + + cell signatures = cs~load_dict(); + if (cell_null?(signatures)) { + return (1, mask); + } + (int cnt, mask) = check_signatures(public_keys, signatures, slice_hash(cs), mask); + return (cnt + 1, mask); +} + +int message_signed_by_id?(int id, int query_id) method_id { + (_, int n, _, _, _, cell pending_queries, _) = unpack_state(); + (var cs, var f) = pending_queries.udict_get?(64, query_id); + if (f) { + if (cs~load_int(1)) { + int cnt_bits = cs.skip_bits(8 + 8).preload_uint(n); + if (cnt_bits & (1 << id)) { + return -1; + } + return 0; + } + return -1; + } + return 0; +} + +cell messages_by_mask(int mask) method_id { + (_, int n, _, _, _, cell pending_queries, _) = unpack_state(); + int i = -1; + cell a = new_dict(); + do { + (i, var cs, var f) = pending_queries.udict_get_next?(64, i); + if (f) { + if (cs~load_int(1)) { + int cnt_bits = cs.skip_bits(8 + 8).preload_uint(n); + if (cnt_bits & mask) { + a~udict_set_builder(64, i, begin_cell().store_slice(cs)); + } + } + } + } until (~ f); + return a; +} + +cell get_messages_unsigned_by_id(int id) method_id { + return messages_by_mask(1 << id); +} + +cell get_messages_unsigned() method_id { + return messages_by_mask(~ 0); +} + +(int, int) get_n_k() method_id { + (_, int n, int k, _, _, _, _) = unpack_state(); + return (n, k); +} + +cell merge_inner_queries(cell a, cell b) method_id { + slice ca = a.begin_parse(); + slice cb = b.begin_parse(); + cell list_a = ca~load_dict(); + cell list_b = cb~load_dict(); + throw_unless(31, slice_hash(ca) == slice_hash(cb)); + return begin_cell() + .store_dict(merge_list(list_a, list_b)) + .store_slice(ca) + .end_cell(); +} + +int get_lock_timeout() method_id { + (_, _, _, _, _, _, int spend_delay) = unpack_state(); + return spend_delay; +} diff --git a/crypto/func/auto-tests/legacy_tests/eth-bridge-multisig/stdlib.fc b/crypto/func/auto-tests/legacy_tests/eth-bridge-multisig/stdlib.fc new file mode 100644 index 00000000..1d144040 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/eth-bridge-multisig/stdlib.fc @@ -0,0 +1,209 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + diff --git a/crypto/func/auto-tests/legacy_tests/gg-marketplace/nft-marketplace-v2.fc b/crypto/func/auto-tests/legacy_tests/gg-marketplace/nft-marketplace-v2.fc new file mode 100644 index 00000000..58444723 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/gg-marketplace/nft-marketplace-v2.fc @@ -0,0 +1,110 @@ +;; NFT marketplace smart contract v2 +;; Extends wallet v3r2 & adds ability to deploy sales + +#include "stdlib.fc"; + +;; +;; storage scheme +;; +;; storage#_ seqno:uint32 subwallet:uint32 public_key:uint25 +;; = Storage; +;; +_ load_data() { + var ds = get_data().begin_parse(); + return ( + ds~load_uint(32), ;; seqno + ds~load_uint(32), ;; subwallet + ds~load_uint(256) ;; public_key + ); +} + +() store_data(var data) impure { + ( + int seqno, + int subwallet, + int public_key + ) = data; + + set_data( + begin_cell() + .store_uint(seqno, 32) + .store_uint(subwallet, 32) + .store_uint(public_key, 256) + .end_cell() + ); +} + +() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { + if (in_msg_body.slice_empty?()) { ;; ignore empty messages + return (); + } + + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if (flags & 1) { ;; ignore all bounced messages + return (); + } + slice sender_address = cs~load_msg_addr(); + var (seqno, subwallet, public_key) = load_data(); + + int op = in_msg_body~load_uint(32); + + if (op == 1) { ;; deploy new signed sale + var signature = in_msg_body~load_bits(512); + throw_unless(35, check_signature(slice_hash(in_msg_body), signature, public_key)); + + (cell state_init, cell body) = (in_msg_body~load_ref(), in_msg_body~load_ref()); + + int state_init_hash = cell_hash(state_init); + slice dest_address = begin_cell().store_int(0, 8).store_uint(state_init_hash, 256).end_cell().begin_parse(); + + var msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(4, 3).store_slice(dest_address) + .store_grams(0) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(body); + + send_raw_message(msg.end_cell(), 64); ;; carry remaining value of message + return (); + } + + return (); +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + throw_if(35, valid_until <= now()); + var (seqno, subwallet, public_key) = load_data(); + throw_unless(33, msg_seqno == seqno); + throw_unless(34, subwallet_id == subwallet); + throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + cs~touch(); + while (cs.slice_refs()) { + var mode = cs~load_uint(8); + send_raw_message(cs~load_ref(), mode); + } + + store_data( + seqno + 1, + subwallet, + public_key + ); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(64); + return cs.preload_uint(256); +} diff --git a/crypto/func/auto-tests/legacy_tests/gg-marketplace/stdlib.fc b/crypto/func/auto-tests/legacy_tests/gg-marketplace/stdlib.fc new file mode 100644 index 00000000..ba9177e9 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/gg-marketplace/stdlib.fc @@ -0,0 +1,215 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; + builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; + +int equal_slices (slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; +builder store_builder(builder to, builder from) asm "STBR"; \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/constants.fc b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/constants.fc new file mode 100644 index 00000000..c260f95e --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/constants.fc @@ -0,0 +1,13 @@ +;; operations (constant values taken from crc32 on op message in the companion .tlb files and appear during build) +int op::increment() asm "0x37491f2f PUSHINT"; +int op::deposit() asm "0x47d54391 PUSHINT"; +int op::withdraw() asm "0x41836980 PUSHINT"; +int op::transfer_ownership() asm "0x2da38aaf PUSHINT"; + +;; errors +int error::unknown_op() asm "101 PUSHINT"; +int error::access_denied() asm "102 PUSHINT"; +int error::insufficient_balance() asm "103 PUSHINT"; + +;; other +int const::min_tons_for_storage() asm "10000000 PUSHINT"; ;; 0.01 TON \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/jetton-utils.fc b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/jetton-utils.fc new file mode 100644 index 00000000..296816a1 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/jetton-utils.fc @@ -0,0 +1,30 @@ +cell pack_jetton_wallet_data(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return begin_cell() + .store_coins(balance) + .store_slice(owner_address) + .store_slice(jetton_master_address) + .store_ref(jetton_wallet_code) + .end_cell(); +} + +cell calculate_jetton_wallet_state_init(slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return begin_cell() + .store_uint(0, 2) + .store_dict(jetton_wallet_code) + .store_dict(pack_jetton_wallet_data(0, owner_address, jetton_master_address, jetton_wallet_code)) + .store_uint(0, 1) + .end_cell(); +} + +slice calculate_jetton_wallet_address(cell state_init) inline { + return begin_cell().store_uint(4, 3) + .store_int(workchain(), 8) + .store_uint(cell_hash(state_init), 256) + .end_cell() + .begin_parse(); +} + +slice calculate_user_jetton_wallet_address(slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return calculate_jetton_wallet_address(calculate_jetton_wallet_state_init(owner_address, jetton_master_address, jetton_wallet_code)); +} + diff --git a/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/op-codes.fc b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/op-codes.fc new file mode 100644 index 00000000..3b0df046 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/op-codes.fc @@ -0,0 +1,9 @@ +int op::transfer() asm "0xf8a7ea5 PUSHINT"; +int op::transfer_notification() asm "0x7362d09c PUSHINT"; +int op::internal_transfer() asm "0x178d4519 PUSHINT"; +int op::excesses() asm "0xd53276db PUSHINT"; +int op::burn() asm "0x595f07bc PUSHINT"; +int op::burn_notification() asm "0x7bdd97de PUSHINT"; + +;; Minter +int op::mint() asm "21 PUSHINT"; diff --git a/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/params.fc b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/params.fc new file mode 100644 index 00000000..e28eac45 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/params.fc @@ -0,0 +1,6 @@ +int workchain() asm "0 PUSHINT"; + +() force_chain(slice addr) impure { + (int wc, _) = parse_std_addr(addr); + throw_unless(333, wc == workchain()); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/stdlib.fc b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/stdlib.fc new file mode 100644 index 00000000..05c3a4c0 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/stdlib.fc @@ -0,0 +1,215 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; + +int equal_slices (slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; +builder store_builder(builder to, builder from) asm "STBR"; diff --git a/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/utils.fc b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/utils.fc new file mode 100644 index 00000000..230a8f79 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-minter/imports/utils.fc @@ -0,0 +1,9 @@ +() send_grams(slice address, int amount) impure { + cell msg = begin_cell() + .store_uint (0x18, 6) ;; bounce + .store_slice(address) ;; 267 bit address + .store_grams(amount) + .store_uint(0, 107) ;; 106 zeroes + 0 as an indicator that there is no cell with the data + .end_cell(); + send_raw_message(msg, 3); ;; mode, 2 for ignoring errors, 1 for sender pays fees, 64 for returning inbound message value +} diff --git a/crypto/func/auto-tests/legacy_tests/jetton-minter/jetton-minter.fc b/crypto/func/auto-tests/legacy_tests/jetton-minter/jetton-minter.fc new file mode 100644 index 00000000..73776e64 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-minter/jetton-minter.fc @@ -0,0 +1,122 @@ +;; Jettons minter smart contract + +;; storage scheme +;; storage#_ total_supply:Coins admin_address:MsgAddress content:^Cell jetton_wallet_code:^Cell = Storage; + +#include "imports/stdlib.fc"; +#include "imports/params.fc"; +#include "imports/constants.fc"; +#include "imports/jetton-utils.fc"; +#include "imports/op-codes.fc"; +#include "imports/utils.fc"; +#pragma version >=0.2.0; + +(int, slice, cell, cell) load_data() inline { + slice ds = get_data().begin_parse(); + return ( + ds~load_coins(), ;; total_supply + ds~load_msg_addr(), ;; admin_address + ds~load_ref(), ;; content + ds~load_ref() ;; jetton_wallet_code + ); +} + +() save_data(int total_supply, slice admin_address, cell content, cell jetton_wallet_code) impure inline { + set_data(begin_cell() + .store_coins(total_supply) + .store_slice(admin_address) + .store_ref(content) + .store_ref(jetton_wallet_code) + .end_cell() + ); +} + +() mint_tokens(slice to_address, cell jetton_wallet_code, int amount, cell master_msg) impure { + cell state_init = calculate_jetton_wallet_state_init(to_address, my_address(), jetton_wallet_code); + slice to_wallet_address = calculate_jetton_wallet_address(state_init); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(to_wallet_address) + .store_coins(amount) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(master_msg); + send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors +} + +() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { + if (in_msg_body.slice_empty?()) { ;; ignore empty messages + return (); + } + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if (flags & 1) { ;; ignore all bounced messages + return (); + } + slice sender_address = cs~load_msg_addr(); + + int op = in_msg_body~load_uint(32); + int query_id = in_msg_body~load_uint(64); + + (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); + + if (op == op::mint()) { + throw_unless(73, equal_slices(sender_address, admin_address)); + slice to_address = in_msg_body~load_msg_addr(); + int amount = in_msg_body~load_coins(); + cell master_msg = in_msg_body~load_ref(); + slice master_msg_cs = master_msg.begin_parse(); + master_msg_cs~skip_bits(32 + 64); ;; op + query_id + int jetton_amount = master_msg_cs~load_coins(); + mint_tokens(to_address, jetton_wallet_code, amount, master_msg); + save_data(total_supply + jetton_amount, admin_address, content, jetton_wallet_code); + return (); + } + + if (op == op::burn_notification()) { + int jetton_amount = in_msg_body~load_coins(); + slice from_address = in_msg_body~load_msg_addr(); + throw_unless(74, + equal_slices(calculate_user_jetton_wallet_address(from_address, my_address(), jetton_wallet_code), sender_address) + ); + save_data(total_supply - jetton_amount, admin_address, content, jetton_wallet_code); + slice response_address = in_msg_body~load_msg_addr(); + if (response_address.preload_uint(2) != 0) { + var msg = begin_cell() + .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 + .store_slice(response_address) + .store_coins(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::excesses(), 32) + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 2 + 64); + } + return (); + } + + if (op == 3) { ;; change admin + throw_unless(73, equal_slices(sender_address, admin_address)); + slice new_admin_address = in_msg_body~load_msg_addr(); + save_data(total_supply, new_admin_address, content, jetton_wallet_code); + return (); + } + + if (op == 4) { ;; change content, delete this for immutable tokens + throw_unless(73, equal_slices(sender_address, admin_address)); + save_data(total_supply, admin_address, in_msg_body~load_ref(), jetton_wallet_code); + return (); + } + + throw(0xffff); +} + +(int, int, slice, cell, cell) get_jetton_data() method_id { + (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); + return (total_supply, -1, admin_address, content, jetton_wallet_code); +} + +slice get_wallet_address(slice owner_address) method_id { + (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); + return calculate_user_jetton_wallet_address(owner_address, my_address(), jetton_wallet_code); +} diff --git a/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/constants.fc b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/constants.fc new file mode 100644 index 00000000..02e239f7 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/constants.fc @@ -0,0 +1,17 @@ +;; operations (constant values taken from crc32 on op message in the companion .tlb files and appear during build) +int op::increment() asm "0x37491f2f PUSHINT"; +int op::deposit() asm "0x47d54391 PUSHINT"; +int op::withdraw() asm "0x41836980 PUSHINT"; +int op::transfer_ownership() asm "0x2da38aaf PUSHINT"; + +;; errors +int error::unknown_op() asm "101 PUSHINT"; +int error::access_denied() asm "102 PUSHINT"; +int error::insufficient_balance() asm "103 PUSHINT"; + +;; other +int const::min_tons_for_storage() asm "10000000 PUSHINT"; ;; 0.01 TON + +;; 6905(computational_gas_price) * 1000(cur_gas_price) = 6905000 +;; ceil(6905000) = 10000000 ~= 0.01 TON +int const::provide_address_gas_consumption() asm "10000000 PUSHINT"; \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/jetton-utils.fc b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/jetton-utils.fc new file mode 100644 index 00000000..296816a1 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/jetton-utils.fc @@ -0,0 +1,30 @@ +cell pack_jetton_wallet_data(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return begin_cell() + .store_coins(balance) + .store_slice(owner_address) + .store_slice(jetton_master_address) + .store_ref(jetton_wallet_code) + .end_cell(); +} + +cell calculate_jetton_wallet_state_init(slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return begin_cell() + .store_uint(0, 2) + .store_dict(jetton_wallet_code) + .store_dict(pack_jetton_wallet_data(0, owner_address, jetton_master_address, jetton_wallet_code)) + .store_uint(0, 1) + .end_cell(); +} + +slice calculate_jetton_wallet_address(cell state_init) inline { + return begin_cell().store_uint(4, 3) + .store_int(workchain(), 8) + .store_uint(cell_hash(state_init), 256) + .end_cell() + .begin_parse(); +} + +slice calculate_user_jetton_wallet_address(slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return calculate_jetton_wallet_address(calculate_jetton_wallet_state_init(owner_address, jetton_master_address, jetton_wallet_code)); +} + diff --git a/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/op-codes.fc b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/op-codes.fc new file mode 100644 index 00000000..3b0df046 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/op-codes.fc @@ -0,0 +1,9 @@ +int op::transfer() asm "0xf8a7ea5 PUSHINT"; +int op::transfer_notification() asm "0x7362d09c PUSHINT"; +int op::internal_transfer() asm "0x178d4519 PUSHINT"; +int op::excesses() asm "0xd53276db PUSHINT"; +int op::burn() asm "0x595f07bc PUSHINT"; +int op::burn_notification() asm "0x7bdd97de PUSHINT"; + +;; Minter +int op::mint() asm "21 PUSHINT"; diff --git a/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/params.fc b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/params.fc new file mode 100644 index 00000000..e28eac45 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/params.fc @@ -0,0 +1,6 @@ +int workchain() asm "0 PUSHINT"; + +() force_chain(slice addr) impure { + (int wc, _) = parse_std_addr(addr); + throw_unless(333, wc == workchain()); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/stdlib.fc b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/stdlib.fc new file mode 100644 index 00000000..05c3a4c0 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/stdlib.fc @@ -0,0 +1,215 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; + +int equal_slices (slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; +builder store_builder(builder to, builder from) asm "STBR"; diff --git a/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/utils.fc b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/utils.fc new file mode 100644 index 00000000..230a8f79 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-wallet/imports/utils.fc @@ -0,0 +1,9 @@ +() send_grams(slice address, int amount) impure { + cell msg = begin_cell() + .store_uint (0x18, 6) ;; bounce + .store_slice(address) ;; 267 bit address + .store_grams(amount) + .store_uint(0, 107) ;; 106 zeroes + 0 as an indicator that there is no cell with the data + .end_cell(); + send_raw_message(msg, 3); ;; mode, 2 for ignoring errors, 1 for sender pays fees, 64 for returning inbound message value +} diff --git a/crypto/func/auto-tests/legacy_tests/jetton-wallet/jetton-wallet.fc b/crypto/func/auto-tests/legacy_tests/jetton-wallet/jetton-wallet.fc new file mode 100644 index 00000000..01cc91bc --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/jetton-wallet/jetton-wallet.fc @@ -0,0 +1,250 @@ +;; Jetton Wallet Smart Contract + +#include "imports/stdlib.fc"; +#include "imports/params.fc"; +#include "imports/constants.fc"; +#include "imports/jetton-utils.fc"; +#include "imports/op-codes.fc"; +#include "imports/utils.fc"; +#pragma version >=0.2.0; + +{- + +NOTE that this tokens can be transferred within the same workchain. + +This is suitable for most tokens, if you need tokens transferable between workchains there are two solutions: + +1) use more expensive but universal function to calculate message forward fee for arbitrary destination (see `misc/forward-fee-calc.cs`) + +2) use token holder proxies in target workchain (that way even 'non-universal' token can be used from any workchain) + +-} + +const min_tons_for_storage = 10000000; ;; 0.01 TON +const gas_consumption = 10000000; ;; 0.01 TON + +{- + Storage + storage#_ balance:Coins owner_address:MsgAddressInt jetton_master_address:MsgAddressInt jetton_wallet_code:^Cell = Storage; +-} + +(int, slice, slice, cell) load_data() inline { + slice ds = get_data().begin_parse(); + return (ds~load_coins(), ds~load_msg_addr(), ds~load_msg_addr(), ds~load_ref()); +} + +() save_data (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) impure inline { + set_data(pack_jetton_wallet_data(balance, owner_address, jetton_master_address, jetton_wallet_code)); +} + +{- + transfer query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress + response_destination:MsgAddress custom_payload:(Maybe ^Cell) + forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell) + = InternalMsgBody; + internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress + response_address:MsgAddress + forward_ton_amount:(VarUInteger 16) + forward_payload:(Either Cell ^Cell) + = InternalMsgBody; +-} + +() send_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure { + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + slice to_owner_address = in_msg_body~load_msg_addr(); + force_chain(to_owner_address); + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + balance -= jetton_amount; + + throw_unless(705, equal_slices(owner_address, sender_address)); + throw_unless(706, balance >= 0); + + cell state_init = calculate_jetton_wallet_state_init(to_owner_address, jetton_master_address, jetton_wallet_code); + slice to_wallet_address = calculate_jetton_wallet_address(state_init); + slice response_address = in_msg_body~load_msg_addr(); + cell custom_payload = in_msg_body~load_dict(); + int forward_ton_amount = in_msg_body~load_coins(); + throw_unless(708, slice_bits(in_msg_body) >= 1); + slice either_forward_payload = in_msg_body; + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(to_wallet_address) + .store_coins(0) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init); + var msg_body = begin_cell() + .store_uint(op::internal_transfer(), 32) + .store_uint(query_id, 64) + .store_coins(jetton_amount) + .store_slice(owner_address) + .store_slice(response_address) + .store_coins(forward_ton_amount) + .store_slice(either_forward_payload) + .end_cell(); + + msg = msg.store_ref(msg_body); + int fwd_count = forward_ton_amount ? 2 : 1; + throw_unless(709, msg_value > + forward_ton_amount + + ;; 3 messages: wal1->wal2, wal2->owner, wal2->response + ;; but last one is optional (it is ok if it fails) + fwd_count * fwd_fee + + (2 * gas_consumption + min_tons_for_storage)); ;; TODO(shahar) ? + ;; universal message send fee calculation may be activated here + ;; by using this instead of fwd_fee + ;; msg_fwd_fee(to_wallet, msg_body, state_init, 15) + + send_raw_message(msg.end_cell(), 64); ;; revert on errors + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +{- + internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress + response_address:MsgAddress + forward_ton_amount:(VarUInteger 16) + forward_payload:(Either Cell ^Cell) + = InternalMsgBody; +-} + +() receive_tokens (slice in_msg_body, slice sender_address, int my_ton_balance, int fwd_fee, int msg_value) impure { + ;; NOTE we can not allow fails in action phase since in that case there will be + ;; no bounce. Thus check and throw in computation phase. + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + balance += jetton_amount; + slice from_address = in_msg_body~load_msg_addr(); + slice response_address = in_msg_body~load_msg_addr(); + throw_unless(707, + equal_slices(jetton_master_address, sender_address) + | + equal_slices(calculate_user_jetton_wallet_address(from_address, jetton_master_address, jetton_wallet_code), sender_address) + ); + int forward_ton_amount = in_msg_body~load_coins(); + + int ton_balance_before_msg = my_ton_balance - msg_value; + int storage_fee = min_tons_for_storage - min(ton_balance_before_msg, min_tons_for_storage); + msg_value -= (storage_fee + gas_consumption); + if(forward_ton_amount) { + msg_value -= (forward_ton_amount + fwd_fee); + slice either_forward_payload = in_msg_body; + + var msg_body = begin_cell() + .store_uint(op::transfer_notification(), 32) + .store_uint(query_id, 64) + .store_coins(jetton_amount) + .store_slice(from_address) + .store_slice(either_forward_payload) + .end_cell(); + + var msg = begin_cell() + .store_uint(0x10, 6) ;; we should not bounce here cause receiver can have uninitialized contract + .store_slice(owner_address) + .store_coins(forward_ton_amount) + .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_ref(msg_body); + + send_raw_message(msg.end_cell(), 1); + } + + if ((response_address.preload_uint(2) != 0) & (msg_value > 0)) { + var msg = begin_cell() + .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000 + .store_slice(response_address) + .store_coins(msg_value) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::excesses(), 32) + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 2); + } + + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +() burn_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure { + ;; NOTE we can not allow fails in action phase since in that case there will be + ;; no bounce. Thus check and throw in computation phase. + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + slice response_address = in_msg_body~load_msg_addr(); + ;; ignore custom payload + ;; slice custom_payload = in_msg_body~load_dict(); + balance -= jetton_amount; + throw_unless(705, equal_slices(owner_address, sender_address)); + throw_unless(706, balance >= 0); + throw_unless(707, msg_value > fwd_fee + 2 * gas_consumption); + + var msg_body = begin_cell() + .store_uint(op::burn_notification(), 32) + .store_uint(query_id, 64) + .store_coins(jetton_amount) + .store_slice(owner_address) + .store_slice(response_address) + .end_cell(); + + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(jetton_master_address) + .store_coins(0) + .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_ref(msg_body); + + send_raw_message(msg.end_cell(), 64); + + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +() on_bounce (slice in_msg_body) impure { + in_msg_body~skip_bits(32); ;; 0xFFFFFFFF + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + int op = in_msg_body~load_uint(32); + throw_unless(709, (op == op::internal_transfer()) | (op == op::burn_notification())); + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + balance += jetton_amount; + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { + if (in_msg_body.slice_empty?()) { ;; ignore empty messages + return (); + } + + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + if (flags & 1) { + on_bounce(in_msg_body); + return (); + } + slice sender_address = cs~load_msg_addr(); + cs~load_msg_addr(); ;; skip dst + cs~load_coins(); ;; skip value + cs~skip_bits(1); ;; skip extracurrency collection + cs~load_coins(); ;; skip ihr_fee + int fwd_fee = muldiv(cs~load_coins(), 3, 2); ;; we use message fwd_fee for estimation of forward_payload costs + + int op = in_msg_body~load_uint(32); + + if (op == op::transfer()) { ;; outgoing transfer + send_tokens(in_msg_body, sender_address, msg_value, fwd_fee); + return (); + } + + if (op == op::internal_transfer()) { ;; incoming transfer + receive_tokens(in_msg_body, sender_address, my_balance, fwd_fee, msg_value); + return (); + } + + if (op == op::burn()) { ;; burn + burn_tokens(in_msg_body, sender_address, msg_value, fwd_fee); + return (); + } + + throw(0xffff); +} + +(int, slice, slice, cell) get_wallet_data() method_id { + return load_data(); +} diff --git a/crypto/func/auto-tests/legacy_tests/nft-collection/nft-collection-editable.fc b/crypto/func/auto-tests/legacy_tests/nft-collection/nft-collection-editable.fc new file mode 100644 index 00000000..73d86b16 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/nft-collection/nft-collection-editable.fc @@ -0,0 +1,173 @@ +;; NFT collection smart contract + +;; storage scheme +;; default#_ royalty_factor:uint16 royalty_base:uint16 royalty_address:MsgAddress = RoyaltyParams; +;; storage#_ owner_address:MsgAddress next_item_index:uint64 +;; ^[collection_content:^Cell common_content:^Cell] +;; nft_item_code:^Cell +;; royalty_params:^RoyaltyParams +;; = Storage; + +#include "op-codes.fc"; +#include "stdlib.fc"; +#include "params.fc"; + +(slice, int, cell, cell, cell) load_data() inline { + var ds = get_data().begin_parse(); + return + (ds~load_msg_addr(), ;; owner_address + ds~load_uint(64), ;; next_item_index + ds~load_ref(), ;; content + ds~load_ref(), ;; nft_item_code + ds~load_ref() ;; royalty_params + ); +} + +() save_data(slice owner_address, int next_item_index, cell content, cell nft_item_code, cell royalty_params) impure inline { + set_data(begin_cell() + .store_slice(owner_address) + .store_uint(next_item_index, 64) + .store_ref(content) + .store_ref(nft_item_code) + .store_ref(royalty_params) + .end_cell()); +} + +cell calculate_nft_item_state_init(int item_index, cell nft_item_code) { + cell data = begin_cell().store_uint(item_index, 64).store_slice(my_address()).end_cell(); + return begin_cell().store_uint(0, 2).store_dict(nft_item_code).store_dict(data).store_uint(0, 1).end_cell(); +} + +slice calculate_nft_item_address(int wc, cell state_init) { + return begin_cell().store_uint(4, 3) + .store_int(wc, 8) + .store_uint(cell_hash(state_init), 256) + .end_cell() + .begin_parse(); +} + +() deploy_nft_item(int item_index, cell nft_item_code, int amount, cell nft_content) impure { + cell state_init = calculate_nft_item_state_init(item_index, nft_item_code); + slice nft_address = calculate_nft_item_address(workchain(), state_init); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(nft_address) + .store_coins(amount) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(nft_content); + send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors +} + +() send_royalty_params(slice to_address, int query_id, slice data) impure inline { + var msg = begin_cell() + .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool packages:MsgAddress -> 011000 + .store_slice(to_address) + .store_coins(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::report_royalty_params(), 32) + .store_uint(query_id, 64) + .store_slice(data); + send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message +} + +() recv_internal(cell in_msg_full, slice in_msg_body) impure { + if (in_msg_body.slice_empty?()) { ;; ignore empty messages + return (); + } + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if (flags & 1) { ;; ignore all bounced messages + return (); + } + slice sender_address = cs~load_msg_addr(); + + int op = in_msg_body~load_uint(32); + int query_id = in_msg_body~load_uint(64); + + var (owner_address, next_item_index, content, nft_item_code, royalty_params) = load_data(); + + if (op == op::get_royalty_params()) { + send_royalty_params(sender_address, query_id, royalty_params.begin_parse()); + return (); + } + + throw_unless(401, equal_slices(sender_address, owner_address)); + + + if (op == 1) { ;; deploy new nft + int item_index = in_msg_body~load_uint(64); + throw_unless(402, item_index <= next_item_index); + var is_last = item_index == next_item_index; + deploy_nft_item(item_index, nft_item_code, in_msg_body~load_coins(), in_msg_body~load_ref()); + if (is_last) { + next_item_index += 1; + save_data(owner_address, next_item_index, content, nft_item_code, royalty_params); + } + return (); + } + if (op == 2) { ;; batch deploy of new nfts + int counter = 0; + cell deploy_list = in_msg_body~load_ref(); + do { + var (item_index, item, f?) = deploy_list~udict::delete_get_min(64); + if (f?) { + counter += 1; + if (counter >= 250) { ;; Limit due to limits of action list size + throw(399); + } + + throw_unless(403 + counter, item_index <= next_item_index); + deploy_nft_item(item_index, nft_item_code, item~load_coins(), item~load_ref()); + if (item_index == next_item_index) { + next_item_index += 1; + } + } + } until ( ~ f?); + save_data(owner_address, next_item_index, content, nft_item_code, royalty_params); + return (); + } + if (op == 3) { ;; change owner + slice new_owner = in_msg_body~load_msg_addr(); + save_data(new_owner, next_item_index, content, nft_item_code, royalty_params); + return (); + } + if (op == 4) { ;; change content + save_data(owner_address, next_item_index, in_msg_body~load_ref(), nft_item_code, in_msg_body~load_ref()); + return (); + } + throw(0xffff); +} + +;; Get methods + +(int, cell, slice) get_collection_data() method_id { + var (owner_address, next_item_index, content, _, _) = load_data(); + slice cs = content.begin_parse(); + return (next_item_index, cs~load_ref(), owner_address); +} + +slice get_nft_address_by_index(int index) method_id { + var (_, _, _, nft_item_code, _) = load_data(); + cell state_init = calculate_nft_item_state_init(index, nft_item_code); + return calculate_nft_item_address(0, state_init); +} + +(int, int, slice) royalty_params() method_id { + var (_, _, _, _, royalty) = load_data(); + slice rs = royalty.begin_parse(); + return (rs~load_uint(16), rs~load_uint(16), rs~load_msg_addr()); +} + +cell get_nft_content(int index, cell individual_nft_content) method_id { + var (_, _, content, _, _) = load_data(); + slice cs = content.begin_parse(); + cs~load_ref(); + slice common_content = cs~load_ref().begin_parse(); + return (begin_cell() + .store_uint(1, 8) ;; offchain tag + .store_slice(common_content) + .store_ref(individual_nft_content) + .end_cell()); +} diff --git a/crypto/func/auto-tests/legacy_tests/nft-collection/op-codes.fc b/crypto/func/auto-tests/legacy_tests/nft-collection/op-codes.fc new file mode 100644 index 00000000..948a977b --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/nft-collection/op-codes.fc @@ -0,0 +1,24 @@ +int op::transfer() asm "0x5fcc3d14 PUSHINT"; +int op::ownership_assigned() asm "0x05138d91 PUSHINT"; +int op::excesses() asm "0xd53276db PUSHINT"; +int op::get_static_data() asm "0x2fcb26a2 PUSHINT"; +int op::report_static_data() asm "0x8b771735 PUSHINT"; +int op::get_royalty_params() asm "0x693d3950 PUSHINT"; +int op::report_royalty_params() asm "0xa8cb00ad PUSHINT"; + +;; NFTEditable +int op::edit_content() asm "0x1a0b9d51 PUSHINT"; +int op::transfer_editorship() asm "0x1c04412a PUSHINT"; +int op::editorship_assigned() asm "0x511a4463 PUSHINT"; + +;; SBT +int op::request_owner() asm "0xd0c3bfea PUSHINT"; +int op::owner_info() asm "0x0dd607e3 PUSHINT"; + +int op::prove_ownership() asm "0x04ded148 PUSHINT"; +int op::ownership_proof() asm "0x0524c7ae PUSHINT"; +int op::ownership_proof_bounced() asm "0xc18e86d2 PUSHINT"; + +int op::destroy() asm "0x1f04537a PUSHINT"; +int op::revoke() asm "0x6f89f5e3 PUSHINT"; +int op::take_excess() asm "0xd136d3b3 PUSHINT"; diff --git a/crypto/func/auto-tests/legacy_tests/nft-collection/params.fc b/crypto/func/auto-tests/legacy_tests/nft-collection/params.fc new file mode 100644 index 00000000..27a42748 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/nft-collection/params.fc @@ -0,0 +1,10 @@ +int workchain() asm "0 PUSHINT"; + +() force_chain(slice addr) impure { + (int wc, _) = parse_std_addr(addr); + throw_unless(333, wc == workchain()); +} + +slice null_addr() asm "b{00} PUSHSLICE"; +int flag::regular() asm "0x10 PUSHINT"; +int flag::bounce() asm "0x8 PUSHINT"; \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/nft-collection/stdlib.fc b/crypto/func/auto-tests/legacy_tests/nft-collection/stdlib.fc new file mode 100644 index 00000000..ba9177e9 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/nft-collection/stdlib.fc @@ -0,0 +1,215 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; + builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; + +int equal_slices (slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; +builder store_builder(builder to, builder from) asm "STBR"; \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/nominator-pool/pool.fc b/crypto/func/auto-tests/legacy_tests/nominator-pool/pool.fc new file mode 100644 index 00000000..25d6cb38 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/nominator-pool/pool.fc @@ -0,0 +1,746 @@ +;; The validator has his own wallet in the masterchain, on which he holds his own coins for operating. +;; From this wallet he sends commands to this nominator pool (mostly `new_stake`, `update_validator_set` and `recover_stake`). +;; Register/vote_for complaints and register/vote_for config proposals are sent from validator's wallet. +;; +;; Pool contract must be in masterchain. +;; Nominators' wallets must be in the basechain. +;; The validator in most cases have two pools (for even and odd validation rounds). + +#include "stdlib.fc"; + +int op::new_stake() asm "0x4e73744b PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L621 +int op::new_stake_error() asm "0xee6f454c PUSHINT"; ;; return_stake https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L169 +int op::new_stake_ok() asm "0xf374484c PUSHINT"; ;; send_confirmation https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L173 + +int op::recover_stake() asm "0x47657424 PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L625 +int op::recover_stake_error() asm "0xfffffffe PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L407 +int op::recover_stake_ok() asm "0xf96f7324 PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L426 + +int ADDR_SIZE() asm "256 PUSHINT"; +int BOUNCEABLE() asm "0x18 PUSHINT"; +int NON_BOUNCEABLE() asm "0x10 PUSHINT"; +int SEND_MODE_PAY_FEE_SEPARATELY() asm "1 PUSHINT"; ;; means that the sender wants to pay transfer fees separately +int SEND_MODE_IGNORE_ERRORS() asm "2 PUSHINT"; ;; means that any errors arising while processing this message during the action phase should be ignored +int SEND_MODE_REMAINING_AMOUNT() asm "64 PUSHINT"; ;; is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message +int ONE_TON() asm "1000000000 PUSHINT"; +int MIN_TONS_FOR_STORAGE() asm "10000000000 PUSHINT"; ;; 10 TON +int DEPOSIT_PROCESSING_FEE() asm "1000000000 PUSHINT"; ;; 1 TON +int MIN_STAKE_TO_SEND() asm "500000000000 PUSHINT"; ;; 500 TON +int VOTES_LIFETIME() asm "2592000 PUSHINT"; ;; 30 days + +int binary_log_ceil(int x) asm "UBITSIZE"; + +;; hex parse same with bridge https://github.com/ton-blockchain/bridge-func/blob/d03dbdbe9236e01efe7f5d344831bf770ac4c613/func/text_utils.fc +(slice, int) ~load_hex_symbol(slice comment) { + int n = comment~load_uint(8); + n = n - 48; + throw_unless(329, n >= 0); + if (n < 10) { + return (comment, (n)); + } + n = n - 7; + throw_unless(329, n >= 0); + if (n < 16) { + return (comment, (n)); + } + n = n - 32; + throw_unless(329, (n >= 0) & (n < 16)); + return (comment, n); +} + +(slice, int) ~load_text_hex_number(slice comment, int byte_length) { + int current_slice_length = comment.slice_bits() / 8; + int result = 0; + int counter = 0; + repeat (2 * byte_length) { + result = result * 16 + comment~load_hex_symbol(); + counter = counter + 1; + if (counter == current_slice_length) { + if (comment.slice_refs() == 1) { + cell _cont = comment~load_ref(); + comment = _cont.begin_parse(); + current_slice_length = comment.slice_bits() / 8; + counter = 0; + } + } + } + return (comment, result); +} + +slice make_address(int wc, int addr) inline_ref { + return begin_cell() + .store_uint(4, 3).store_int(wc, 8).store_uint(addr, ADDR_SIZE()).end_cell().begin_parse(); +} + +;; https://github.com/ton-blockchain/ton/blob/ae5c0720143e231c32c3d2034cfe4e533a16d969/crypto/block/block.tlb#L584 +int is_elector_address(int wc, int addr) inline_ref { + return (wc == -1) & (config_param(1).begin_parse().preload_uint(ADDR_SIZE()) == addr); +} + +slice elector_address() inline_ref { + int elector = config_param(1).begin_parse().preload_uint(ADDR_SIZE()); + return make_address(-1, elector); +} + +;; https://github.com/ton-blockchain/ton/blob/ae5c0720143e231c32c3d2034cfe4e533a16d969/crypto/block/block.tlb#L721 +int max_recommended_punishment_for_validator_misbehaviour(int stake) inline_ref { + cell cp = config_param(40); + if (cell_null?(cp)) { + return 101000000000; ;; 101 TON - https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/lite-client/lite-client.cpp#L3678 + } + + slice cs = cp.begin_parse(); + + (int prefix, + int default_flat_fine, int default_proportional_fine, + int severity_flat_mult, int severity_proportional_mult, + int unpunishable_interval, + int long_interval, int long_flat_mult, int long_proportional_mult) = + (cs~load_uint(8), + cs~load_coins(), cs~load_uint(32), + cs~load_uint(16), cs~load_uint(16), + cs~load_uint(16), + cs~load_uint(16), cs~load_uint(16), cs~load_uint(16) + ); + + ;; https://github.com/ton-blockchain/ton/blob/master/lite-client/lite-client.cpp#L3721 + int fine = default_flat_fine; + int fine_part = default_proportional_fine; + + fine *= severity_flat_mult; fine >>= 8; + fine_part *= severity_proportional_mult; fine_part >>= 8; + + fine *= long_flat_mult; fine >>= 8; + fine_part *= long_proportional_mult; fine_part >>= 8; + + return min(stake, fine + muldiv(stake, fine_part, 1 << 32)); ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L529 +} + +;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/block/block.tlb#L632 +;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L118 +int get_validator_config() inline_ref { + slice cs = config_param(15).begin_parse(); + (int validators_elected_for, int elections_start_before, int elections_end_before, int stake_held_for) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32), cs.preload_uint(32)); + return stake_held_for; +} + +;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/block/block.tlb#L712 +(int, int, cell) get_current_validator_set() inline_ref { + cell vset = config_param(34); ;; current validator set + slice cs = vset.begin_parse(); + ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/block/block.tlb#L579 + ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/config-code.fc#L49 + throw_unless(9, cs~load_uint(8) == 0x12); ;; validators_ext#12 only + int utime_since = cs~load_uint(32); ;; actual start unixtime of current validation round + int utime_until = cs~load_uint(32); ;; supposed end unixtime of current validation round (utime_until = utime_since + validators_elected_for); unfreeze_at = utime_until + stake_held_for + return (utime_since, utime_until, vset); +} + +;; check the validity of the new_stake message +;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L208 +int check_new_stake_msg(slice cs) impure inline_ref { + var validator_pubkey = cs~load_uint(256); + var stake_at = cs~load_uint(32); + var max_factor = cs~load_uint(32); + var adnl_addr = cs~load_uint(256); + var signature = cs~load_ref().begin_parse().preload_bits(512); + cs.end_parse(); + return stake_at; ;; supposed start of next validation round (utime_since) +} + +builder pack_nominator(int amount, int pending_deposit_amount) inline_ref { + return begin_cell().store_coins(amount).store_coins(pending_deposit_amount); +} + +(int, int) unpack_nominator(slice ds) inline_ref { + return ( + ds~load_coins(), ;; amount + ds~load_coins() ;; pending_deposit_amount + ); +} + +cell pack_config(int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake) inline_ref { + return begin_cell() + .store_uint(validator_address, ADDR_SIZE()) + .store_uint(validator_reward_share, 16) + .store_uint(max_nominators_count, 16) + .store_coins(min_validator_stake) + .store_coins(min_nominator_stake) + .end_cell(); +} + +(int, int, int, int, int) unpack_config(slice ds) inline_ref { + return ( + ds~load_uint(ADDR_SIZE()), ;; validator_address + ds~load_uint(16), ;; validator_reward_share + ds~load_uint(16), ;; max_nominators_count + ds~load_coins(), ;; min_validator_stake + ds~load_coins() ;; min_nominator_stake + ); +} + +() save_data(int state, int nominators_count, int stake_amount_sent, int validator_amount, cell config, cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) impure inline_ref { + set_data(begin_cell() + .store_uint(state, 8) + .store_uint(nominators_count, 16) + .store_coins(stake_amount_sent) + .store_coins(validator_amount) + .store_ref(config) + .store_dict(nominators) + .store_dict(withdraw_requests) + .store_uint(stake_at, 32) + .store_uint(saved_validator_set_hash, 256) + .store_uint(validator_set_changes_count, 8) + .store_uint(validator_set_change_time, 32) + .store_uint(stake_held_for, 32) + .store_dict(config_proposal_votings) + .end_cell()); +} + +(int, int, int, int, (int, int, int, int, int), cell, cell, int, int, int, int, int, cell) load_data() inline_ref { + slice ds = get_data().begin_parse(); + return ( + ds~load_uint(8), ;; state + ds~load_uint(16), ;; nominators_count + ds~load_coins(), ;; stake_amount_sent + ds~load_coins(), ;; validator_amount + unpack_config(ds~load_ref().begin_parse()), ;; config + ds~load_dict(), ;; nominators + ds~load_dict(), ;; withdraw_requests + ds~load_uint(32), ;; stake_at + ds~load_uint(256), ;; saved_validator_set_hash + ds~load_uint(8), ;; validator_set_changes_count + ds~load_uint(32), ;; validator_set_change_time + ds~load_uint(32), ;; stake_held_for + ds~load_dict() ;; config_proposal_votings + ); +} + +() send_msg(slice to_address, int amount, cell payload, int flags, int send_mode) impure inline_ref { + int has_payload = ~ cell_null?(payload); + + builder msg = begin_cell() + .store_uint(flags, 6) + .store_slice(to_address) + .store_coins(amount) + .store_uint(has_payload ? 1 : 0, 1 + 4 + 4 + 64 + 32 + 1 + 1); + + if (has_payload) { + msg = msg.store_ref(payload); + } + + send_raw_message(msg.end_cell(), send_mode); +} + +() send_excesses(slice sender_address) impure inline_ref { + send_msg(sender_address, 0, null(), NON_BOUNCEABLE(), SEND_MODE_REMAINING_AMOUNT() + SEND_MODE_IGNORE_ERRORS()); ;; non-bouneable, remaining inbound message amount, fee deducted from amount, ignore errors +} + +(cell, cell, int, int) withdraw_nominator(int address, cell nominators, cell withdraw_requests, int balance, int nominators_count) impure inline_ref { + (slice nominator, int found) = nominators.udict_get?(ADDR_SIZE(), address); + throw_unless(60, found); + (int amount, int pending_deposit_amount) = unpack_nominator(nominator); + int withdraw_amount = amount + pending_deposit_amount; + + if (withdraw_amount > balance - MIN_TONS_FOR_STORAGE()) { + return (nominators, withdraw_requests, balance, nominators_count); + } + + nominators~udict_delete?(ADDR_SIZE(), address); + withdraw_requests~udict_delete?(ADDR_SIZE(), address); + nominators_count -= 1; + balance -= withdraw_amount; + + if (withdraw_amount >= ONE_TON()) { + send_msg(make_address(0, address), withdraw_amount, null(), NON_BOUNCEABLE(), 0); ;; non-bouneable, fee deducted from amount, revert on errors + } + return (nominators, withdraw_requests, balance, nominators_count); +} + +(cell, cell, int, int) process_withdraw_requests(cell nominators, cell withdraw_requests, int balance, int nominators_count, int limit) impure inline_ref { + int count = 0; + int address = -1; + int need_break = 0; + do { + (address, slice cs, int f) = withdraw_requests.udict_get_next?(ADDR_SIZE(), address); + if (f) { + (nominators, withdraw_requests, int new_balance, nominators_count) = withdraw_nominator(address, nominators, withdraw_requests, balance, nominators_count); + need_break = (new_balance == balance); + balance = new_balance; + count += 1; + if (count >= limit) { + need_break = -1; + } + } + } until ((~ f) | (need_break)); + + return (nominators, withdraw_requests, nominators_count, balance); +} + +int calculate_total_nominators_amount(cell nominators) inline_ref { + int total = 0; + int address = -1; + do { + (address, slice cs, int f) = nominators.udict_get_next?(ADDR_SIZE(), address); + if (f) { + (int amount, int pending_deposit_amount) = unpack_nominator(cs); + total += (amount + pending_deposit_amount); + } + } until (~ f); + return total; +} + +cell distribute_share(int reward, cell nominators) inline_ref { + int total_amount = 0; + int address = -1; + do { + (address, slice cs, int f) = nominators.udict_get_next?(ADDR_SIZE(), address); + if (f) { + (int amount, int pending_deposit_amount) = unpack_nominator(cs); + total_amount += amount; + } + } until (~ f); + + cell new_nominators = new_dict(); + address = -1; + do { + (address, slice cs, int f) = nominators.udict_get_next?(ADDR_SIZE(), address); + if (f) { + (int amount, int pending_deposit_amount) = unpack_nominator(cs); + if (total_amount > 0) { + amount += muldiv(reward, amount, total_amount); + if (amount < 0) { + amount = 0; + } + } + amount += pending_deposit_amount; + new_nominators~udict_set_builder(ADDR_SIZE(), address, pack_nominator(amount, 0)); + } + } until (~ f); + + return new_nominators; +} + +() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { + int balance = pair_first(get_balance()); + + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + slice sender_address = cs~load_msg_addr(); + (int sender_wc, int sender_addr) = parse_std_addr(sender_address); + + if (flags & 1) { ;; bounced messages + if (in_msg_body.slice_bits() >= 64) { + in_msg_body~skip_bits(32); ;; skip 0xFFFFFFFF bounced prefix + int op = in_msg_body~load_uint(32); + if ((op == op::new_stake()) & (is_elector_address(sender_wc, sender_addr))) { + ;; `new_stake` from nominator-pool should always be handled without throws by elector + ;; because nominator-pool do `check_new_stake_msg` and `msg_value` checks before sending `new_stake`. + ;; If the stake is not accepted elector will send `new_stake_error` response message. + ;; Nevertheless we do process theoretically possible bounced `new_stake`. + + (int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data(); + if (state == 1) { + state = 0; + } + save_data( + state, + nominators_count, + stake_amount_sent, + validator_amount, + pack_config(validator_address, validator_reward_share, max_nominators_count, min_validator_stake, min_nominator_stake), + nominators, + withdraw_requests, + stake_at, + saved_validator_set_hash, + validator_set_changes_count, + validator_set_change_time, + stake_held_for, + config_proposal_votings + ); + } + } + return (); ;; ignore other bounces messages + } + + int op = in_msg_body~load_uint(32); + + (int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data(); + + if (op == 0) { + ;; We use simple text comments for nominator operations so nominators can do it from any wallet app. + ;; In other cases, they will need to put a stake on a browser extension, or use scripts, which can be inconvenient. + + ;; Throw on any unexpected request so that the stake is bounced back to the nominator in case of a typo. + + int action = in_msg_body~load_uint(8); + int is_vote = (action == 121) | (action == 110); ;; "y" or "n" + throw_unless(64, (action == 100) | (action == 119) | is_vote); ;; "d" or "w" or "y" or "n" + + if (~ is_vote) { + in_msg_body.end_parse(); + throw_unless(61, sender_wc == 0); ;; nominators only in basechain + throw_unless(62, sender_addr != validator_address); + } + + if (action == 100) { ;; "d" - deposit nominator (any time, will take effect in the next round) + (slice nominator, int found) = nominators.udict_get?(ADDR_SIZE(), sender_addr); + + if (~ found) { + nominators_count += 1; + } + throw_unless(65, nominators_count <= max_nominators_count); + + msg_value -= DEPOSIT_PROCESSING_FEE(); + throw_unless(66, msg_value > 0); + + (int amount, int pending_deposit_amount) = found ? unpack_nominator(nominator) : (0, 0); + if (state == 0) { + amount += msg_value; + } else { + pending_deposit_amount += msg_value; + } + throw_unless(67, amount + pending_deposit_amount >= min_nominator_stake); + throw_unless(68, cell_depth(nominators) < max(5, binary_log_ceil(nominators_count) * 2) ); ;; prevent dict depth ddos + nominators~udict_set_builder(ADDR_SIZE(), sender_addr, pack_nominator(amount, pending_deposit_amount)); + } + + if (action == 119) { ;; "w" - withdraw request (any time) + if (state == 0) { + (nominators, withdraw_requests, int new_balance, nominators_count) = withdraw_nominator(sender_addr, nominators, withdraw_requests, balance, nominators_count); + if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) { + send_excesses(sender_address); + } + } else { + (slice nominator, int found) = nominators.udict_get?(ADDR_SIZE(), sender_addr); + throw_unless(69, found); + withdraw_requests~udict_set_builder(ADDR_SIZE(), sender_addr, begin_cell()); + send_excesses(sender_address); + } + } + + if (is_vote) { + int authorized = (sender_wc == -1) & (sender_addr == validator_address); + + if (~ authorized) { + throw_unless(121, sender_wc == 0); + (slice nominator, authorized) = nominators.udict_get?(ADDR_SIZE(), sender_addr); + throw_unless(122, authorized); + (int amount, int pending_deposit_amount) = unpack_nominator(nominator); + throw_unless(123, amount > 0); + } + + int proposal_hash = in_msg_body~load_text_hex_number(32); + in_msg_body.end_parse(); + int support = action == 121; + + (slice votes_slice, int found) = config_proposal_votings.udict_get?(256, proposal_hash); + + if (~ found) { + ;; require higher fee to prevent dictionary spam + int fee = ONE_TON(); + int power = cell_depth(config_proposal_votings); + repeat (power) { + fee = muldiv(fee, 15, 10); + } + throw_unless(123, msg_value >= fee); + } + + (cell votes_dict, int votes_create_time) = found ? (votes_slice~load_dict(), votes_slice~load_uint(32)) : (new_dict(), now()); + + (_, int vote_found) = votes_dict.udict_get?(256, sender_addr); + throw_if(124, vote_found); + votes_dict~udict_set_builder(256, sender_addr, begin_cell().store_int(support, 1).store_uint(now(), 32)); + + builder new_votes = begin_cell().store_dict(votes_dict).store_uint(votes_create_time, 32); + config_proposal_votings~udict_set_builder(256, proposal_hash, new_votes); + + if (found) { + send_excesses(sender_address); + } + } + + } else { + + int query_id = in_msg_body~load_uint(64); + + if (is_elector_address(sender_wc, sender_addr)) { ;; response from elector + + accept_message(); + + if (op == op::recover_stake_ok()) { + state = 0; + + int reward = msg_value - stake_amount_sent; + int nominators_reward = 0; + + if (reward <= 0) { + validator_amount += reward; + if (validator_amount < 0) { + ;; even this should never happen + nominators_reward = validator_amount; + validator_amount = 0; + } + } else { + int validator_reward = (reward * validator_reward_share) / 10000; + if (validator_reward > reward) { ;; Theoretical invalid case if validator_reward_share > 10000 + validator_reward = reward; + } + validator_amount += validator_reward; + nominators_reward = reward - validator_reward; + } + + nominators = distribute_share(nominators_reward, nominators); ;; call even if there was no reward to process deposit requests + stake_amount_sent = 0; + } + + if (state == 1) { + if (op == op::new_stake_error()) { ;; error when new_stake; stake returned + state = 0; + } + + if (op == op::new_stake_ok()) { + state = 2; + } + } + + ;; else just accept coins from elector + + } else { + + ;; throw on any unexpected request so that the coins is bounced back to the sender in case of a typo + throw_unless(70, ((op >= 1) & (op <= 7)) | (op == op::recover_stake()) | (op == op::new_stake())); + + if (op == 1) { + ;; just accept coins + } + + if (op == 2) { ;; process withdraw requests (at any time while the balance is enough) + int limit = in_msg_body~load_uint(8); + + (nominators, withdraw_requests, nominators_count, int new_balance) = process_withdraw_requests(nominators, withdraw_requests, balance, nominators_count, limit); + + if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) { + send_excesses(sender_address); + } + } + + if (op == 3) { ;; emergency process withdraw request (at any time if the balance is enough) + int request_address = in_msg_body~load_uint(ADDR_SIZE()); + (slice withdraw_request, int found) = withdraw_requests.udict_get?(ADDR_SIZE(), request_address); + throw_unless(71, found); + (nominators, withdraw_requests, int new_balance, nominators_count) = withdraw_nominator(request_address, nominators, withdraw_requests, balance, nominators_count); + if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) { + send_excesses(sender_address); + } + } + + if (op == 6) { ;; update current valudator set hash (anyone can invoke) + throw_unless(113, validator_set_changes_count < 3); + (int utime_since, int utime_until, cell vset) = get_current_validator_set(); + int current_hash = cell_hash(vset); + if (saved_validator_set_hash != current_hash) { + saved_validator_set_hash = current_hash; + validator_set_changes_count += 1; + validator_set_change_time = now(); + } + send_excesses(sender_address); + } + + if (op == 7) { ;; clean up outdating votings + int t = now(); + int proposal_hash = -1; + do { + (proposal_hash, slice votes_slice, int found) = config_proposal_votings.udict_get_next?(256, proposal_hash); + if (found) { + (cell votes_dict, int votes_create_time) = (votes_slice~load_dict(), votes_slice~load_uint(32)); + if (t - votes_create_time > VOTES_LIFETIME()) { + config_proposal_votings~udict_delete?(256, proposal_hash); + } + } + } until (~ found); + send_excesses(sender_address); + } + + if (op == op::recover_stake()) { ;; send recover_stake to elector (anyone can send) + + ;; We need to take all credits from the elector at once, + ;; because if we do not take all at once, then it will be processed as a fine by pool. + ;; In the elector, credits (`credit_to`) are accrued in three places: + ;; 1) return of surplus stake in elections (`try_elect`) + ;; 2) reward for complaint when punish (`punish`) - before unfreezing + ;; 3) unfreeze round (`unfreeze_without_bonuses`/`unfreeze_with_bonuses`) + ;; We need to be guaranteed to wait for unfreezing round and only then send `recover_stake`. + ;; So we are waiting for the change of 3 validator sets. + + ;; ADDITIONAL NOTE: + ;; In a special case (if the network was down), the config theoretically can refuse the elector to save a new round after election - https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/config-code.fc#L494 + ;; and the elector will start a new election - https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L364 + ;; in this case, our pool will have to skip the round, but it will be able to recover stake later + + throw_unless(111, validator_set_changes_count >= 2); + throw_unless(112, (validator_set_changes_count > 2) | (now() - validator_set_change_time > stake_held_for + 60)); + ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L887 + + cell payload = begin_cell().store_uint(op::recover_stake(), 32).store_uint(query_id, 64).end_cell(); + send_msg(elector_address(), 0, payload, BOUNCEABLE(), SEND_MODE_REMAINING_AMOUNT()); ;; bounceable, carry all the remaining value of the inbound message, fee deducted from amount, revert on errors + } + + ;; message from validator + + if (op == 4) { ;; deposit validator (any time) + throw_unless(73, (sender_wc == -1) & (sender_addr == validator_address)); + msg_value -= DEPOSIT_PROCESSING_FEE(); + throw_unless(74, msg_value > 0); + validator_amount += msg_value; + } + + if (op == 5) { ;; withdraw validator (after recover_stake and before new_stake) + throw_unless(74, state == 0); ;; no withdraw request because validator software can wait right time + throw_unless(75, (sender_wc == -1) & (sender_addr == validator_address)); + int request_amount = in_msg_body~load_coins(); + throw_unless(78, request_amount > 0); + + int total_nominators_amount = calculate_total_nominators_amount(nominators); + ;; the validator can withdraw everything that does not belong to the nominators + throw_unless(76, request_amount <= balance - MIN_TONS_FOR_STORAGE() - total_nominators_amount); + validator_amount -= request_amount; + if (validator_amount < 0) { + validator_amount = 0; + } + send_msg(make_address(-1, validator_address), request_amount, null(), NON_BOUNCEABLE(), 0); ;; non-bouneable, fee deducted from amount, revert on errors + int new_balance = balance - request_amount; + if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) { + send_excesses(sender_address); + } + } + + if (op == op::new_stake()) { + throw_unless(78, (sender_wc == -1) & (sender_addr == validator_address)); + + throw_unless(79, state == 0); + + throw_unless(80, query_id); ;; query_id must be greater then 0 to receive confirmation message from elector + + throw_unless(86, msg_value >= ONE_TON()); ;; must be greater then new_stake sending to elector fee + + int value = in_msg_body~load_coins(); + + slice msg = in_msg_body; + + stake_at = check_new_stake_msg(in_msg_body); + + stake_amount_sent = value - ONE_TON(); + + throw_unless(81, value >= MIN_STAKE_TO_SEND()); + + throw_unless(82, value <= balance - MIN_TONS_FOR_STORAGE()); + + throw_unless(83, validator_amount >= min_validator_stake); + + throw_unless(84, validator_amount >= max_recommended_punishment_for_validator_misbehaviour(stake_amount_sent)); + + throw_unless(85, cell_null?(withdraw_requests)); ;; no withdraw requests + + state = 1; + (int utime_since, int utime_until, cell vset) = get_current_validator_set(); + saved_validator_set_hash = cell_hash(vset); ;; current validator set, we will be in next validator set + validator_set_changes_count = 0; + validator_set_change_time = utime_since; + stake_held_for = get_validator_config(); ;; save `stake_held_for` in case the config changes in the process + + send_msg(elector_address(), value, begin_cell().store_uint(op, 32).store_uint(query_id, 64).store_slice(msg).end_cell(), BOUNCEABLE(), SEND_MODE_PAY_FEE_SEPARATELY()); ;; pay fee separately, rever on errors + } + } + } + + save_data( + state, + nominators_count, + stake_amount_sent, + validator_amount, + pack_config(validator_address, validator_reward_share, max_nominators_count, min_validator_stake, min_nominator_stake), + nominators, + withdraw_requests, + stake_at, + saved_validator_set_hash, + validator_set_changes_count, + validator_set_change_time, + stake_held_for, + config_proposal_votings + ); +} + +;; Get methods + +_ get_pool_data() method_id { + return load_data(); +} + +int has_withdraw_requests() method_id { + (int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data(); + return ~ cell_null?(withdraw_requests); +} + +(int, int, int) get_nominator_data(int nominator_address) method_id { + (int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data(); + + (slice nominator, int found) = nominators.udict_get?(ADDR_SIZE(), nominator_address); + throw_unless(86, found); + (int amount, int pending_deposit_amount) = unpack_nominator(nominator); + (slice withdraw_request, int withdraw_found) = withdraw_requests.udict_get?(ADDR_SIZE(), nominator_address); + + return (amount, pending_deposit_amount, withdraw_found); +} + +int get_max_punishment(int stake) method_id { + return max_recommended_punishment_for_validator_misbehaviour(stake); +} + +tuple list_nominators() method_id { + (int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data(); + var list = null(); + int address = -1; + do { + (address, slice nominator, int found) = nominators.udict_get_next?(ADDR_SIZE(), address); + if (found) { + (int amount, int pending_deposit_amount) = unpack_nominator(nominator); + (_, int withdraw_requested) = withdraw_requests.udict_get?(ADDR_SIZE(), address); + list = cons(tuple4(address, amount, pending_deposit_amount, withdraw_requested), list); + } + } until (~ found); + return list; +} + +tuple list_votes() method_id { + (int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data(); + var list = null(); + int proposal_hash = -1; + do { + (proposal_hash, slice votes_slice, int found) = config_proposal_votings.udict_get_next?(256, proposal_hash); + if (found) { + (cell votes_dict, int votes_create_time) = (votes_slice~load_dict(), votes_slice~load_uint(32)); + list = cons(pair(proposal_hash, votes_create_time), list); + } + } until (~ found); + return list; +} + +tuple list_voters(int proposal_hash) method_id { + (int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data(); + var list = null(); + (slice votes_slice, int found) = config_proposal_votings.udict_get?(256, proposal_hash); + throw_unless(133, found); + cell votes_dict = votes_slice~load_dict(); + + int address = -1; + do { + (address, slice cs, int found) = votes_dict.udict_get_next?(ADDR_SIZE(), address); + if (found) { + (int support, int vote_time) = (cs~load_int(1), cs~load_uint(32)); + list = cons(triple(address, support, vote_time), list); + } + } until (~ found); + return list; +} diff --git a/crypto/func/auto-tests/legacy_tests/nominator-pool/stdlib.fc b/crypto/func/auto-tests/legacy_tests/nominator-pool/stdlib.fc new file mode 100644 index 00000000..0431d32d --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/nominator-pool/stdlib.fc @@ -0,0 +1,211 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/storage/constants.fc b/crypto/func/auto-tests/legacy_tests/storage/constants.fc new file mode 100644 index 00000000..479cdfa3 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/storage/constants.fc @@ -0,0 +1,23 @@ +const op::offer_storage_contract = 0x107c49ef; +const op::close_contract = 0x79f937ea; +const op::contract_deployed = 0xbf7bd0c1; +const op::storage_contract_confirmed = 0xd4caedcd; +const op::reward_withdrawal = 0xa91baf56; +const op::storage_contract_terminated = 0xb6236d63; +const op::accept_storage_contract = 0x7a361688; +const op::withdraw = 0x46ed2e94; +const op::proof_storage = 0x419d5d4d; + +const op::update_pubkey = 0x53f34cd6; +const op::update_storage_params = 0x54cbf19b; + +const error::not_enough_money = 1001; +const error::unauthorized = 401; +const error::wrong_proof = 1002; +const error::contract_not_active = 1003; +const error::file_too_small = 1004; +const error::file_too_big = 1005; +const error::no_new_contracts = 1006; +const error::contract_already_active = 1007; +const error::no_microchunk_hash = 1008; +const error::provider_params_changed = 1009; diff --git a/crypto/func/auto-tests/legacy_tests/storage/stdlib.fc b/crypto/func/auto-tests/legacy_tests/storage/stdlib.fc new file mode 100644 index 00000000..781fdcbc --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/storage/stdlib.fc @@ -0,0 +1,625 @@ +;; Standard library for funC +;; + +{- + # Tuple manipulation primitives + The names and the types are mostly self-explaining. + See [polymorhism with forall](https://ton.org/docs/#/func/functions?id=polymorphism-with-forall) + for more info on the polymorphic functions. + + Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) + and vise versa. +-} + +{- + # Lisp-style lists + + Lists can be represented as nested 2-elements tuples. + Empty list is conventionally represented as TVM `null` value (it can be obtained by calling [null()]). + For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types. +-} + +;;; Adds an element to the beginning of lisp-style list. +forall X -> tuple cons(X head, tuple tail) asm "CONS"; + +;;; Extracts the head and the tail of lisp-style list. +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; + +;;; Extracts the tail and the head of lisp-style list. +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; + +;;; Returns the head of lisp-style list. +forall X -> X car(tuple list) asm "CAR"; + +;;; Returns the tail of lisp-style list. +tuple cdr(tuple list) asm "CDR"; + +;;; Creates tuple with zero elements. +tuple empty_tuple() asm "NIL"; + +;;; Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` +;;; is of length at most 255. Otherwise throws a type check exception. +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; + +;;; Creates a tuple of length one with given argument as element. +forall X -> [X] single(X x) asm "SINGLE"; + +;;; Unpacks a tuple of length one +forall X -> X unsingle([X] t) asm "UNSINGLE"; + +;;; Creates a tuple of length two with given arguments as elements. +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; + +;;; Unpacks a tuple of length two +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; + +;;; Creates a tuple of length three with given arguments as elements. +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; + +;;; Unpacks a tuple of length three +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; + +;;; Creates a tuple of length four with given arguments as elements. +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; + +;;; Unpacks a tuple of length four +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; + +;;; Returns the first element of a tuple (with unknown element types). +forall X -> X first(tuple t) asm "FIRST"; + +;;; Returns the second element of a tuple (with unknown element types). +forall X -> X second(tuple t) asm "SECOND"; + +;;; Returns the third element of a tuple (with unknown element types). +forall X -> X third(tuple t) asm "THIRD"; + +;;; Returns the fourth element of a tuple (with unknown element types). +forall X -> X fourth(tuple t) asm "3 INDEX"; + +;;; Returns the first element of a pair tuple. +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; + +;;; Returns the second element of a pair tuple. +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; + +;;; Returns the first element of a triple tuple. +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; + +;;; Returns the second element of a triple tuple. +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; + +;;; Returns the third element of a triple tuple. +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; + + +;;; Push null element (casted to given type) +;;; By the TVM type `Null` FunC represents absence of a value of some atomic type. +;;; So `null` can actually have any atomic type. +forall X -> X null() asm "PUSHNULL"; + +;;; Moves a variable [x] to the top of the stack +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + + + +;;; Returns the current Unix time as an Integer +int now() asm "NOW"; + +;;; Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. +;;; If necessary, it can be parsed further using primitives such as [parse_std_addr]. +slice my_address() asm "MYADDR"; + +;;; Returns the balance of the smart contract as a tuple consisting of an int +;;; (balance in nanotoncoins) and a `cell` +;;; (a dictionary with 32-bit keys representing the balance of "extra currencies") +;;; at the start of Computation Phase. +;;; Note that RAW primitives such as [send_raw_message] do not update this field. +[int, cell] get_balance() asm "BALANCE"; + +;;; Returns the logical time of the current transaction. +int cur_lt() asm "LTIME"; + +;;; Returns the starting logical time of the current block. +int block_lt() asm "BLOCKLT"; + +;;; Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. +;;; Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. +int cell_hash(cell c) asm "HASHCU"; + +;;; Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. +;;; The result is the same as if an ordinary cell containing only data and references from `s` had been created +;;; and its hash computed by [cell_hash]. +int slice_hash(slice s) asm "HASHSU"; + +;;; Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, +;;; throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. +int string_hash(slice s) asm "SHA256U"; + +{- + # Signature checks +-} + +;;; Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) +;;; using [public_key] (also represented by a 256-bit unsigned integer). +;;; The signature must contain at least 512 data bits; only the first 512 bits are used. +;;; The result is `−1` if the signature is valid, `0` otherwise. +;;; Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. +;;; That is, if [hash] is computed as the hash of some data, these data are hashed twice, +;;; the second hashing occurring inside `CHKSIGNS`. +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; + +;;; Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, +;;; similarly to [check_signature]. +;;; If the bit length of [data] is not divisible by eight, throws a cell underflow exception. +;;; The verification of Ed25519 signatures is the standard one, +;;; with sha256 used to reduce [data] to the 256-bit number that is actually signed. +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +{--- + # Computation of boc size + The primitives below may be useful for computing storage fees of user-provided data. +-} + +;;; Returns `(x, y, z, -1)` or `(null, null, null, 0)`. +;;; Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` +;;; in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account +;;; the identification of equal cells. +;;; The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, +;;; with a hash table of visited cell hashes used to prevent visits of already-visited cells. +;;; The total count of visited cells `x` cannot exceed non-negative [max_cells]; +;;; otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and +;;; a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; + +;;; Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. +;;; The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; +;;; however, the data bits and the cell references of [s] are accounted for in `y` and `z`. +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; + +;;; A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (8) on failure. +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; Throws an exception with exit_code excno if cond is not 0 (commented since implemented in compilator) +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +{-- + # Debug primitives + Only works for local TVM execution with debug level verbosity +-} +;;; Dumps the stack (at most the top 255 values) and shows the total stack depth. +() dump_stack() impure asm "DUMPSTK"; + +{- + # Persistent storage save and load +-} + +;;; Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. +cell get_data() asm "c4 PUSH"; + +;;; Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. +() set_data(cell c) impure asm "c4 POP"; + +{- + # Continuation primitives +-} +;;; Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. +;;; The primitive returns the current value of `c3`. +cont get_c3() impure asm "c3 PUSH"; + +;;; Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. +;;; Note that after execution of this primitive the current code +;;; (and the stack of recursive function calls) won't change, +;;; but any other function call will use a function from the new code. +() set_c3(cont c) impure asm "c3 POP"; + +;;; Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. +cont bless(slice s) impure asm "BLESS"; + +{--- + # Gas related primitives +-} + +;;; Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, +;;; decreasing the value of `gr` by `gc` in the process. +;;; In other words, the current smart contract agrees to buy some gas to finish the current transaction. +;;; This action is required to process external messages, which bring no value (hence no gas) with themselves. +;;; +;;; For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). +() accept_message() impure asm "ACCEPT"; + +;;; Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. +;;; If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, +;;; an (unhandled) out of gas exception is thrown before setting new gas limits. +;;; Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; + +;;; Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) +;;; so that the current execution is considered “successful” with the saved values even if an exception +;;; in Computation Phase is thrown later. +() commit() impure asm "COMMIT"; + +;;; Not implemented +;;() buy_gas(int gram) impure asm "BUYGAS"; + +;;; Computes the amount of gas that can be bought for `amount` nanoTONs, +;;; and sets `gl` accordingly in the same way as [set_gas_limit]. +() buy_gas(int amount) impure asm "BUYGAS"; + +;;; Computes the minimum of two integers [x] and [y]. +int min(int x, int y) asm "MIN"; + +;;; Computes the maximum of two integers [x] and [y]. +int max(int x, int y) asm "MAX"; + +;;; Sorts two integers. +(int, int) minmax(int x, int y) asm "MINMAX"; + +;;; Computes the absolute value of an integer [x]. +int abs(int x) asm "ABS"; + +{- + # Slice primitives + + It is said that a primitive _loads_ some data, + if it returns the data and the remainder of the slice + (so it can also be used as [modifying method](https://ton.org/docs/#/func/statements?id=modifying-methods)). + + It is said that a primitive _preloads_ some data, if it returns only the data + (it can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods)). + + Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice. +-} + + +;;; Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, +;;; or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) +;;; which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. +slice begin_parse(cell c) asm "CTOS"; + +;;; Checks if [s] is empty. If not, throws an exception. +() end_parse(slice s) impure asm "ENDS"; + +;;; Loads the first reference from the slice. +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; + +;;; Preloads the first reference from the slice. +cell preload_ref(slice s) asm "PLDREF"; + + {- Functions below are commented because are implemented on compilator level for optimisation -} + +;;; Loads a signed [len]-bit integer from a slice [s]. +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; + +;;; Loads an unsigned [len]-bit integer from a slice [s]. +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; + +;;; Preloads a signed [len]-bit integer from a slice [s]. +;; int preload_int(slice s, int len) asm "PLDIX"; + +;;; Preloads an unsigned [len]-bit integer from a slice [s]. +;; int preload_uint(slice s, int len) asm "PLDUX"; + +;;; Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; + +;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; + +;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^128 - 1`). +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; + +;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; + +;;; Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice first_bits(slice s, int len) asm "SDCUTFIRST"; + +;;; Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; + +;;; Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice slice_last(slice s, int len) asm "SDCUTLAST"; + +;;; Loads a dictionary `D` (HashMapE) from `slice` [s]. +;;; (returns `null` if `nothing` constructor is used). +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; + +;;; Preloads a dictionary `D` from `slice` [s]. +cell preload_dict(slice s) asm "PLDDICT"; + +;;; Loads a dictionary as [load_dict], but returns only the remainder of the slice. +slice skip_dict(slice s) asm "SKIPDICT"; + +;;; Loads (Maybe ^Cell) from `slice` [s]. +;;; In other words loads 1 bit and if it is true +;;; loads first ref and return it with slice remainder +;;; otherwise returns `null` and slice remainder +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; + +;;; Preloads (Maybe ^Cell) from `slice` [s]. +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; + + +;;; Returns the depth of `cell` [c]. +;;; If [c] has no references, then return `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. +;;; If [c] is a `null` instead of a cell, returns zero. +int cell_depth(cell c) asm "CDEPTH"; + + +{- + # Slice size primitives +-} + +;;; Returns the number of references in `slice` [s]. +int slice_refs(slice s) asm "SREFS"; + +;;; Returns the number of data bits in `slice` [s]. +int slice_bits(slice s) asm "SBITS"; + +;;; Returns both the number of data bits and the number of references in `slice` [s]. +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; + +;;; Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). +int slice_empty?(slice s) asm "SEMPTY"; + +;;; Checks whether `slice` [s] has no bits of data. +int slice_data_empty?(slice s) asm "SDEMPTY"; + +;;; Checks whether `slice` [s] has no references. +int slice_refs_empty?(slice s) asm "SREMPTY"; + +;;; Returns the depth of `slice` [s]. +;;; If [s] has no references, then returns `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. +int slice_depth(slice s) asm "SDEPTH"; + +{- + # Builder size primitives +-} + +;;; Returns the number of cell references already stored in `builder` [b] +int builder_refs(builder b) asm "BREFS"; + +;;; Returns the number of data bits already stored in `builder` [b]. +int builder_bits(builder b) asm "BBITS"; + +;;; Returns the depth of `builder` [b]. +;;; If no cell references are stored in [b], then returns 0; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. +int builder_depth(builder b) asm "BDEPTH"; + +{- + # Builder primitives + It is said that a primitive _stores_ a value `x` into a builder `b` + if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. + It can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods). + + All the primitives below first check whether there is enough space in the `builder`, + and only then check the range of the value being serialized. +-} + +;;; Creates a new empty `builder`. +builder begin_cell() asm "NEWC"; + +;;; Converts a `builder` into an ordinary `cell`. +cell end_cell(builder b) asm "ENDC"; + +;;; Stores a reference to `cell` [c] into `builder` [b]. +builder store_ref(builder b, cell c) asm(c b) "STREF"; + +;;; Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; + +;;; Stores a signed [len]-bit integer `x` into `b` for` 0 ≤ len ≤ 257`. +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; + + +;;; Stores `slice` [s] into `builder` [b] +builder store_slice(builder b, slice s) asm "STSLICER"; + +;;; Stores (serializes) an integer [x] in the range `0..2^128 − 1` into `builder` [b]. +;;; The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, +;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, +;;; followed by an `8l`-bit unsigned big-endian representation of [x]. +;;; If [x] does not belong to the supported range, a range check exception is thrown. +;;; +;;; Store amounts of TonCoins to the builder as VarUInteger 16 +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_coins(builder b, int x) asm "STGRAMS"; + +;;; Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. +;;; In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +;;; Stores (Maybe ^Cell) to builder: +;;; if cell is null store 1 zero bit +;;; otherwise store 1 true bit and ref to cell +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + + +{- + # Address manipulation primitives + The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: + ```TL-B + addr_none$00 = MsgAddressExt; + addr_extern$01 len:(## 8) external_address:(bits len) + = MsgAddressExt; + anycast_info$_ depth:(#<= 30) { depth >= 1 } + rewrite_pfx:(bits depth) = Anycast; + addr_std$10 anycast:(Maybe Anycast) + workchain_id:int8 address:bits256 = MsgAddressInt; + addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) + workchain_id:int32 address:(bits addr_len) = MsgAddressInt; + _ _:MsgAddressInt = MsgAddress; + _ _:MsgAddressExt = MsgAddress; + + int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddress dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ``` + A deserialized `MsgAddress` is represented by a tuple `t` as follows: + + - `addr_none` is represented by `t = (0)`, + i.e., a tuple containing exactly one integer equal to zero. + - `addr_extern` is represented by `t = (1, s)`, + where slice `s` contains the field `external_address`. In other words, ` + t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`. + - `addr_std` is represented by `t = (2, u, x, s)`, + where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present). + Next, integer `x` is the `workchain_id`, and slice `s` contains the address. + - `addr_var` is represented by `t = (3, u, x, s)`, + where `u`, `x`, and `s` have the same meaning as for `addr_std`. +-} + +;;; Loads from slice [s] the only prefix that is a valid `MsgAddress`, +;;; and returns both this prefix `s'` and the remainder `s''` of [s] as slices. +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; + +;;; Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. +;;; If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. +tuple parse_addr(slice s) asm "PARSEMSGADDR"; + +;;; Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), +;;; applies rewriting from the anycast (if present) to the same-length prefix of the address, +;;; and returns both the workchain and the 256-bit address as integers. +;;; If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, +;;; throws a cell deserialization exception. +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; + +;;; A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s], +;;; even if it is not exactly 256 bit long (represented by a `msg_addr_var`). +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +{- + # Dictionary primitives +-} + + +;;; Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; + +;;; Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; + +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; + +;;; Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL +cell new_dict() asm "NEWDICT"; +;;; Checks whether a dictionary is empty. Equivalent to cell_null?. +int dict_empty?(cell c) asm "DICTEMPTY"; + + +{- Prefix dictionary primitives -} +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +;;; Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. +cell config_param(int x) asm "CONFIGOPTPARAM"; +;;; Checks whether c is a null. Note, that FunC also has polymorphic null? built-in. +int cell_null?(cell c) asm "ISNULL"; + +;;; Creates an output action which would reserve exactly amount nanotoncoins (if mode = 0), at most amount nanotoncoins (if mode = 2), or all but amount nanotoncoins (if mode = 1 or mode = 3), from the remaining balance of the account. It is roughly equivalent to creating an outbound message carrying amount nanotoncoins (or b − amount nanotoncoins, where b is the remaining balance) to oneself, so that the subsequent output actions would not be able to spend more money than the remainder. Bit +2 in mode means that the external action does not fail if the specified amount cannot be reserved; instead, all remaining balance is reserved. Bit +8 in mode means `amount <- -amount` before performing any further actions. Bit +4 in mode means that amount is increased by the original balance of the current account (before the compute phase), including all extra currencies, before performing any other checks and actions. Currently, amount must be a non-negative integer, and mode must be in the range 0..15. +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +;;; Similar to raw_reserve, but also accepts a dictionary extra_amount (represented by a cell or null) with extra currencies. In this way currencies other than TonCoin can be reserved. +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +;;; Sends a raw message contained in msg, which should contain a correctly serialized object Message X, with the only exception that the source address is allowed to have dummy value addr_none (to be automatically replaced with the current smart contract address), and ihr_fee, fwd_fee, created_lt and created_at fields can have arbitrary values (to be rewritten with correct values during the action phase of the current transaction). Integer parameter mode contains the flags. Currently mode = 0 is used for ordinary messages; mode = 128 is used for messages that are to carry all the remaining balance of the current smart contract (instead of the value originally indicated in the message); mode = 64 is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message (if bit 0 is not set, the gas fees are deducted from this amount); mode' = mode + 1 means that the sender wants to pay transfer fees separately; mode' = mode + 2 means that any errors arising while processing this message during the action phase should be ignored. Finally, mode' = mode + 32 means that the current account must be destroyed if its resulting balance is zero. This flag is usually employed together with +128. +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +;;; Creates an output action that would change this smart contract code to that given by cell new_code. Notice that this change will take effect only after the successful termination of the current run of the smart contract +() set_code(cell new_code) impure asm "SETCODE"; + +;;; Generates a new pseudo-random unsigned 256-bit integer x. The algorithm is as follows: if r is the old value of the random seed, considered as a 32-byte array (by constructing the big-endian representation of an unsigned 256-bit integer), then its sha512(r) is computed; the first 32 bytes of this hash are stored as the new value r' of the random seed, and the remaining 32 bytes are returned as the next random value x. +int random() impure asm "RANDU256"; +;;; Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. +int rand(int range) impure asm "RAND"; +;;; Returns the current random seed as an unsigned 256-bit Integer. +int get_seed() impure asm "RANDSEED"; +;;; Sets the random seed to unsigned 256-bit seed. +() set_seed(int) impure asm "SETRAND"; +;;; Mixes unsigned 256-bit integer x into the random seed r by setting the random seed to sha256 of the concatenation of two 32-byte strings: the first with the big-endian representation of the old seed r, and the second with the big-endian representation of x. +() randomize(int x) impure asm "ADDRAND"; +;;; Equivalent to randomize(cur_lt());. +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +;;; Checks whether the data parts of two slices coinside +int equal_slice_bits (slice a, slice b) asm "SDEQ"; + +;;; Concatenates two builders +builder store_builder(builder to, builder from) asm "STBR"; + diff --git a/crypto/func/auto-tests/legacy_tests/storage/storage-contract.fc b/crypto/func/auto-tests/legacy_tests/storage/storage-contract.fc new file mode 100644 index 00000000..3dfe3ff1 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/storage/storage-contract.fc @@ -0,0 +1,266 @@ +#include "stdlib.fc"; +#include "constants.fc"; + +const CHUNK_SIZE = 64; +const fee::receipt_value = 20000000; +const fee::storage = 10000000; + + + +{- + storage#_ active:Bool + balance:Coins provider:MsgAddress + merkle_hash:uint256 file_size:uint64 next_proof_byte:uint64 + rate_per_mb_day:Coins + max_span:uint32 last_proof_time:uint32 + ^[client:MsgAddress torrent_hash:uint256] = Storage; +-} + +(slice, int) begin_parse_special(cell c) asm "x{D739} s,"; + +int check_proof(int merkle_hash, int byte_to_proof, int file_size, cell file_dict_proof) { + (slice cs, int special) = file_dict_proof.begin_parse_special(); + if (~ special) { + return false; + } + if (cs~load_uint(8) != 3) { ;; Merkle proof + return false; + } + if (cs~load_uint(256) != merkle_hash) { + return false; + } + cell file_dict = cs~load_ref(); + int key_len = 0; + while ((CHUNK_SIZE << key_len) < file_size) { + key_len += 1; + } + (slice data, int found?) = file_dict.udict_get?(key_len, byte_to_proof / CHUNK_SIZE); + if(found?) { + return true; + } + return false; +} + +() add_to_balance(int amount) impure inline_ref { + var ds = get_data().begin_parse(); + var (active, balance, residue) = (ds~load_int(1), ds~load_grams(), ds); + balance += amount; + begin_cell() + .store_int(active, 1) + .store_coins(balance) + .store_slice(residue) + .end_cell().set_data(); +} + +(slice, int) get_client_data(ds) { + ds = ds.preload_ref().begin_parse(); + return (ds~load_msg_addr(), ds~load_uint(256)); +} + +() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if (flags & 1) { ;; ignore all bounced messages + return (); + } + slice sender_address = cs~load_msg_addr(); + + if (in_msg_body.slice_empty?()) { + return add_to_balance(msg_value); + } + int op = in_msg_body~load_uint(32); + if (op == 0) { + return add_to_balance(msg_value); + } + + int query_id = in_msg_body~load_uint(64); + + if(op == op::offer_storage_contract) { + add_to_balance(msg_value - 2 * fee::receipt_value); + var (client, torrent_hash) = get_client_data(get_data().begin_parse()); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(client) + .store_coins(fee::receipt_value) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::contract_deployed, 32) + .store_uint(query_id, 64) + .store_uint(torrent_hash, 256) + .end_cell(); + send_raw_message(msg, 0); + } + + if (op == op::accept_storage_contract) { + var ds = get_data().begin_parse(); + (int active, int balance, slice provider, slice rest) = + (ds~load_int(1), ds~load_coins(), ds~load_msg_addr(), ds); + throw_unless(error::contract_already_active, ~ active); + throw_unless(error::unauthorized, equal_slice_bits(sender_address, provider)); + begin_cell() + .store_int(true, 1) + .store_coins(balance) + .store_slice(provider) + .store_slice(rest) + .end_cell().set_data(); + var (client, torrent_hash) = get_client_data(rest); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(client) + .store_coins(fee::receipt_value) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::storage_contract_confirmed, 32) + .store_uint(cur_lt(), 64) + .store_uint(torrent_hash, 256) + .end_cell(); + send_raw_message(msg, 0); + } + + if (op == op::close_contract) { + var ds = get_data().begin_parse(); + (int active, int balance, slice provider, slice rest) = + (ds~load_int(1), ds~load_coins(), ds~load_msg_addr(), ds); + var (client, torrent_hash) = get_client_data(rest); + throw_unless(error::unauthorized, equal_slice_bits(sender_address, provider) | equal_slice_bits(sender_address, client)); + var client_msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(client) + .store_coins(balance) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::storage_contract_terminated, 32) + .store_uint(cur_lt(), 64) + .store_uint(torrent_hash, 256) + .end_cell(); + if(~ active) { + return send_raw_message(client_msg, 128 + 32); + } + send_raw_message(client_msg, 64); + var provider_msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(provider) + .store_coins(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::storage_contract_terminated, 32) + .store_uint(cur_lt(), 64) + .store_uint(torrent_hash, 256) + .end_cell(); + return send_raw_message(provider_msg, 128 + 32); + } + + if (op == op::withdraw) { + var ds = get_data().begin_parse(); + (int active, int balance, slice provider) = (ds~load_int(1), ds~load_coins(), ds~load_msg_addr()); + throw_unless(error::contract_not_active, active); + throw_unless(error::unauthorized, equal_slice_bits(sender_address, provider)); + if(balance > 0) { + raw_reserve(balance + fee::storage, 2); + } + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(provider) + .store_coins(fee::receipt_value) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::reward_withdrawal, 32) + .store_uint(query_id, 64) + .end_cell(); + send_raw_message(msg, 128 + 32); + } + + if (op == op::proof_storage) { + cell file_dict_proof = in_msg_body~load_ref(); + var ds = get_data().begin_parse(); + var (active, + balance, + provider, + merkle_hash, + file_size, + next_proof, + rate_per_mb_day, + max_span, + last_proof_time, + client_data) = (ds~load_int(1), + ds~load_coins(), + ds~load_msg_addr(), + ds~load_uint(256), + ds~load_uint(64), + ds~load_uint(64), + ds~load_coins(), + ds~load_uint(32), + ds~load_uint(32), + ds~load_ref()); + throw_unless(error::contract_not_active, active); + throw_unless(error::unauthorized, equal_slice_bits(sender_address, provider)); + throw_unless(error::wrong_proof, check_proof(merkle_hash, next_proof, file_size, file_dict_proof)); + next_proof = rand(file_size); + int actual_span = min(now() - last_proof_time, max_span); + int bounty = muldiv(file_size * rate_per_mb_day, actual_span, 24 * 60 * 60 * 1024 * 1024); + balance = max(0, balance - bounty); + last_proof_time = now(); + begin_cell() + .store_int(true, 1) + .store_coins(balance) + .store_slice(provider) + .store_uint(merkle_hash, 256) + .store_uint(file_size, 64) + .store_uint(next_proof, 64) + .store_coins(rate_per_mb_day) + .store_uint(max_span, 32) + .store_uint(last_proof_time, 32) + .store_ref(client_data) + .end_cell().set_data(); + + ;; Send remaining balance back + cell msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(sender_address) + .store_uint(0, 4 + 1 + 4 + 4 + 64 + 32 + 1 + 1) + .end_cell(); + send_raw_message(msg, 64 + 2); + } +} + +_ get_storage_contract_data() method_id { + var ds = get_data().begin_parse(); + var (active, + balance, + provider, + merkle_hash, + file_size, + next_proof, + rate_per_mb_day, + max_span, + last_proof_time, + rest) = (ds~load_int(1), + ds~load_coins(), + ds~load_msg_addr(), + ds~load_uint(256), + ds~load_uint(64), + ds~load_uint(64), + ds~load_coins(), + ds~load_uint(32), + ds~load_uint(32), + ds); + var (client, torrent_hash) = get_client_data(rest); + return (active, balance, provider, merkle_hash, file_size, + next_proof, rate_per_mb_day, max_span, last_proof_time, + client, torrent_hash); +} + +_ get_torrent_hash() method_id { + var (active, balance, provider, merkle_hash, file_size, + next_proof, rate_per_mb_day, max_span, last_proof_time, + client, torrent_hash) = get_storage_contract_data(); + return torrent_hash; +} + +_ is_active() method_id { + return get_data().begin_parse().preload_int(1); +} + +;; next_proof, last_proof_time, max_span +_ get_next_proof_info() method_id { + var (active, balance, provider, merkle_hash, file_size, + next_proof, rate_per_mb_day, max_span, last_proof_time, + client, torrent_hash) = get_storage_contract_data(); + return (next_proof, last_proof_time, max_span); +} diff --git a/crypto/func/auto-tests/legacy_tests/storage/storage-provider.fc b/crypto/func/auto-tests/legacy_tests/storage/storage-provider.fc new file mode 100644 index 00000000..7df5739d --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/storage/storage-provider.fc @@ -0,0 +1,228 @@ +;; Storage contract fabric + +#include "stdlib.fc"; +#include "constants.fc"; + +const min_deploy_amount = 50000000; + +cell storage_contract_code() asm """ "storage-contract-code.boc" file>B B>boc PUSHREF """; + +slice calculate_address_by_stateinit(cell state_init) { + return begin_cell().store_uint(4, 3) + .store_int(0, 8) + .store_uint(cell_hash(state_init), 256) + .end_cell() + .begin_parse(); +} + +cell build_storage_contract_stateinit(int merkle_hash, int file_size, int rate_per_mb_day, + int max_span, slice client, int torrent_hash) { + cell data = begin_cell() + .store_int(0, 1) ;; active + .store_coins(0) ;; client balance + .store_slice(my_address()) + .store_uint(merkle_hash, 256) + .store_uint(file_size, 64) + .store_uint(0, 64) ;; next_proof + .store_coins(rate_per_mb_day) + .store_uint(max_span, 32) + .store_uint(now(), 32) ;; last_proof_time + .store_ref(begin_cell() + .store_slice(client) + .store_uint(torrent_hash, 256) + .end_cell()) + .end_cell(); + + cell state_init = begin_cell() + .store_uint(0, 2) + .store_maybe_ref(storage_contract_code()) + .store_maybe_ref(data) + .store_uint(0, 1) .end_cell(); + return state_init; +} + +() deploy_storage_contract (slice client, int query_id, int file_size, int merkle_hash, int torrent_hash, + int expected_rate, int expected_max_span) impure { + var ds = get_data().begin_parse(); + var (wallet_data, + accept_new_contracts?, + rate_per_mb_day, + max_span, + minimal_file_size, + maximal_file_size) = (ds~load_bits(32 + 32 + 256), + ds~load_int(1), + ds~load_coins(), + ds~load_uint(32), + ds~load_uint(64), + ds~load_uint(64)); + throw_unless(error::no_new_contracts, accept_new_contracts?); + throw_unless(error::file_too_small, file_size >= minimal_file_size); + throw_unless(error::file_too_big, file_size <= maximal_file_size); + throw_unless(error::provider_params_changed, expected_rate == rate_per_mb_day); + throw_unless(error::provider_params_changed, expected_max_span == max_span); + cell state_init = build_storage_contract_stateinit(merkle_hash, file_size, rate_per_mb_day, + max_span, client, torrent_hash); + cell msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(calculate_address_by_stateinit(state_init)) + .store_coins(0) + .store_uint(4 + 2, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_uint(op::offer_storage_contract, 32) + .store_uint(query_id, 64) + .end_cell(); + send_raw_message(msg, 64); +} + +() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if ((flags & 1) | in_msg_body.slice_empty?()) { ;; ignore all bounced and empty messages + return (); + } + slice sender_address = cs~load_msg_addr(); + + int op = in_msg_body~load_uint(32); + if (op == 0) { ;; transfer with text message + return (); + } + int query_id = in_msg_body~load_uint(64); + + if(op == op::offer_storage_contract) { + throw_unless(error::not_enough_money, msg_value >= min_deploy_amount); + ;; torrent_info piece_size:uint32 file_size:uint64 root_hash:(## 256) header_size:uint64 header_hash:(## 256) + ;; microchunk_hash:(Maybe (## 256)) description:Text = TorrentInfo; + ;; + ;; new_storage_contract#00000001 query_id:uint64 info:(^ TorrentInfo) microchunk_hash:uint256 + ;; expected_rate:Coins expected_max_span:uint32 = NewStorageContract; + cell torrent_info = in_msg_body~load_ref(); + int torrent_hash = cell_hash(torrent_info); + slice info_cs = torrent_info.begin_parse(); + info_cs~skip_bits(32); + int file_size = info_cs~load_uint(64); + int merkle_hash = in_msg_body~load_uint(256); + + int expected_rate = in_msg_body~load_coins(); + int expected_max_span = in_msg_body~load_uint(32); + deploy_storage_contract(sender_address, query_id, file_size, merkle_hash, torrent_hash, + expected_rate, expected_max_span); + return (); + } + if(op == op::storage_contract_terminated) { + return (); + } + + if(op == op::update_pubkey) { + if(~ equal_slice_bits(my_address(), sender_address)) { + return (); + } + var ds = get_data().begin_parse(); + var (seqno_subwallet, + _, + non_wallet_data) = (ds~load_bits(32 + 32), + ds~load_uint(256), + ds); + int new_pubkey = in_msg_body~load_uint(256); + set_data(begin_cell() + .store_slice(seqno_subwallet) + .store_uint(new_pubkey, 256) + .store_slice(non_wallet_data) + .end_cell()); + } + if(op == op::update_storage_params) { + if(~ equal_slice_bits(my_address(), sender_address)) { + return (); + } + var ds = get_data().begin_parse(); + var wallet_data = ds~load_bits(32 + 32 + 256); + var(accept_new_contracts?, + rate_per_mb_day, + max_span, + minimal_file_size, + maximal_file_size) = (in_msg_body~load_int(1), + in_msg_body~load_coins(), + in_msg_body~load_uint(32), + in_msg_body~load_uint(64), + in_msg_body~load_uint(64)); + set_data(begin_cell() + .store_slice(wallet_data) + .store_int(accept_new_contracts?, 1) + .store_coins(rate_per_mb_day) + .store_uint(max_span, 32) + .store_uint(minimal_file_size, 64) + .store_uint(maximal_file_size, 64) + .end_cell()); + } +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + throw_if(35, valid_until <= now()); + var ds = get_data().begin_parse(); + var (stored_seqno, + stored_subwallet, + public_key, + non_wallet_data) = (ds~load_uint(32), + ds~load_uint(32), + ds~load_uint(256), + ds); + throw_unless(33, msg_seqno == stored_seqno); + throw_unless(34, subwallet_id == stored_subwallet); + throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + cs~touch(); + while (cs.slice_refs()) { + var mode = cs~load_uint(8); + send_raw_message(cs~load_ref(), mode); + } + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .store_slice(non_wallet_data) + .end_cell()); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(64); + return cs.preload_uint(256); +} + +;; seqno, subwallet, key +_ get_wallet_params() method_id { + var ds = get_data().begin_parse(); + var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256)); + return (stored_seqno, stored_subwallet, public_key); +} + +_ get_storage_params() method_id { + var ds = get_data().begin_parse(); + var (wallet_data, + accept_new_contracts?, + rate_per_mb_day, + max_span, + minimal_file_size, + maximal_file_size) = (ds~load_bits(32 + 32 + 256), + ds~load_int(1), + ds~load_coins(), + ds~load_uint(32), + ds~load_uint(64), + ds~load_uint(64)); + return (accept_new_contracts?, rate_per_mb_day, max_span, minimal_file_size, maximal_file_size); +} + +slice get_storage_contract_address(int merkle_hash, int file_size, slice client, int torrent_hash) method_id { + var (_, rate_per_mb_day, max_span, _, _) = get_storage_params(); + cell state_init = build_storage_contract_stateinit(merkle_hash, file_size, rate_per_mb_day, max_span, client, torrent_hash); + return calculate_address_by_stateinit(state_init); +} diff --git a/crypto/func/auto-tests/legacy_tests/tact-examples/jetton_JettonDefaultWallet.code.fc b/crypto/func/auto-tests/legacy_tests/tact-examples/jetton_JettonDefaultWallet.code.fc new file mode 100644 index 00000000..522fa9e1 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tact-examples/jetton_JettonDefaultWallet.code.fc @@ -0,0 +1,439 @@ +#include "stdlib.fc"; + +int __tact_my_balance() inline { + return pair_first(get_balance()); +} + +forall X -> X __tact_not_null(X x) { throw_if(128, null?(x)); return x; } + +global (int, slice, int, slice) __tact_context; +global cell __tact_context_sys; + +(int, slice, int, slice) __tact_context_get() inline { return __tact_context; } + +() __tact_verify_address(slice address) inline { + throw_unless(136, address.slice_bits() != 267); +} + +builder __tact_store_bool(builder b, int v) inline { + b = b.store_int(v, 1); + return b; +} + +(slice, slice) __tact_load_address(slice cs) inline { + slice raw = cs~load_msg_addr(); + __tact_verify_address(raw); + return (cs, raw); +} + +(slice, slice) __tact_load_address_opt(slice cs) inline { + slice raw = cs~load_msg_addr(); + if (raw.preload_uint(2) != 0) { + __tact_verify_address(raw); + return (cs, raw); + } else { + return (cs, null()); + } +} + +builder __tact_store_address(builder b, slice address) inline { + __tact_verify_address(address); + b = b.store_slice(address); + return b; +} + +builder __tact_store_address_opt(builder b, slice address) inline { + if (null?(address)) { + b = b.store_uint(0, 2); + return b; + } else { + return __tact_store_address(b, address); + } +} + +slice __tact_create_address(int chain, int hash) inline { + var b = begin_cell(); + b = b.store_uint(2, 2); + b = b.store_uint(0, 1); + b = b.store_int(chain, 8); + b = b.store_uint(hash, 256); + return b.end_cell().begin_parse(); +} + +slice __tact_compute_contract_address(int chain, cell code, cell data) inline { + var b = begin_cell(); + b = b.store_uint(0, 2); + b = b.store_uint(3, 2); + b = b.store_uint(0, 1); + b = b.store_ref(code); + b = b.store_ref(data); + var hash = cell_hash(b.end_cell()); + return __tact_create_address(chain, hash); +} + +int __tact_address_eq(slice a, slice b) inline { + return equal_slice_bits(a, b); +} + +int __tact_address_neq(slice a, slice b) inline { + return ~ equal_slice_bits(a, b); +} + +cell __tact_dict_set_code(cell dict, int id, cell code) inline { + return udict_set_ref(dict, 16, id, code); +} + +cell __tact_dict_get_code(cell dict, int id) inline { + var (data, ok) = udict_get_ref?(dict, 16, id); + throw_unless(135, ok); + return data; +} + +(slice, ((int, int, slice, slice, cell, int, slice))) __gen_read_TokenTransfer(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 260734629); + var v'queryId = sc_0~load_uint(64); + var v'amount = sc_0~load_coins(); + var v'destination = sc_0~__tact_load_address(); + var v'responseDestination = sc_0~__tact_load_address_opt(); + var v'customPayload = sc_0~load_int(1) ? sc_0~load_ref() : null(); + var v'forwardTonAmount = sc_0~load_coins(); + var v'forwardPayload = sc_0; + return (sc_0, (v'queryId, v'amount, v'destination, v'responseDestination, v'customPayload, v'forwardTonAmount, v'forwardPayload)); +} + +builder __gen_write_TokenTransferInternal(builder build_0, (int, int, slice, slice, int, slice) v) inline_ref { + var (v'queryId, v'amount, v'from, v'responseAddress, v'forwardTonAmount, v'forwardPayload) = v; + build_0 = store_uint(build_0, 395134233, 32); + build_0 = build_0.store_uint(v'queryId, 64); + build_0 = build_0.store_coins(v'amount); + build_0 = __tact_store_address(build_0, v'from); + build_0 = __tact_store_address_opt(build_0, v'responseAddress); + build_0 = build_0.store_coins(v'forwardTonAmount); + build_0 = build_0.store_slice(v'forwardPayload); + return build_0; +} + +cell __gen_writecell_TokenTransferInternal((int, int, slice, slice, int, slice) v) inline_ref { + return __gen_write_TokenTransferInternal(begin_cell(), v).end_cell(); +} + +(slice, ((int, int, slice, slice, int, slice))) __gen_read_TokenTransferInternal(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 395134233); + var v'queryId = sc_0~load_uint(64); + var v'amount = sc_0~load_coins(); + var v'from = sc_0~__tact_load_address(); + var v'responseAddress = sc_0~__tact_load_address_opt(); + var v'forwardTonAmount = sc_0~load_coins(); + var v'forwardPayload = sc_0; + return (sc_0, (v'queryId, v'amount, v'from, v'responseAddress, v'forwardTonAmount, v'forwardPayload)); +} + +builder __gen_write_TokenNotification(builder build_0, (int, int, slice, slice) v) inline_ref { + var (v'queryId, v'amount, v'from, v'forwardPayload) = v; + build_0 = store_uint(build_0, 1935855772, 32); + build_0 = build_0.store_uint(v'queryId, 64); + build_0 = build_0.store_coins(v'amount); + build_0 = __tact_store_address(build_0, v'from); + build_0 = build_0.store_slice(v'forwardPayload); + return build_0; +} + +cell __gen_writecell_TokenNotification((int, int, slice, slice) v) inline_ref { + return __gen_write_TokenNotification(begin_cell(), v).end_cell(); +} + +(slice, ((int, int, slice, slice))) __gen_read_TokenBurn(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 1499400124); + var v'queryId = sc_0~load_uint(64); + var v'amount = sc_0~load_coins(); + var v'owner = sc_0~__tact_load_address(); + var v'responseAddress = sc_0~__tact_load_address_opt(); + return (sc_0, (v'queryId, v'amount, v'owner, v'responseAddress)); +} + +builder __gen_write_TokenBurnNotification(builder build_0, (int, int, slice, slice) v) inline_ref { + var (v'queryId, v'amount, v'owner, v'responseAddress) = v; + build_0 = store_uint(build_0, 2078119902, 32); + build_0 = build_0.store_uint(v'queryId, 64); + build_0 = build_0.store_coins(v'amount); + build_0 = __tact_store_address(build_0, v'owner); + build_0 = __tact_store_address_opt(build_0, v'responseAddress); + return build_0; +} + +cell __gen_writecell_TokenBurnNotification((int, int, slice, slice) v) inline_ref { + return __gen_write_TokenBurnNotification(begin_cell(), v).end_cell(); +} + +builder __gen_write_TokenExcesses(builder build_0, (int) v) inline_ref { + var (v'queryId) = v; + build_0 = store_uint(build_0, 3576854235, 32); + build_0 = build_0.store_uint(v'queryId, 64); + return build_0; +} + +cell __gen_writecell_TokenExcesses((int) v) inline_ref { + return __gen_write_TokenExcesses(begin_cell(), v).end_cell(); +} + +builder __gen_write_JettonDefaultWallet(builder build_0, (int, slice, slice) v) inline_ref { + var (v'balance, v'owner, v'master) = v; + build_0 = build_0.store_int(v'balance, 257); + build_0 = __tact_store_address(build_0, v'owner); + build_0 = __tact_store_address(build_0, v'master); + return build_0; +} + +(slice, ((int, slice, slice))) __gen_read_JettonDefaultWallet(slice sc_0) inline_ref { + var v'balance = sc_0~load_int(257); + var v'owner = sc_0~__tact_load_address(); + var v'master = sc_0~__tact_load_address(); + return (sc_0, (v'balance, v'owner, v'master)); +} + +_ __gen_StateInit_get_code((cell, cell) v) inline { + var (v'code, v'data) = v; + return v'code; +} + +(int, slice, slice, cell) __gen_JettonWalletData_to_external(((int, slice, slice, cell)) v) { + var (v'balance, v'owner, v'master, v'walletCode) = v; + return (v'balance, v'owner, v'master, v'walletCode); +} + +(int, slice, slice) __gen_load_JettonDefaultWallet() inline_ref { + slice sc = get_data().begin_parse(); + __tact_context_sys = sc~load_ref(); + return sc~__gen_read_JettonDefaultWallet(); +} + +() __gen_store_JettonDefaultWallet((int, slice, slice) v) impure inline_ref { + builder b = begin_cell(); + b = b.store_ref(__tact_context_sys); + b = __gen_write_JettonDefaultWallet(b, v); + set_data(b.end_cell()); +} + +slice $contractAddress((cell, cell) $s) impure { + var (($s'code, $s'data)) = $s; + return __tact_compute_contract_address(0, $s'code, $s'data); +} + +() $send((int, slice, int, int, cell, cell, cell) $params) impure { + var (($params'bounce, $params'to, $params'value, $params'mode, $params'body, $params'code, $params'data)) = $params; + builder $b = begin_cell(); + $b = store_int($b, 1, 2); + $b = __tact_store_bool($b, $params'bounce); + $b = store_int($b, 0, 3); + $b = __tact_store_address($b, $params'to); + $b = store_coins($b, $params'value); + $b = store_int($b, 0, ((((1 + 4) + 4) + 64) + 32)); + if (((~ null?($params'code)) | (~ null?($params'data)))) { + $b = __tact_store_bool($b, true); + builder $bc = begin_cell(); + $bc = __tact_store_bool($bc, false); + $bc = __tact_store_bool($bc, false); + if ((~ null?($params'code))) { + $bc = __tact_store_bool($bc, true); + $bc = store_ref($bc, __tact_not_null($params'code)); + } else { + $bc = __tact_store_bool($bc, false); + } + if ((~ null?($params'data))) { + $bc = __tact_store_bool($bc, true); + $bc = store_ref($bc, __tact_not_null($params'data)); + } else { + $bc = __tact_store_bool($bc, false); + } + $bc = __tact_store_bool($bc, false); + $b = __tact_store_bool($b, true); + $b = store_ref($b, end_cell($bc)); + } else { + $b = __tact_store_bool($b, false); + } + cell $body = $params'body; + if ((~ null?($body))) { + $b = __tact_store_bool($b, true); + $b = store_ref($b, __tact_not_null($body)); + } else { + $b = __tact_store_bool($b, false); + } + cell $c = end_cell($b); + send_raw_message($c, $params'mode); +} + +int $__gen_Context_readForwardFee((int, slice, int, slice) $self) impure { + var (($self'bounced, $self'sender, $self'value, $self'raw)) = $self; + var (($self'bounced, $self'sender, $self'value, $self'raw)) = $self; + slice $sc = $self'raw; + $sc~load_coins(); + $sc~skip_bits(1); + $sc~load_coins(); + return (($sc~load_coins() * 3) / 2); +} + +cell $__gen_JettonDefaultWallet_init(cell sys', slice $master, slice $owner) { + var (($self'balance, $self'owner, $self'master)) = (null(), null(), null()); + $self'balance = 0; + $self'owner = $owner; + $self'master = $master; + var b' = begin_cell(); + b' = b'.store_ref(sys'); + b' = __gen_write_JettonDefaultWallet(b', ($self'balance, $self'owner, $self'master)); + return b'.end_cell(); +} + +(cell, cell) $__gen_JettonDefaultWallet_init_child(cell sys', slice $master, slice $owner) { + slice sc' = sys'.begin_parse(); + cell source = sc'~load_dict(); + cell contracts = new_dict(); + + ;; Contract Code: JettonDefaultWallet + cell mine = __tact_dict_get_code(source, 55471); + contracts = __tact_dict_set_code(contracts, 55471, mine); + cell sys = begin_cell().store_dict(contracts).end_cell(); + return (mine, $__gen_JettonDefaultWallet_init(sys, $master, $owner)); +} + +(int, slice, slice, cell) $__gen_JettonDefaultWallet_get_wallet_data((int, slice, slice) $self) impure { + var (($self'balance, $self'owner, $self'master)) = $self; + return ($self'balance, $self'owner, $self'master, __gen_StateInit_get_code($__gen_JettonDefaultWallet_init_child(__tact_context_sys, $self'master, $self'owner))); +} + +_ $__gen_get_get_wallet_data() method_id(97026) { + var self = __gen_load_JettonDefaultWallet(); + var res = $__gen_JettonDefaultWallet_get_wallet_data(self); + return __gen_JettonWalletData_to_external(res); +} + +(((int, slice, slice)), ()) $__gen_JettonDefaultWallet_receive_TokenTransfer((int, slice, slice) $self, (int, int, slice, slice, cell, int, slice) $msg) impure { + var ($self'balance, $self'owner, $self'master) = $self; + var ($msg'queryId, $msg'amount, $msg'destination, $msg'responseDestination, $msg'customPayload, $msg'forwardTonAmount, $msg'forwardPayload) = $msg; + var ($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw) = __tact_context_get(); + throw_unless(4429, __tact_address_eq($ctx'sender, $self'owner)); + $self'balance = ($self'balance - $msg'amount); + throw_unless(62972, ($self'balance >= 0)); + int $fwdFee = $__gen_Context_readForwardFee(($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw)); + int $fwdCount = 1; + if (($msg'forwardTonAmount > 0)) { + $fwdCount = 2; + } + throw_unless(16059, ($ctx'value > ((($fwdCount * $fwdFee) + (2 * 10000000)) + 10000000))); + var ($init'code, $init'data) = $__gen_JettonDefaultWallet_init_child(__tact_context_sys, $self'master, $msg'destination); + slice $walletAddress = $contractAddress(($init'code, $init'data)); + $send((true, $walletAddress, 0, 64, __gen_writecell_TokenTransferInternal(($msg'queryId, $msg'amount, $self'owner, $self'owner, $msg'forwardTonAmount, $msg'forwardPayload)), $init'code, $init'data)); + return (($self'balance, $self'owner, $self'master), ()); +} + +(((int, slice, slice)), ()) $__gen_JettonDefaultWallet_receive_TokenTransferInternal((int, slice, slice) $self, (int, int, slice, slice, int, slice) $msg) impure { + var ($self'balance, $self'owner, $self'master) = $self; + var ($msg'queryId, $msg'amount, $msg'from, $msg'responseAddress, $msg'forwardTonAmount, $msg'forwardPayload) = $msg; + var ($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw) = __tact_context_get(); + if (__tact_address_neq($ctx'sender, $self'master)) { + var ($sinit'code, $sinit'data) = $__gen_JettonDefaultWallet_init_child(__tact_context_sys, $self'master, $msg'from); + throw_unless(4429, __tact_address_eq($contractAddress(($sinit'code, $sinit'data)), $ctx'sender)); + } + $self'balance = ($self'balance + $msg'amount); + throw_unless(62972, ($self'balance >= 0)); + int $msgValue = $ctx'value; + int $tonBalanceBeforeMsg = (__tact_my_balance() - $msgValue); + int $storageFee = (10000000 - min($tonBalanceBeforeMsg, 10000000)); + $msgValue = ($msgValue - ($storageFee + 10000000)); + if (($msg'forwardTonAmount > 0)) { + int $fwdFee = $__gen_Context_readForwardFee(($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw)); + $msgValue = ($msgValue - ($msg'forwardTonAmount + $fwdFee)); + $send((false, $self'owner, $msg'forwardTonAmount, 0, __gen_writecell_TokenNotification(($msg'queryId, $msg'amount, $msg'from, $msg'forwardPayload)), null(), null())); + } + if (((~ null?($msg'responseAddress)) & ($msgValue > 0))) { + $send((false, __tact_not_null($msg'responseAddress), $msgValue, 0, __gen_writecell_TokenExcesses(($msg'queryId)), null(), null())); + } + return (($self'balance, $self'owner, $self'master), ()); +} + +(((int, slice, slice)), ()) $__gen_JettonDefaultWallet_receive_TokenBurn((int, slice, slice) $self, (int, int, slice, slice) $msg) impure { + var ($self'balance, $self'owner, $self'master) = $self; + var ($msg'queryId, $msg'amount, $msg'owner, $msg'responseAddress) = $msg; + var ($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw) = __tact_context_get(); + throw_unless(4429, __tact_address_eq($ctx'sender, $self'owner)); + $self'balance = ($self'balance - $msg'amount); + throw_unless(62972, ($self'balance >= 0)); + int $fwdFee = $__gen_Context_readForwardFee(($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw)); + throw_unless(16059, ($ctx'value > (($fwdFee + (2 * 10000000)) + 10000000))); + $send((true, $self'master, 0, 64, __gen_writecell_TokenBurnNotification(($msg'queryId, $msg'amount, $self'owner, $self'owner)), null(), null())); + return (($self'balance, $self'owner, $self'master), ()); +} + +((int, slice, slice), ()) $__gen_JettonDefaultWallet_receive_bounced((int, slice, slice) $self, slice $msg) impure { + var ($self'balance, $self'owner, $self'master) = $self; + $msg~skip_bits(32); + int $op = $msg~load_uint(32); + int $queryId = $msg~load_uint(64); + int $jettonAmount = $msg~load_coins(); + throw_unless(13650, (($op == 395134233) | ($op == 2078119902))); + $self'balance = ($self'balance + $jettonAmount); + return (($self'balance, $self'owner, $self'master), ()); +} + + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + + ;; Parse incoming message + int op = 0; + if (slice_bits(in_msg) >= 32) { + op = in_msg.preload_uint(32); + } + var cs = in_msg_cell.begin_parse(); + var msg_flags = cs~load_uint(4); + var msg_bounced = ((msg_flags & 1) == 1 ? true : false); + slice msg_sender_addr = cs~load_msg_addr(); + __tact_context = (msg_bounced, msg_sender_addr, msg_value, cs); + + ;; Handle bounced messages + if (msg_bounced) { + var self = __gen_load_JettonDefaultWallet(); + self~$__gen_JettonDefaultWallet_receive_bounced(in_msg); + __gen_store_JettonDefaultWallet(self); + return (); + } + + ;; Receive TokenTransfer message + if (op == 260734629) { + var self = __gen_load_JettonDefaultWallet(); + var msg = in_msg~__gen_read_TokenTransfer(); + self~$__gen_JettonDefaultWallet_receive_TokenTransfer(msg); + __gen_store_JettonDefaultWallet(self); + return (); + } + + ;; Receive TokenTransferInternal message + if (op == 395134233) { + var self = __gen_load_JettonDefaultWallet(); + var msg = in_msg~__gen_read_TokenTransferInternal(); + self~$__gen_JettonDefaultWallet_receive_TokenTransferInternal(msg); + __gen_store_JettonDefaultWallet(self); + return (); + } + + ;; Receive TokenBurn message + if (op == 1499400124) { + var self = __gen_load_JettonDefaultWallet(); + var msg = in_msg~__gen_read_TokenBurn(); + self~$__gen_JettonDefaultWallet_receive_TokenBurn(msg); + __gen_store_JettonDefaultWallet(self); + return (); + } + + throw(130); +} + +_ supported_interfaces() method_id { + return ( + "org.ton.introspection.v0"H >> 128, + "org.ton.abi.ipfs.v0"H >> 128, + "org.ton.jetton.wallet"H >> 128 + ); +} + +_ get_abi_ipfs() { + return "ipfs://QmXBfqbQzeN1uT55MyYpwhU9RV47Sq3quVt3qFLgWH8NhD"; +} diff --git a/crypto/func/auto-tests/legacy_tests/tact-examples/jetton_SampleJetton.code.fc b/crypto/func/auto-tests/legacy_tests/tact-examples/jetton_SampleJetton.code.fc new file mode 100644 index 00000000..30bff4e1 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tact-examples/jetton_SampleJetton.code.fc @@ -0,0 +1,440 @@ +#include "stdlib.fc"; + +forall X -> X __tact_not_null(X x) { throw_if(128, null?(x)); return x; } + +global (int, slice, int, slice) __tact_context; +global cell __tact_context_sys; + +(int, slice, int, slice) __tact_context_get() inline { return __tact_context; } + +() __tact_verify_address(slice address) inline { + throw_unless(136, address.slice_bits() != 267); +} + +builder __tact_store_bool(builder b, int v) inline { + b = b.store_int(v, 1); + return b; +} + +(slice, slice) __tact_load_address(slice cs) inline { + slice raw = cs~load_msg_addr(); + __tact_verify_address(raw); + return (cs, raw); +} + +(slice, slice) __tact_load_address_opt(slice cs) inline { + slice raw = cs~load_msg_addr(); + if (raw.preload_uint(2) != 0) { + __tact_verify_address(raw); + return (cs, raw); + } else { + return (cs, null()); + } +} + +builder __tact_store_address(builder b, slice address) inline { + __tact_verify_address(address); + b = b.store_slice(address); + return b; +} + +builder __tact_store_address_opt(builder b, slice address) inline { + if (null?(address)) { + b = b.store_uint(0, 2); + return b; + } else { + return __tact_store_address(b, address); + } +} + +slice __tact_create_address(int chain, int hash) inline { + var b = begin_cell(); + b = b.store_uint(2, 2); + b = b.store_uint(0, 1); + b = b.store_int(chain, 8); + b = b.store_uint(hash, 256); + return b.end_cell().begin_parse(); +} + +slice __tact_compute_contract_address(int chain, cell code, cell data) inline { + var b = begin_cell(); + b = b.store_uint(0, 2); + b = b.store_uint(3, 2); + b = b.store_uint(0, 1); + b = b.store_ref(code); + b = b.store_ref(data); + var hash = cell_hash(b.end_cell()); + return __tact_create_address(chain, hash); +} + +int __tact_address_eq(slice a, slice b) inline { + return equal_slice_bits(a, b); +} + +cell __tact_dict_set_code(cell dict, int id, cell code) inline { + return udict_set_ref(dict, 16, id, code); +} + +cell __tact_dict_get_code(cell dict, int id) inline { + var (data, ok) = udict_get_ref?(dict, 16, id); + throw_unless(135, ok); + return data; +} + +builder __gen_write_TokenTransferInternal(builder build_0, (int, int, slice, slice, int, slice) v) inline_ref { + var (v'queryId, v'amount, v'from, v'responseAddress, v'forwardTonAmount, v'forwardPayload) = v; + build_0 = store_uint(build_0, 395134233, 32); + build_0 = build_0.store_uint(v'queryId, 64); + build_0 = build_0.store_coins(v'amount); + build_0 = __tact_store_address(build_0, v'from); + build_0 = __tact_store_address_opt(build_0, v'responseAddress); + build_0 = build_0.store_coins(v'forwardTonAmount); + build_0 = build_0.store_slice(v'forwardPayload); + return build_0; +} + +cell __gen_writecell_TokenTransferInternal((int, int, slice, slice, int, slice) v) inline_ref { + return __gen_write_TokenTransferInternal(begin_cell(), v).end_cell(); +} + +(slice, ((int, int, slice, slice))) __gen_read_TokenBurnNotification(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 2078119902); + var v'queryId = sc_0~load_uint(64); + var v'amount = sc_0~load_coins(); + var v'owner = sc_0~__tact_load_address(); + var v'responseAddress = sc_0~__tact_load_address_opt(); + return (sc_0, (v'queryId, v'amount, v'owner, v'responseAddress)); +} + +builder __gen_write_TokenExcesses(builder build_0, (int) v) inline_ref { + var (v'queryId) = v; + build_0 = store_uint(build_0, 3576854235, 32); + build_0 = build_0.store_uint(v'queryId, 64); + return build_0; +} + +cell __gen_writecell_TokenExcesses((int) v) inline_ref { + return __gen_write_TokenExcesses(begin_cell(), v).end_cell(); +} + +(slice, ((cell))) __gen_read_TokenUpdateContent(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 201882270); + var v'content = sc_0~load_int(1) ? sc_0~load_ref() : null(); + return (sc_0, (v'content)); +} + +(slice, ((int))) __gen_read_Mint(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 33240155); + var v'amount = sc_0~load_int(257); + return (sc_0, (v'amount)); +} + +builder __gen_write_JettonDefaultWallet(builder build_0, (int, slice, slice) v) inline_ref { + var (v'balance, v'owner, v'master) = v; + build_0 = build_0.store_int(v'balance, 257); + build_0 = __tact_store_address(build_0, v'owner); + build_0 = __tact_store_address(build_0, v'master); + return build_0; +} + +builder __gen_write_SampleJetton(builder build_0, (int, slice, cell, int) v) inline_ref { + var (v'totalSupply, v'owner, v'content, v'mintable) = v; + build_0 = build_0.store_coins(v'totalSupply); + build_0 = __tact_store_address(build_0, v'owner); + build_0 = ~ null?(v'content) ? build_0.store_int(true, 1).store_ref(v'content) : build_0.store_int(false, 1); + build_0 = build_0.store_int(v'mintable, 1); + return build_0; +} + +(slice, ((int, slice, cell, int))) __gen_read_SampleJetton(slice sc_0) inline_ref { + var v'totalSupply = sc_0~load_coins(); + var v'owner = sc_0~__tact_load_address(); + var v'content = sc_0~load_int(1) ? sc_0~load_ref() : null(); + var v'mintable = sc_0~load_int(1); + return (sc_0, (v'totalSupply, v'owner, v'content, v'mintable)); +} + +_ __gen_StateInit_get_code((cell, cell) v) inline { + var (v'code, v'data) = v; + return v'code; +} + +_ __gen_Context_get_sender((int, slice, int, slice) v) inline { + var (v'bounced, v'sender, v'value, v'raw) = v; + return v'sender; +} + +(int, int, slice, cell, cell) __gen_JettonData_to_external(((int, int, slice, cell, cell)) v) { + var (v'totalSupply, v'mintable, v'owner, v'content, v'walletCode) = v; + return (v'totalSupply, v'mintable, v'owner, v'content, v'walletCode); +} + +(int, slice, cell, int) __gen_load_SampleJetton() inline_ref { + slice sc = get_data().begin_parse(); + __tact_context_sys = sc~load_ref(); + return sc~__gen_read_SampleJetton(); +} + +() __gen_store_SampleJetton((int, slice, cell, int) v) impure inline_ref { + builder b = begin_cell(); + b = b.store_ref(__tact_context_sys); + b = __gen_write_SampleJetton(b, v); + set_data(b.end_cell()); +} + +cell $emptyCell() impure { + return end_cell(begin_cell()); +} + +slice $__gen_Cell_asSlice(cell $self) impure { + var ($self) = $self; + return begin_parse($self); +} + +slice $emptySlice() impure { + return $__gen_Cell_asSlice($emptyCell()); +} + +slice $contractAddress((cell, cell) $s) impure { + var (($s'code, $s'data)) = $s; + return __tact_compute_contract_address(0, $s'code, $s'data); +} + +() $send((int, slice, int, int, cell, cell, cell) $params) impure { + var (($params'bounce, $params'to, $params'value, $params'mode, $params'body, $params'code, $params'data)) = $params; + builder $b = begin_cell(); + $b = store_int($b, 1, 2); + $b = __tact_store_bool($b, $params'bounce); + $b = store_int($b, 0, 3); + $b = __tact_store_address($b, $params'to); + $b = store_coins($b, $params'value); + $b = store_int($b, 0, ((((1 + 4) + 4) + 64) + 32)); + if (((~ null?($params'code)) | (~ null?($params'data)))) { + $b = __tact_store_bool($b, true); + builder $bc = begin_cell(); + $bc = __tact_store_bool($bc, false); + $bc = __tact_store_bool($bc, false); + if ((~ null?($params'code))) { + $bc = __tact_store_bool($bc, true); + $bc = store_ref($bc, __tact_not_null($params'code)); + } else { + $bc = __tact_store_bool($bc, false); + } + if ((~ null?($params'data))) { + $bc = __tact_store_bool($bc, true); + $bc = store_ref($bc, __tact_not_null($params'data)); + } else { + $bc = __tact_store_bool($bc, false); + } + $bc = __tact_store_bool($bc, false); + $b = __tact_store_bool($b, true); + $b = store_ref($b, end_cell($bc)); + } else { + $b = __tact_store_bool($b, false); + } + cell $body = $params'body; + if ((~ null?($body))) { + $b = __tact_store_bool($b, true); + $b = store_ref($b, __tact_not_null($body)); + } else { + $b = __tact_store_bool($b, false); + } + cell $c = end_cell($b); + send_raw_message($c, $params'mode); +} + +cell $__gen_JettonDefaultWallet_init(cell sys', slice $master, slice $owner) { + var (($self'balance, $self'owner, $self'master)) = (null(), null(), null()); + $self'balance = 0; + $self'owner = $owner; + $self'master = $master; + var b' = begin_cell(); + b' = b'.store_ref(sys'); + b' = __gen_write_JettonDefaultWallet(b', ($self'balance, $self'owner, $self'master)); + return b'.end_cell(); +} + +(cell, cell) $__gen_JettonDefaultWallet_init_child(cell sys', slice $master, slice $owner) { + slice sc' = sys'.begin_parse(); + cell source = sc'~load_dict(); + cell contracts = new_dict(); + + ;; Contract Code: JettonDefaultWallet + cell mine = __tact_dict_get_code(source, 55471); + contracts = __tact_dict_set_code(contracts, 55471, mine); + cell sys = begin_cell().store_dict(contracts).end_cell(); + return (mine, $__gen_JettonDefaultWallet_init(sys, $master, $owner)); +} + +((int, slice, cell, int), (cell, cell)) $__gen_SampleJetton_getJettonWalletInit((int, slice, cell, int) $self, slice $address) impure { + var (($self'totalSupply, $self'owner, $self'content, $self'mintable)) = $self; + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), $__gen_JettonDefaultWallet_init_child(__tact_context_sys, my_address(), $address)); +} + +slice $__gen_SampleJetton_get_wallet_address((int, slice, cell, int) $self, slice $owner) impure { + var (($self'totalSupply, $self'owner, $self'content, $self'mintable)) = $self; + var ($winit'code, $winit'data) = ($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_getJettonWalletInit($owner); + return $contractAddress(($winit'code, $winit'data)); +} + +_ $__gen_get_get_wallet_address(slice $$owner) method_id(103289) { + slice $owner = $$owner; + var self = __gen_load_SampleJetton(); + var res = $__gen_SampleJetton_get_wallet_address(self, $owner); + return res; +} + +(int, int, slice, cell, cell) $__gen_SampleJetton_get_jetton_data((int, slice, cell, int) $self) impure { + var (($self'totalSupply, $self'owner, $self'content, $self'mintable)) = $self; + cell $code = __gen_StateInit_get_code(($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_getJettonWalletInit(my_address())); + return ($self'totalSupply, $self'mintable, $self'owner, $self'content, $code); +} + +_ $__gen_get_get_jetton_data() method_id(106029) { + var self = __gen_load_SampleJetton(); + var res = $__gen_SampleJetton_get_jetton_data(self); + return __gen_JettonData_to_external(res); +} + +((int, slice, cell, int), ()) $__gen_SampleJetton_mint((int, slice, cell, int) $self, slice $to, int $amount, slice $responseAddress) impure { + var (($self'totalSupply, $self'owner, $self'content, $self'mintable)) = $self; + $self'totalSupply = ($self'totalSupply + $amount); + var ($winit'code, $winit'data) = ($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_getJettonWalletInit($to); + slice $walletAddress = $contractAddress(($winit'code, $winit'data)); + $send((false, $walletAddress, 0, 64, __gen_writecell_TokenTransferInternal((0, $amount, my_address(), $responseAddress, 0, $emptySlice())), $winit'code, $winit'data)); + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), ()); +} + +((int, slice, cell, int), ()) $__gen_SampleJetton_requireWallet((int, slice, cell, int) $self, slice $owner) impure { + var (($self'totalSupply, $self'owner, $self'content, $self'mintable)) = $self; + var ($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw) = __tact_context_get(); + var ($winit'code, $winit'data) = ($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_getJettonWalletInit($owner); + throw_unless(4429, __tact_address_eq($contractAddress(($winit'code, $winit'data)), $ctx'sender)); + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), ()); +} + +((int, slice, cell, int), ()) $__gen_SampleJetton_requireOwner((int, slice, cell, int) $self) impure { + var (($self'totalSupply, $self'owner, $self'content, $self'mintable)) = $self; + throw_unless(132, __tact_address_eq(__gen_Context_get_sender(__tact_context_get()), $self'owner)); + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), ()); +} + +slice $__gen_SampleJetton_owner((int, slice, cell, int) $self) impure { + var (($self'totalSupply, $self'owner, $self'content, $self'mintable)) = $self; + return $self'owner; +} + +_ $__gen_get_owner() method_id(83229) { + var self = __gen_load_SampleJetton(); + var res = $__gen_SampleJetton_owner(self); + return res; +} + +(((int, slice, cell, int)), ()) $__gen_SampleJetton_receive_Mint((int, slice, cell, int) $self, (int) $msg) impure { + var ($self'totalSupply, $self'owner, $self'content, $self'mintable) = $self; + var ($msg'amount) = $msg; + var ($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw) = __tact_context_get(); + ($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_mint($ctx'sender, $msg'amount, $ctx'sender); + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), ()); +} + +((int, slice, cell, int), ()) $__gen_SampleJetton_receive_comment_cd0d986cb1a2f468ae7089f4fc3162c116e5f53fbd11a6839f52dbf5040830b2((int, slice, cell, int) $self) impure { + var ($self'totalSupply, $self'owner, $self'content, $self'mintable) = $self; + var ($ctx'bounced, $ctx'sender, $ctx'value, $ctx'raw) = __tact_context_get(); + ($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_mint($ctx'sender, 1000000000, $ctx'sender); + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), ()); +} + +(((int, slice, cell, int)), ()) $__gen_SampleJetton_receive_TokenUpdateContent((int, slice, cell, int) $self, (cell) $msg) impure { + var ($self'totalSupply, $self'owner, $self'content, $self'mintable) = $self; + var ($msg'content) = $msg; + ($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_requireOwner(); + $self'content = $msg'content; + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), ()); +} + +(((int, slice, cell, int)), ()) $__gen_SampleJetton_receive_TokenBurnNotification((int, slice, cell, int) $self, (int, int, slice, slice) $msg) impure { + var ($self'totalSupply, $self'owner, $self'content, $self'mintable) = $self; + var ($msg'queryId, $msg'amount, $msg'owner, $msg'responseAddress) = $msg; + ($self'totalSupply, $self'owner, $self'content, $self'mintable)~$__gen_SampleJetton_requireWallet($msg'owner); + $self'totalSupply = ($self'totalSupply - $msg'amount); + if ((~ null?($msg'responseAddress))) { + $send((false, $msg'responseAddress, 0, (64 + 2), __gen_writecell_TokenExcesses(($msg'queryId)), null(), null())); + } + return (($self'totalSupply, $self'owner, $self'content, $self'mintable), ()); +} + + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + + ;; Parse incoming message + int op = 0; + if (slice_bits(in_msg) >= 32) { + op = in_msg.preload_uint(32); + } + var cs = in_msg_cell.begin_parse(); + var msg_flags = cs~load_uint(4); + var msg_bounced = ((msg_flags & 1) == 1 ? true : false); + slice msg_sender_addr = cs~load_msg_addr(); + __tact_context = (msg_bounced, msg_sender_addr, msg_value, cs); + + ;; Handle bounced messages + if (msg_bounced) { + return (); + } + + ;; Receive Mint message + if (op == 33240155) { + var self = __gen_load_SampleJetton(); + var msg = in_msg~__gen_read_Mint(); + self~$__gen_SampleJetton_receive_Mint(msg); + __gen_store_SampleJetton(self); + return (); + } + + ;; Receive TokenUpdateContent message + if (op == 201882270) { + var self = __gen_load_SampleJetton(); + var msg = in_msg~__gen_read_TokenUpdateContent(); + self~$__gen_SampleJetton_receive_TokenUpdateContent(msg); + __gen_store_SampleJetton(self); + return (); + } + + ;; Receive TokenBurnNotification message + if (op == 2078119902) { + var self = __gen_load_SampleJetton(); + var msg = in_msg~__gen_read_TokenBurnNotification(); + self~$__gen_SampleJetton_receive_TokenBurnNotification(msg); + __gen_store_SampleJetton(self); + return (); + } + + ;; Text Receivers + if (op == 0) { + var text_op = slice_hash(in_msg); + + ;; Receive "Mint!" message + if (text_op == 0xcd0d986cb1a2f468ae7089f4fc3162c116e5f53fbd11a6839f52dbf5040830b2) { + var self = __gen_load_SampleJetton(); + self~$__gen_SampleJetton_receive_comment_cd0d986cb1a2f468ae7089f4fc3162c116e5f53fbd11a6839f52dbf5040830b2(); + __gen_store_SampleJetton(self); + return (); + } + } + + throw(130); +} + +_ supported_interfaces() method_id { + return ( + "org.ton.introspection.v0"H >> 128, + "org.ton.abi.ipfs.v0"H >> 128, + "org.ton.jetton.master"H >> 128, + "org.ton.ownable"H >> 128 + ); +} + +_ get_abi_ipfs() { + return "ipfs://QmPfyoAvkPUqzx93gq8EBcVccAYXFEbjnqCMrHYtyPUHfE"; +} diff --git a/crypto/func/auto-tests/legacy_tests/tact-examples/maps_MapTestContract.code.fc b/crypto/func/auto-tests/legacy_tests/tact-examples/maps_MapTestContract.code.fc new file mode 100644 index 00000000..833e9d1a --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tact-examples/maps_MapTestContract.code.fc @@ -0,0 +1,603 @@ +#include "stdlib.fc"; + +(cell, int) __tact_dict_delete(cell dict, int key_len, slice index) asm(index dict key_len) "DICTDEL"; + +((cell), ()) __tact_dict_set_ref(cell dict, int key_len, slice index, cell value) asm(value index dict key_len) "DICTSETREF"; + +(slice, int) __tact_dict_get(cell dict, int key_len, slice index) asm(index dict key_len) "DICTGET" "NULLSWAPIFNOT"; + +(cell, int) __tact_dict_get_ref(cell dict, int key_len, slice index) asm(index dict key_len) "DICTGETREF" "NULLSWAPIFNOT"; + +global (int, slice, int, slice) __tact_context; +global cell __tact_context_sys; + +() __tact_verify_address(slice address) inline { + throw_unless(136, address.slice_bits() != 267); +} + +(slice, slice) __tact_load_address(slice cs) inline { + slice raw = cs~load_msg_addr(); + __tact_verify_address(raw); + return (cs, raw); +} + +(cell, ()) __tact_dict_set_int_int(cell d, int kl, int k, int v, int vl) inline { + if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); + } else { + return (idict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); + } +} + +int __tact_dict_get_int_int(cell d, int kl, int k, int vl) inline { + var (r, ok) = idict_get?(d, kl, k); + if (ok) { + return r~load_int(vl); + } else { + return null(); + } +} + +(cell, ()) __tact_dict_set_int_cell(cell d, int kl, int k, cell v) inline { + if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); + } else { + return (idict_set_ref(d, kl, k, v), ()); + } +} + +cell __tact_dict_get_int_cell(cell d, int kl, int k) { + var (r, ok) = idict_get_ref?(d, kl, k); + if (ok) { + return r; + } else { + return null(); + } +} + +(cell, ()) __tact_dict_set_slice_int(cell d, int kl, slice k, int v, int vl) { + if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); + } else { + return (dict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); + } +} + +int __tact_dict_get_slice_int(cell d, int kl, slice k, int vl) inline { + var (r, ok) = __tact_dict_get(d, kl, k); + if (ok) { + return r~load_int(vl); + } else { + return null(); + } +} + +(cell, ()) __tact_dict_set_slice_cell(cell d, int kl, slice k, cell v) inline { + if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); + } else { + return __tact_dict_set_ref(d, kl, k, v); + } +} + +cell __tact_dict_get_slice_cell(cell d, int kl, slice k) inline { + var (r, ok) = __tact_dict_get_ref(d, kl, k); + if (ok) { + return r; + } else { + return null(); + } +} + +forall X0 -> tuple __tact_tuple_create_1((X0) v) asm "1 TUPLE"; + +forall X0 -> (X0) __tact_tuple_destroy_1(tuple v) asm "1 UNTUPLE"; + +(slice, ((int, int))) __gen_read_SetIntMap1(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 1510253336); + var v'key = sc_0~load_int(257); + var v'value = sc_0~load_int(1) ? sc_0~load_int(257) : null(); + return (sc_0, (v'key, v'value)); +} + +(slice, ((int, int))) __gen_read_SetIntMap2(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 1629867766); + var v'key = sc_0~load_int(257); + var v'value = sc_0~load_int(1) ? sc_0~load_int(1) : null(); + return (sc_0, (v'key, v'value)); +} + +(slice, ((int, cell))) __gen_read_SetIntMap3(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 3613954633); + var v'key = sc_0~load_int(257); + var v'value = sc_0~load_int(1) ? sc_0~load_ref() : null(); + return (sc_0, (v'key, v'value)); +} + +builder __gen_write_SomeStruct(builder build_0, (int) v) inline_ref { + var (v'value) = v; + build_0 = build_0.store_int(v'value, 257); + return build_0; +} + +cell __gen_writecell_SomeStruct((int) v) inline_ref { + return __gen_write_SomeStruct(begin_cell(), v).end_cell(); +} + +((int)) __gen_SomeStruct_not_null(tuple v) inline { + throw_if(128, null?(v)); + var (int vvv'value) = __tact_tuple_destroy_1(v); + return (vvv'value); +} + +cell __gen_writecellopt_SomeStruct(tuple v) inline_ref { + if (null?(v)) { + return null(); + } + return __gen_writecell_SomeStruct(__gen_SomeStruct_not_null(v)); +} + +(slice, ((int))) __gen_read_SomeStruct(slice sc_0) inline_ref { + var v'value = sc_0~load_int(257); + return (sc_0, (v'value)); +} + +tuple __gen_SomeStruct_as_optional(((int)) v) inline { + var (v'value) = v; + return __tact_tuple_create_1(v'value); +} + +tuple __gen_readopt_SomeStruct(cell cl) inline_ref { + if (null?(cl)) { + return null(); + } + var sc = cl.begin_parse(); + return __gen_SomeStruct_as_optional(sc~__gen_read_SomeStruct()); +} + +(slice, ((int, tuple))) __gen_read_SetIntMap4(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 383013829); + var v'key = sc_0~load_int(257); + var v'value = sc_0~load_int(1) ? __gen_SomeStruct_as_optional(sc_0~__gen_read_SomeStruct()) : null(); + return (sc_0, (v'key, v'value)); +} + +(slice, ((slice, int))) __gen_read_SetAddrMap1(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 1749966413); + var v'key = sc_0~__tact_load_address(); + var v'value = sc_0~load_int(1) ? sc_0~load_int(257) : null(); + return (sc_0, (v'key, v'value)); +} + +(slice, ((slice, int))) __gen_read_SetAddrMap2(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 624157584); + var v'key = sc_0~__tact_load_address(); + var v'value = sc_0~load_int(1) ? sc_0~load_int(1) : null(); + return (sc_0, (v'key, v'value)); +} + +(slice, ((slice, cell))) __gen_read_SetAddrMap3(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 4276365062); + var v'key = sc_0~__tact_load_address(); + var v'value = sc_0~load_int(1) ? sc_0~load_ref() : null(); + return (sc_0, (v'key, v'value)); +} + +(slice, ((slice, tuple))) __gen_read_SetAddrMap4(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 1683777913); + var v'key = sc_0~__tact_load_address(); + var v'value = sc_0~load_int(1) ? __gen_SomeStruct_as_optional(sc_0~__gen_read_SomeStruct()) : null(); + return (sc_0, (v'key, v'value)); +} + +builder __gen_write_MapTestContract(builder build_0, (cell, cell, cell, cell, cell, cell, cell, cell) v) inline_ref { + var (v'intMap1, v'intMap2, v'intMap3, v'intMap4, v'addrMap1, v'addrMap2, v'addrMap3, v'addrMap4) = v; + build_0 = build_0.store_dict(v'intMap1); + build_0 = build_0.store_dict(v'intMap2); + var build_1 = begin_cell(); + build_1 = build_1.store_dict(v'intMap3); + build_1 = build_1.store_dict(v'intMap4); + build_1 = build_1.store_dict(v'addrMap1); + var build_2 = begin_cell(); + build_2 = build_2.store_dict(v'addrMap2); + build_2 = build_2.store_dict(v'addrMap3); + build_2 = build_2.store_dict(v'addrMap4); + build_1 = store_ref(build_1, build_2.end_cell()); + build_0 = store_ref(build_0, build_1.end_cell()); + return build_0; +} + +(slice, ((cell, cell, cell, cell, cell, cell, cell, cell))) __gen_read_MapTestContract(slice sc_0) inline_ref { + var v'intMap1 = sc_0~load_dict(); + var v'intMap2 = sc_0~load_dict(); + slice sc_1 = sc_0~load_ref().begin_parse(); + var v'intMap3 = sc_1~load_dict(); + var v'intMap4 = sc_1~load_dict(); + var v'addrMap1 = sc_1~load_dict(); + slice sc_2 = sc_1~load_ref().begin_parse(); + var v'addrMap2 = sc_2~load_dict(); + var v'addrMap3 = sc_2~load_dict(); + var v'addrMap4 = sc_2~load_dict(); + return (sc_0, (v'intMap1, v'intMap2, v'intMap3, v'intMap4, v'addrMap1, v'addrMap2, v'addrMap3, v'addrMap4)); +} + +tuple __gen_SomeStruct_to_tuple(((int)) v) { + var (v'value) = v; + return __tact_tuple_create_1(v'value); +} + +tuple __gen_SomeStruct_opt_to_tuple(tuple v) inline { + if (null?(v)) { return null(); } + return __gen_SomeStruct_to_tuple(__gen_SomeStruct_not_null(v)); +} + +tuple __gen_SomeStruct_opt_to_external(tuple v) inline { + var loaded = __gen_SomeStruct_opt_to_tuple(v); + if (null?(loaded)) { + return null(); + } else { + return (loaded); + } +} + +(cell, cell, cell, cell, cell, cell, cell, cell) __gen_load_MapTestContract() inline_ref { + slice sc = get_data().begin_parse(); + __tact_context_sys = sc~load_ref(); + return sc~__gen_read_MapTestContract(); +} + +() __gen_store_MapTestContract((cell, cell, cell, cell, cell, cell, cell, cell) v) impure inline_ref { + builder b = begin_cell(); + b = b.store_ref(__tact_context_sys); + b = __gen_write_MapTestContract(b, v); + set_data(b.end_cell()); +} + +cell $__gen_MapTestContract_intMap1((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'intMap1; +} + +_ $__gen_get_intMap1() method_id(67207) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap1(self); + return res; +} + +int $__gen_MapTestContract_intMap1Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, int $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __tact_dict_get_int_int($self'intMap1, 257, $key, 257); +} + +_ $__gen_get_intMap1Value(int $$key) method_id(103396) { + int $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap1Value(self, $key); + return res; +} + +cell $__gen_MapTestContract_intMap2((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'intMap2; +} + +_ $__gen_get_intMap2() method_id(79588) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap2(self); + return res; +} + +int $__gen_MapTestContract_intMap2Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, int $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __tact_dict_get_int_int($self'intMap2, 257, $key, 1); +} + +_ $__gen_get_intMap2Value(int $$key) method_id(89348) { + int $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap2Value(self, $key); + return res; +} + +cell $__gen_MapTestContract_intMap3((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'intMap3; +} + +_ $__gen_get_intMap3() method_id(75461) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap3(self); + return res; +} + +cell $__gen_MapTestContract_intMap3Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, int $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __tact_dict_get_int_cell($self'intMap3, 257, $key); +} + +_ $__gen_get_intMap3Value(int $$key) method_id(71844) { + int $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap3Value(self, $key); + return res; +} + +cell $__gen_MapTestContract_intMap4((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'intMap4; +} + +_ $__gen_get_intMap4() method_id(87586) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap4(self); + return res; +} + +tuple $__gen_MapTestContract_intMap4Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, int $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __gen_readopt_SomeStruct(__tact_dict_get_int_cell($self'intMap4, 257, $key)); +} + +_ $__gen_get_intMap4Value(int $$key) method_id(119013) { + int $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_intMap4Value(self, $key); + return __gen_SomeStruct_opt_to_external(res); +} + +cell $__gen_MapTestContract_addrMap1((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'addrMap1; +} + +_ $__gen_get_addrMap1() method_id(93537) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap1(self); + return res; +} + +int $__gen_MapTestContract_addrMap1Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, slice $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __tact_dict_get_slice_int($self'addrMap1, 267, $key, 257); +} + +_ $__gen_get_addrMap1Value(slice $$key) method_id(116148) { + slice $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap1Value(self, $key); + return res; +} + +cell $__gen_MapTestContract_addrMap2((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'addrMap2; +} + +_ $__gen_get_addrMap2() method_id(89346) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap2(self); + return res; +} + +int $__gen_MapTestContract_addrMap2Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, slice $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __tact_dict_get_slice_int($self'addrMap2, 267, $key, 1); +} + +_ $__gen_get_addrMap2Value(slice $$key) method_id(68436) { + slice $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap2Value(self, $key); + return res; +} + +cell $__gen_MapTestContract_addrMap3((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'addrMap3; +} + +_ $__gen_get_addrMap3() method_id(85283) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap3(self); + return res; +} + +cell $__gen_MapTestContract_addrMap3Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, slice $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __tact_dict_get_slice_cell($self'addrMap3, 267, $key); +} + +_ $__gen_get_addrMap3Value(slice $$key) method_id(85748) { + slice $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap3Value(self, $key); + return res; +} + +cell $__gen_MapTestContract_addrMap4((cell, cell, cell, cell, cell, cell, cell, cell) $self) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return $self'addrMap4; +} + +_ $__gen_get_addrMap4() method_id(81348) { + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap4(self); + return res; +} + +tuple $__gen_MapTestContract_addrMap4Value((cell, cell, cell, cell, cell, cell, cell, cell) $self, slice $key) impure { + var (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4)) = $self; + return __gen_readopt_SomeStruct(__tact_dict_get_slice_cell($self'addrMap4, 267, $key)); +} + +_ $__gen_get_addrMap4Value(slice $$key) method_id(100021) { + slice $key = $$key; + var self = __gen_load_MapTestContract(); + var res = $__gen_MapTestContract_addrMap4Value(self, $key); + return __gen_SomeStruct_opt_to_external(res); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetIntMap1((cell, cell, cell, cell, cell, cell, cell, cell) $self, (int, int) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'intMap1~__tact_dict_set_int_int(257, $msg'key, $msg'value, 257); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetIntMap2((cell, cell, cell, cell, cell, cell, cell, cell) $self, (int, int) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'intMap2~__tact_dict_set_int_int(257, $msg'key, $msg'value, 1); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetIntMap3((cell, cell, cell, cell, cell, cell, cell, cell) $self, (int, cell) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'intMap3~__tact_dict_set_int_cell(257, $msg'key, $msg'value); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetIntMap4((cell, cell, cell, cell, cell, cell, cell, cell) $self, (int, tuple) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'intMap4~__tact_dict_set_int_cell(257, $msg'key, __gen_writecellopt_SomeStruct($msg'value)); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetAddrMap1((cell, cell, cell, cell, cell, cell, cell, cell) $self, (slice, int) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'addrMap1~__tact_dict_set_slice_int(267, $msg'key, $msg'value, 257); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetAddrMap2((cell, cell, cell, cell, cell, cell, cell, cell) $self, (slice, int) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'addrMap2~__tact_dict_set_slice_int(267, $msg'key, $msg'value, 1); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetAddrMap3((cell, cell, cell, cell, cell, cell, cell, cell) $self, (slice, cell) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'addrMap3~__tact_dict_set_slice_cell(267, $msg'key, $msg'value); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + +(((cell, cell, cell, cell, cell, cell, cell, cell)), ()) $__gen_MapTestContract_receive_SetAddrMap4((cell, cell, cell, cell, cell, cell, cell, cell) $self, (slice, tuple) $msg) impure { + var ($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4) = $self; + var ($msg'key, $msg'value) = $msg; + $self'addrMap4~__tact_dict_set_slice_cell(267, $msg'key, __gen_writecellopt_SomeStruct($msg'value)); + return (($self'intMap1, $self'intMap2, $self'intMap3, $self'intMap4, $self'addrMap1, $self'addrMap2, $self'addrMap3, $self'addrMap4), ()); +} + + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + + ;; Parse incoming message + int op = 0; + if (slice_bits(in_msg) >= 32) { + op = in_msg.preload_uint(32); + } + var cs = in_msg_cell.begin_parse(); + var msg_flags = cs~load_uint(4); + var msg_bounced = ((msg_flags & 1) == 1 ? true : false); + slice msg_sender_addr = cs~load_msg_addr(); + __tact_context = (msg_bounced, msg_sender_addr, msg_value, cs); + + ;; Handle bounced messages + if (msg_bounced) { + return (); + } + + ;; Receive SetIntMap1 message + if (op == 1510253336) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetIntMap1(); + self~$__gen_MapTestContract_receive_SetIntMap1(msg); + __gen_store_MapTestContract(self); + return (); + } + + ;; Receive SetIntMap2 message + if (op == 1629867766) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetIntMap2(); + self~$__gen_MapTestContract_receive_SetIntMap2(msg); + __gen_store_MapTestContract(self); + return (); + } + + ;; Receive SetIntMap3 message + if (op == 3613954633) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetIntMap3(); + self~$__gen_MapTestContract_receive_SetIntMap3(msg); + __gen_store_MapTestContract(self); + return (); + } + + ;; Receive SetIntMap4 message + if (op == 383013829) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetIntMap4(); + self~$__gen_MapTestContract_receive_SetIntMap4(msg); + __gen_store_MapTestContract(self); + return (); + } + + ;; Receive SetAddrMap1 message + if (op == 1749966413) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetAddrMap1(); + self~$__gen_MapTestContract_receive_SetAddrMap1(msg); + __gen_store_MapTestContract(self); + return (); + } + + ;; Receive SetAddrMap2 message + if (op == 624157584) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetAddrMap2(); + self~$__gen_MapTestContract_receive_SetAddrMap2(msg); + __gen_store_MapTestContract(self); + return (); + } + + ;; Receive SetAddrMap3 message + if (op == 4276365062) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetAddrMap3(); + self~$__gen_MapTestContract_receive_SetAddrMap3(msg); + __gen_store_MapTestContract(self); + return (); + } + + ;; Receive SetAddrMap4 message + if (op == 1683777913) { + var self = __gen_load_MapTestContract(); + var msg = in_msg~__gen_read_SetAddrMap4(); + self~$__gen_MapTestContract_receive_SetAddrMap4(msg); + __gen_store_MapTestContract(self); + return (); + } + + throw(130); +} + +_ supported_interfaces() method_id { + return ( + "org.ton.introspection.v0"H >> 128, + "org.ton.abi.ipfs.v0"H >> 128 + ); +} + +_ get_abi_ipfs() { + return "ipfs://QmVmzQSudFJ3B1LEYUvRk86wLNZwXw7ZNgZXj8L2ET5ymw"; +} diff --git a/crypto/func/auto-tests/legacy_tests/tact-examples/stdlib.fc b/crypto/func/auto-tests/legacy_tests/tact-examples/stdlib.fc new file mode 100644 index 00000000..3531608a --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tact-examples/stdlib.fc @@ -0,0 +1,624 @@ +;; Standard library for funC +;; + +{- + # Tuple manipulation primitives + The names and the types are mostly self-explaining. + See [polymorhism with forall](https://ton.org/docs/#/func/functions?id=polymorphism-with-forall) + for more info on the polymorphic functions. + + Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) + and vise versa. +-} + +{- + # Lisp-style lists + + Lists can be represented as nested 2-elements tuples. + Empty list is conventionally represented as TVM `null` value (it can be obtained by calling [null()]). + For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types. +-} + +;;; Adds an element to the beginning of lisp-style list. +forall X -> tuple cons(X head, tuple tail) asm "CONS"; + +;;; Extracts the head and the tail of lisp-style list. +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; + +;;; Extracts the tail and the head of lisp-style list. +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; + +;;; Returns the head of lisp-style list. +forall X -> X car(tuple list) asm "CAR"; + +;;; Returns the tail of lisp-style list. +tuple cdr(tuple list) asm "CDR"; + +;;; Creates tuple with zero elements. +tuple empty_tuple() asm "NIL"; + +;;; Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` +;;; is of length at most 255. Otherwise throws a type check exception. +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; + +;;; Creates a tuple of length one with given argument as element. +forall X -> [X] single(X x) asm "SINGLE"; + +;;; Unpacks a tuple of length one +forall X -> X unsingle([X] t) asm "UNSINGLE"; + +;;; Creates a tuple of length two with given arguments as elements. +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; + +;;; Unpacks a tuple of length two +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; + +;;; Creates a tuple of length three with given arguments as elements. +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; + +;;; Unpacks a tuple of length three +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; + +;;; Creates a tuple of length four with given arguments as elements. +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; + +;;; Unpacks a tuple of length four +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; + +;;; Returns the first element of a tuple (with unknown element types). +forall X -> X first(tuple t) asm "FIRST"; + +;;; Returns the second element of a tuple (with unknown element types). +forall X -> X second(tuple t) asm "SECOND"; + +;;; Returns the third element of a tuple (with unknown element types). +forall X -> X third(tuple t) asm "THIRD"; + +;;; Returns the fourth element of a tuple (with unknown element types). +forall X -> X fourth(tuple t) asm "3 INDEX"; + +;;; Returns the first element of a pair tuple. +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; + +;;; Returns the second element of a pair tuple. +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; + +;;; Returns the first element of a triple tuple. +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; + +;;; Returns the second element of a triple tuple. +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; + +;;; Returns the third element of a triple tuple. +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; + + +;;; Push null element (casted to given type) +;;; By the TVM type `Null` FunC represents absence of a value of some atomic type. +;;; So `null` can actually have any atomic type. +forall X -> X null() asm "PUSHNULL"; + +;;; Moves a variable [x] to the top of the stack +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + + + +;;; Returns the current Unix time as an Integer +int now() asm "NOW"; + +;;; Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. +;;; If necessary, it can be parsed further using primitives such as [parse_std_addr]. +slice my_address() asm "MYADDR"; + +;;; Returns the balance of the smart contract as a tuple consisting of an int +;;; (balance in nanotoncoins) and a `cell` +;;; (a dictionary with 32-bit keys representing the balance of "extra currencies") +;;; at the start of Computation Phase. +;;; Note that RAW primitives such as [send_raw_message] do not update this field. +[int, cell] get_balance() asm "BALANCE"; + +;;; Returns the logical time of the current transaction. +int cur_lt() asm "LTIME"; + +;;; Returns the starting logical time of the current block. +int block_lt() asm "BLOCKLT"; + +;;; Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. +;;; Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. +int cell_hash(cell c) asm "HASHCU"; + +;;; Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. +;;; The result is the same as if an ordinary cell containing only data and references from `s` had been created +;;; and its hash computed by [cell_hash]. +int slice_hash(slice s) asm "HASHSU"; + +;;; Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, +;;; throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. +int string_hash(slice s) asm "SHA256U"; + +{- + # Signature checks +-} + +;;; Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) +;;; using [public_key] (also represented by a 256-bit unsigned integer). +;;; The signature must contain at least 512 data bits; only the first 512 bits are used. +;;; The result is `−1` if the signature is valid, `0` otherwise. +;;; Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. +;;; That is, if [hash] is computed as the hash of some data, these data are hashed twice, +;;; the second hashing occurring inside `CHKSIGNS`. +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; + +;;; Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, +;;; similarly to [check_signature]. +;;; If the bit length of [data] is not divisible by eight, throws a cell underflow exception. +;;; The verification of Ed25519 signatures is the standard one, +;;; with sha256 used to reduce [data] to the 256-bit number that is actually signed. +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +{--- + # Computation of boc size + The primitives below may be useful for computing storage fees of user-provided data. +-} + +;;; Returns `(x, y, z, -1)` or `(null, null, null, 0)`. +;;; Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` +;;; in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account +;;; the identification of equal cells. +;;; The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, +;;; with a hash table of visited cell hashes used to prevent visits of already-visited cells. +;;; The total count of visited cells `x` cannot exceed non-negative [max_cells]; +;;; otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and +;;; a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; + +;;; Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. +;;; The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; +;;; however, the data bits and the cell references of [s] are accounted for in `y` and `z`. +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; + +;;; A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (8) on failure. +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; Throws an exception with exit_code excno if cond is not 0 (commented since implemented in compilator) +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +{-- + # Debug primitives + Only works for local TVM execution with debug level verbosity +-} +;;; Dumps the stack (at most the top 255 values) and shows the total stack depth. +() dump_stack() impure asm "DUMPSTK"; + +{- + # Persistent storage save and load +-} + +;;; Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. +cell get_data() asm "c4 PUSH"; + +;;; Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. +() set_data(cell c) impure asm "c4 POP"; + +{- + # Continuation primitives +-} +;;; Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. +;;; The primitive returns the current value of `c3`. +cont get_c3() impure asm "c3 PUSH"; + +;;; Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. +;;; Note that after execution of this primitive the current code +;;; (and the stack of recursive function calls) won't change, +;;; but any other function call will use a function from the new code. +() set_c3(cont c) impure asm "c3 POP"; + +;;; Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. +cont bless(slice s) impure asm "BLESS"; + +{--- + # Gas related primitives +-} + +;;; Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, +;;; decreasing the value of `gr` by `gc` in the process. +;;; In other words, the current smart contract agrees to buy some gas to finish the current transaction. +;;; This action is required to process external messages, which bring no value (hence no gas) with themselves. +;;; +;;; For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). +() accept_message() impure asm "ACCEPT"; + +;;; Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. +;;; If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, +;;; an (unhandled) out of gas exception is thrown before setting new gas limits. +;;; Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; + +;;; Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) +;;; so that the current execution is considered “successful” with the saved values even if an exception +;;; in Computation Phase is thrown later. +() commit() impure asm "COMMIT"; + +;;; Not implemented +;;() buy_gas(int gram) impure asm "BUYGAS"; + +;;; Computes the amount of gas that can be bought for `amount` nanoTONs, +;;; and sets `gl` accordingly in the same way as [set_gas_limit]. +() buy_gas(int amount) impure asm "BUYGAS"; + +;;; Computes the minimum of two integers [x] and [y]. +int min(int x, int y) asm "MIN"; + +;;; Computes the maximum of two integers [x] and [y]. +int max(int x, int y) asm "MAX"; + +;;; Sorts two integers. +(int, int) minmax(int x, int y) asm "MINMAX"; + +;;; Computes the absolute value of an integer [x]. +int abs(int x) asm "ABS"; + +{- + # Slice primitives + + It is said that a primitive _loads_ some data, + if it returns the data and the remainder of the slice + (so it can also be used as [modifying method](https://ton.org/docs/#/func/statements?id=modifying-methods)). + + It is said that a primitive _preloads_ some data, if it returns only the data + (it can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods)). + + Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice. +-} + + +;;; Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, +;;; or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) +;;; which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. +slice begin_parse(cell c) asm "CTOS"; + +;;; Checks if [s] is empty. If not, throws an exception. +() end_parse(slice s) impure asm "ENDS"; + +;;; Loads the first reference from the slice. +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; + +;;; Preloads the first reference from the slice. +cell preload_ref(slice s) asm "PLDREF"; + + {- Functions below are commented because are implemented on compilator level for optimisation -} + +;;; Loads a signed [len]-bit integer from a slice [s]. +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; + +;;; Loads an unsigned [len]-bit integer from a slice [s]. +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; + +;;; Preloads a signed [len]-bit integer from a slice [s]. +;; int preload_int(slice s, int len) asm "PLDIX"; + +;;; Preloads an unsigned [len]-bit integer from a slice [s]. +;; int preload_uint(slice s, int len) asm "PLDUX"; + +;;; Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; + +;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; + +;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^128 - 1`). +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; + +;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; + +;;; Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice first_bits(slice s, int len) asm "SDCUTFIRST"; + +;;; Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; + +;;; Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice slice_last(slice s, int len) asm "SDCUTLAST"; + +;;; Loads a dictionary `D` (HashMapE) from `slice` [s]. +;;; (returns `null` if `nothing` constructor is used). +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; + +;;; Preloads a dictionary `D` from `slice` [s]. +cell preload_dict(slice s) asm "PLDDICT"; + +;;; Loads a dictionary as [load_dict], but returns only the remainder of the slice. +slice skip_dict(slice s) asm "SKIPDICT"; + +;;; Loads (Maybe ^Cell) from `slice` [s]. +;;; In other words loads 1 bit and if it is true +;;; loads first ref and return it with slice remainder +;;; otherwise returns `null` and slice remainder +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; + +;;; Preloads (Maybe ^Cell) from `slice` [s]. +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; + + +;;; Returns the depth of `cell` [c]. +;;; If [c] has no references, then return `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. +;;; If [c] is a `null` instead of a cell, returns zero. +int cell_depth(cell c) asm "CDEPTH"; + + +{- + # Slice size primitives +-} + +;;; Returns the number of references in `slice` [s]. +int slice_refs(slice s) asm "SREFS"; + +;;; Returns the number of data bits in `slice` [s]. +int slice_bits(slice s) asm "SBITS"; + +;;; Returns both the number of data bits and the number of references in `slice` [s]. +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; + +;;; Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). +int slice_empty?(slice s) asm "SEMPTY"; + +;;; Checks whether `slice` [s] has no bits of data. +int slice_data_empty?(slice s) asm "SDEMPTY"; + +;;; Checks whether `slice` [s] has no references. +int slice_refs_empty?(slice s) asm "SREMPTY"; + +;;; Returns the depth of `slice` [s]. +;;; If [s] has no references, then returns `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. +int slice_depth(slice s) asm "SDEPTH"; + +{- + # Builder size primitives +-} + +;;; Returns the number of cell references already stored in `builder` [b] +int builder_refs(builder b) asm "BREFS"; + +;;; Returns the number of data bits already stored in `builder` [b]. +int builder_bits(builder b) asm "BBITS"; + +;;; Returns the depth of `builder` [b]. +;;; If no cell references are stored in [b], then returns 0; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. +int builder_depth(builder b) asm "BDEPTH"; + +{- + # Builder primitives + It is said that a primitive _stores_ a value `x` into a builder `b` + if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. + It can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods). + + All the primitives below first check whether there is enough space in the `builder`, + and only then check the range of the value being serialized. +-} + +;;; Creates a new empty `builder`. +builder begin_cell() asm "NEWC"; + +;;; Converts a `builder` into an ordinary `cell`. +cell end_cell(builder b) asm "ENDC"; + +;;; Stores a reference to `cell` [c] into `builder` [b]. +builder store_ref(builder b, cell c) asm(c b) "STREF"; + +;;; Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; + +;;; Stores a signed [len]-bit integer `x` into `b` for` 0 ≤ len ≤ 257`. +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; + + +;;; Stores `slice` [s] into `builder` [b] +builder store_slice(builder b, slice s) asm "STSLICER"; + +;;; Stores (serializes) an integer [x] in the range `0..2^128 − 1` into `builder` [b]. +;;; The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, +;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, +;;; followed by an `8l`-bit unsigned big-endian representation of [x]. +;;; If [x] does not belong to the supported range, a range check exception is thrown. +;;; +;;; Store amounts of TonCoins to the builder as VarUInteger 16 +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_coins(builder b, int x) asm "STGRAMS"; + +;;; Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. +;;; In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +;;; Stores (Maybe ^Cell) to builder: +;;; if cell is null store 1 zero bit +;;; otherwise store 1 true bit and ref to cell +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + + +{- + # Address manipulation primitives + The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: + ```TL-B + addr_none$00 = MsgAddressExt; + addr_extern$01 len:(## 8) external_address:(bits len) + = MsgAddressExt; + anycast_info$_ depth:(#<= 30) { depth >= 1 } + rewrite_pfx:(bits depth) = Anycast; + addr_std$10 anycast:(Maybe Anycast) + workchain_id:int8 address:bits256 = MsgAddressInt; + addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) + workchain_id:int32 address:(bits addr_len) = MsgAddressInt; + _ _:MsgAddressInt = MsgAddress; + _ _:MsgAddressExt = MsgAddress; + + int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddress dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ``` + A deserialized `MsgAddress` is represented by a tuple `t` as follows: + + - `addr_none` is represented by `t = (0)`, + i.e., a tuple containing exactly one integer equal to zero. + - `addr_extern` is represented by `t = (1, s)`, + where slice `s` contains the field `external_address`. In other words, ` + t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`. + - `addr_std` is represented by `t = (2, u, x, s)`, + where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present). + Next, integer `x` is the `workchain_id`, and slice `s` contains the address. + - `addr_var` is represented by `t = (3, u, x, s)`, + where `u`, `x`, and `s` have the same meaning as for `addr_std`. +-} + +;;; Loads from slice [s] the only prefix that is a valid `MsgAddress`, +;;; and returns both this prefix `s'` and the remainder `s''` of [s] as slices. +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; + +;;; Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. +;;; If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. +tuple parse_addr(slice s) asm "PARSEMSGADDR"; + +;;; Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), +;;; applies rewriting from the anycast (if present) to the same-length prefix of the address, +;;; and returns both the workchain and the 256-bit address as integers. +;;; If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, +;;; throws a cell deserialization exception. +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; + +;;; A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s], +;;; even if it is not exactly 256 bit long (represented by a `msg_addr_var`). +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +{- + # Dictionary primitives +-} + + +;;; Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; + +;;; Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; + +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; + +;;; Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL +cell new_dict() asm "NEWDICT"; +;;; Checks whether a dictionary is empty. Equivalent to cell_null?. +int dict_empty?(cell c) asm "DICTEMPTY"; + + +{- Prefix dictionary primitives -} +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +;;; Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. +cell config_param(int x) asm "CONFIGOPTPARAM"; +;;; Checks whether c is a null. Note, that FunC also has polymorphic null? built-in. +int cell_null?(cell c) asm "ISNULL"; + +;;; Creates an output action which would reserve exactly amount nanotoncoins (if mode = 0), at most amount nanotoncoins (if mode = 2), or all but amount nanotoncoins (if mode = 1 or mode = 3), from the remaining balance of the account. It is roughly equivalent to creating an outbound message carrying amount nanotoncoins (or b − amount nanotoncoins, where b is the remaining balance) to oneself, so that the subsequent output actions would not be able to spend more money than the remainder. Bit +2 in mode means that the external action does not fail if the specified amount cannot be reserved; instead, all remaining balance is reserved. Bit +8 in mode means `amount <- -amount` before performing any further actions. Bit +4 in mode means that amount is increased by the original balance of the current account (before the compute phase), including all extra currencies, before performing any other checks and actions. Currently, amount must be a non-negative integer, and mode must be in the range 0..15. +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +;;; Similar to raw_reserve, but also accepts a dictionary extra_amount (represented by a cell or null) with extra currencies. In this way currencies other than TonCoin can be reserved. +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +;;; Sends a raw message contained in msg, which should contain a correctly serialized object Message X, with the only exception that the source address is allowed to have dummy value addr_none (to be automatically replaced with the current smart contract address), and ihr_fee, fwd_fee, created_lt and created_at fields can have arbitrary values (to be rewritten with correct values during the action phase of the current transaction). Integer parameter mode contains the flags. Currently mode = 0 is used for ordinary messages; mode = 128 is used for messages that are to carry all the remaining balance of the current smart contract (instead of the value originally indicated in the message); mode = 64 is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message (if bit 0 is not set, the gas fees are deducted from this amount); mode' = mode + 1 means that the sender wants to pay transfer fees separately; mode' = mode + 2 means that any errors arising while processing this message during the action phase should be ignored. Finally, mode' = mode + 32 means that the current account must be destroyed if its resulting balance is zero. This flag is usually employed together with +128. +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +;;; Creates an output action that would change this smart contract code to that given by cell new_code. Notice that this change will take effect only after the successful termination of the current run of the smart contract +() set_code(cell new_code) impure asm "SETCODE"; + +;;; Generates a new pseudo-random unsigned 256-bit integer x. The algorithm is as follows: if r is the old value of the random seed, considered as a 32-byte array (by constructing the big-endian representation of an unsigned 256-bit integer), then its sha512(r) is computed; the first 32 bytes of this hash are stored as the new value r' of the random seed, and the remaining 32 bytes are returned as the next random value x. +int random() impure asm "RANDU256"; +;;; Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. +int rand(int range) impure asm "RAND"; +;;; Returns the current random seed as an unsigned 256-bit Integer. +int get_seed() impure asm "RANDSEED"; +;;; Sets the random seed to unsigned 256-bit seed. +() set_seed(int) impure asm "SETRAND"; +;;; Mixes unsigned 256-bit integer x into the random seed r by setting the random seed to sha256 of the concatenation of two 32-byte strings: the first with the big-endian representation of the old seed r, and the second with the big-endian representation of x. +() randomize(int x) impure asm "ADDRAND"; +;;; Equivalent to randomize(cur_lt());. +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +;;; Checks whether the data parts of two slices coinside +int equal_slice_bits (slice a, slice b) asm "SDEQ"; + +;;; Concatenates two builders +builder store_builder(builder to, builder from) asm "STBR"; diff --git a/crypto/func/auto-tests/legacy_tests/tact-examples/treasure_Treasure.code.fc b/crypto/func/auto-tests/legacy_tests/tact-examples/treasure_Treasure.code.fc new file mode 100644 index 00000000..e4fc974f --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tact-examples/treasure_Treasure.code.fc @@ -0,0 +1,229 @@ +#include "stdlib.fc"; + +forall X -> X __tact_not_null(X x) { throw_if(128, null?(x)); return x; } + +global (int, slice, int, slice) __tact_context; +global cell __tact_context_sys; + +(int, slice, int, slice) __tact_context_get() inline { return __tact_context; } + +() __tact_verify_address(slice address) inline { + throw_unless(136, address.slice_bits() != 267); +} + +builder __tact_store_bool(builder b, int v) inline { + b = b.store_int(v, 1); + return b; +} + +(slice, slice) __tact_load_address(slice cs) inline { + slice raw = cs~load_msg_addr(); + __tact_verify_address(raw); + return (cs, raw); +} + +builder __tact_store_address(builder b, slice address) inline { + __tact_verify_address(address); + b = b.store_slice(address); + return b; +} + +int __tact_address_eq(slice a, slice b) inline { + return equal_slice_bits(a, b); +} + +(slice, ((slice))) __gen_read_ChangeOwner(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 256331011); + var v'newOwner = sc_0~__tact_load_address(); + return (sc_0, (v'newOwner)); +} + +(slice, ((int, int))) __gen_read_Withdraw(slice sc_0) inline_ref { + throw_unless(129, sc_0~load_uint(32) == 1672521544); + var v'amount = sc_0~load_coins(); + var v'mode = sc_0~load_uint(8); + return (sc_0, (v'amount, v'mode)); +} + +builder __gen_write_Treasure(builder build_0, (slice) v) inline_ref { + var (v'owner) = v; + build_0 = __tact_store_address(build_0, v'owner); + return build_0; +} + +(slice, ((slice))) __gen_read_Treasure(slice sc_0) inline_ref { + var v'owner = sc_0~__tact_load_address(); + return (sc_0, (v'owner)); +} + +_ __gen_Context_get_sender((int, slice, int, slice) v) inline { + var (v'bounced, v'sender, v'value, v'raw) = v; + return v'sender; +} + +(slice) __gen_load_Treasure() inline_ref { + slice sc = get_data().begin_parse(); + __tact_context_sys = sc~load_ref(); + return sc~__gen_read_Treasure(); +} + +() __gen_store_Treasure((slice) v) impure inline_ref { + builder b = begin_cell(); + b = b.store_ref(__tact_context_sys); + b = __gen_write_Treasure(b, v); + set_data(b.end_cell()); +} + +() $send((int, slice, int, int, cell, cell, cell) $params) impure { + var (($params'bounce, $params'to, $params'value, $params'mode, $params'body, $params'code, $params'data)) = $params; + builder $b = begin_cell(); + $b = store_int($b, 1, 2); + $b = __tact_store_bool($b, $params'bounce); + $b = store_int($b, 0, 3); + $b = __tact_store_address($b, $params'to); + $b = store_coins($b, $params'value); + $b = store_int($b, 0, ((((1 + 4) + 4) + 64) + 32)); + if (((~ null?($params'code)) | (~ null?($params'data)))) { + $b = __tact_store_bool($b, true); + builder $bc = begin_cell(); + $bc = __tact_store_bool($bc, false); + $bc = __tact_store_bool($bc, false); + if ((~ null?($params'code))) { + $bc = __tact_store_bool($bc, true); + $bc = store_ref($bc, __tact_not_null($params'code)); + } else { + $bc = __tact_store_bool($bc, false); + } + if ((~ null?($params'data))) { + $bc = __tact_store_bool($bc, true); + $bc = store_ref($bc, __tact_not_null($params'data)); + } else { + $bc = __tact_store_bool($bc, false); + } + $bc = __tact_store_bool($bc, false); + $b = __tact_store_bool($b, true); + $b = store_ref($b, end_cell($bc)); + } else { + $b = __tact_store_bool($b, false); + } + cell $body = $params'body; + if ((~ null?($body))) { + $b = __tact_store_bool($b, true); + $b = store_ref($b, __tact_not_null($body)); + } else { + $b = __tact_store_bool($b, false); + } + cell $c = end_cell($b); + send_raw_message($c, $params'mode); +} + +((slice), ()) $__gen_Treasure_requireOwner((slice) $self) impure { + var (($self'owner)) = $self; + throw_unless(132, __tact_address_eq(__gen_Context_get_sender(__tact_context_get()), $self'owner)); + return (($self'owner), ()); +} + +((slice), ()) $__gen_Treasure_doWithdraw((slice) $self, int $amount, int $mode) impure { + var (($self'owner)) = $self; + ($self'owner)~$__gen_Treasure_requireOwner(); + $send((true, $self'owner, $amount, $mode, end_cell(begin_cell()), null(), null())); + return (($self'owner), ()); +} + +slice $__gen_Treasure_owner((slice) $self) impure { + var (($self'owner)) = $self; + return $self'owner; +} + +_ $__gen_get_owner() method_id(83229) { + var self = __gen_load_Treasure(); + var res = $__gen_Treasure_owner(self); + return res; +} + +(((slice)), ()) $__gen_Treasure_receive_Withdraw((slice) $self, (int, int) $msg) impure { + var ($self'owner) = $self; + var ($msg'amount, $msg'mode) = $msg; + ($self'owner)~$__gen_Treasure_doWithdraw($msg'amount, $msg'mode); + return (($self'owner), ()); +} + +((slice), ()) $__gen_Treasure_receive_comment_986c2ba124bb9287eb4a0bd8d3104e1c0067a3c93952d889c74d08185bd30d4d((slice) $self) impure { + var ($self'owner) = $self; + ($self'owner)~$__gen_Treasure_doWithdraw(0, (32 + 128)); + return (($self'owner), ()); +} + +(((slice)), ()) $__gen_Treasure_receive_ChangeOwner((slice) $self, (slice) $msg) impure { + var ($self'owner) = $self; + var ($msg'newOwner) = $msg; + ($self'owner)~$__gen_Treasure_requireOwner(); + $self'owner = $msg'newOwner; + return (($self'owner), ()); +} + + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + + ;; Parse incoming message + int op = 0; + if (slice_bits(in_msg) >= 32) { + op = in_msg.preload_uint(32); + } + var cs = in_msg_cell.begin_parse(); + var msg_flags = cs~load_uint(4); + var msg_bounced = ((msg_flags & 1) == 1 ? true : false); + slice msg_sender_addr = cs~load_msg_addr(); + __tact_context = (msg_bounced, msg_sender_addr, msg_value, cs); + + ;; Handle bounced messages + if (msg_bounced) { + return (); + } + + ;; Receive Withdraw message + if (op == 1672521544) { + var self = __gen_load_Treasure(); + var msg = in_msg~__gen_read_Withdraw(); + self~$__gen_Treasure_receive_Withdraw(msg); + __gen_store_Treasure(self); + return (); + } + + ;; Receive ChangeOwner message + if (op == 256331011) { + var self = __gen_load_Treasure(); + var msg = in_msg~__gen_read_ChangeOwner(); + self~$__gen_Treasure_receive_ChangeOwner(msg); + __gen_store_Treasure(self); + return (); + } + + ;; Text Receivers + if (op == 0) { + var text_op = slice_hash(in_msg); + + ;; Receive "Destroy" message + if (text_op == 0x986c2ba124bb9287eb4a0bd8d3104e1c0067a3c93952d889c74d08185bd30d4d) { + var self = __gen_load_Treasure(); + self~$__gen_Treasure_receive_comment_986c2ba124bb9287eb4a0bd8d3104e1c0067a3c93952d889c74d08185bd30d4d(); + __gen_store_Treasure(self); + return (); + } + } + + throw(130); +} + +_ supported_interfaces() method_id { + return ( + "org.ton.introspection.v0"H >> 128, + "org.ton.abi.ipfs.v0"H >> 128, + "org.ton.ownable.transferable"H >> 128, + "org.ton.ownable"H >> 128 + ); +} + +_ get_abi_ipfs() { + return "ipfs://QmSZriPPLDUQWqjYmMRWAqKkhCeq32L339Q2PQrBaYMAqT"; +} diff --git a/crypto/func/auto-tests/legacy_tests/tele-nft-item/common.fc b/crypto/func/auto-tests/legacy_tests/tele-nft-item/common.fc new file mode 100644 index 00000000..4527fe3d --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tele-nft-item/common.fc @@ -0,0 +1,416 @@ +const int one_ton = 1000000000; +const int dns_next_resolver_prefix = 0xba93; ;; dns_next_resolver prefix - https://github.com/ton-blockchain/ton/blob/7e3df93ca2ab336716a230fceb1726d81bac0a06/crypto/block/block.tlb#L819 + +const int op::fill_up = 0x370fec51; +const int op::outbid_notification = 0x557cea20; +const int op::change_dns_record = 0x4eb1f0f9; +const int op::dns_balance_release = 0x4ed14b65; + +const int op::telemint_msg_deploy = 0x4637289a; +const int op::teleitem_msg_deploy = 0x299a3e15; +const int op::teleitem_start_auction = 0x487a8e81; +const int op::teleitem_cancel_auction = 0x371638ae; +const int op::teleitem_bid_info = 0x38127de1; +const int op::teleitem_return_bid = 0xa43227e1; +const int op::teleitem_ok = 0xa37a0983; + +const int op::nft_cmd_transfer = 0x5fcc3d14; +const int op::nft_cmd_get_static_data = 0x2fcb26a2; +const int op::nft_cmd_edit_content = 0x1a0b9d51; +const int op::nft_answer_ownership_assigned = 0x05138d91; +const int op::nft_answer_excesses = 0xd53276db; + +const int op::ownership_assigned = 0x05138d91; +const int op::excesses = 0xd53276db; +const int op::get_static_data = 0x2fcb26a2; +const int op::report_static_data = 0x8b771735; +const int op::get_royalty_params = 0x693d3950; +const int op::report_royalty_params = 0xa8cb00ad; + +const int err::invalid_length = 201; +const int err::invalid_signature = 202; +const int err::wrong_subwallet_id = 203; +const int err::not_yet_valid_signature = 204; +const int err::expired_signature = 205; +const int err::not_enough_funds = 206; +const int err::wrong_topup_comment = 207; +const int err::unknown_op = 208; +const int err::uninited = 210; +const int err::too_small_stake = 211; +const int err::expected_onchain_content = 212; +const int err::forbidden_not_deploy = 213; +const int err::forbidden_not_stake = 214; +const int err::forbidden_topup = 215; +const int err::forbidden_transfer = 216; +const int err::forbidden_change_dns = 217; +const int err::forbidden_touch = 218; +const int err::no_auction = 219; +const int err::forbidden_auction = 220; +const int err::already_has_stakes = 221; +const int err::auction_already_started = 222; +const int err::invalid_auction_config = 223; +const int err::incorrect_workchain = 333; +const int err::no_first_zero_byte = 413; +const int err::bad_subdomain_length = 70; + +const int min_tons_for_storage = one_ton; +const int workchain = 0; + +int equal_slices(slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; +builder store_builder(builder to, builder from) asm "STBR"; +slice zero_address() asm "b{00} PUSHSLICE"; +(slice, int) skip_first_zero_byte?(slice cs) asm "x{00} SDBEGINSQ"; + +() force_chain(slice addr) impure inline { + (int wc, _) = parse_std_addr(addr); + throw_unless(err::incorrect_workchain, wc == workchain); +} + + +;; "ton\0test\0" -> "ton" +int get_top_domain_bits(slice domain) inline { + int i = -8; + int char = 1; + while (char) { + i += 8; + char = domain~load_uint(8); ;; we do not check domain.length because it MUST contains \0 character + } + throw_unless(201, i); ;; should not start with \0 + return i; +} + +_ load_text(slice cs) inline { + int len = cs~load_uint(8); + slice text = cs~load_bits(len * 8); + return (cs, text); +} + +_ load_text_ref(slice cs) inline { + slice text_cs = cs~load_ref().begin_parse(); + slice text = text_cs~load_text(); + return (cs, text); +} + +builder store_text(builder b, slice text) inline { + int len = slice_bits(text); + (int bytes, int rem) = len /% 8; + throw_if(err::invalid_length, rem); + return b.store_uint(bytes, 8) + .store_slice(text); +} + +(slice, slice) unpack_token_info(cell c) inline { + slice cs = c.begin_parse(); + var res = ( + cs~load_text(), + cs~load_text() + ); + cs.end_parse(); + return res; +} + +cell pack_token_info(slice name, slice domain) { + return begin_cell() + .store_text(name) + .store_text(domain) + .end_cell(); +} + +cell pack_state_init(cell code, cell data) inline { + return begin_cell() + .store_uint(0, 2) + .store_maybe_ref(code) + .store_maybe_ref(data) + .store_uint(0, 1) + .end_cell(); +} + +cell pack_init_int_message(slice dest, cell state_init, cell body) inline { + return begin_cell() + .store_uint(0x18, 6) ;; 011000 tag=0, ihr_disabled=1, allow_bounces=1, bounced=0, add_none + .store_slice(dest) + .store_grams(0) ;; grams + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(body) + .end_cell(); +} + +() send_msg(slice to_address, int amount, int op, int query_id, builder payload, int mode) impure inline { + var msg = begin_cell() + .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000 + .store_slice(to_address) + .store_grams(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op, 32) + .store_uint(query_id, 64); + + ifnot (builder_null?(payload)) { + msg = msg.store_builder(payload); + } + + send_raw_message(msg.end_cell(), mode); +} + +slice calculate_address(int wc, cell state_init) inline { + slice res = begin_cell() + .store_uint(4, 3) + .store_int(wc, 8) + .store_uint(cell_hash(state_init), 256) + .end_cell() + .begin_parse(); + return res; +} + +(int, slice) unpack_item_config(cell c) inline { + slice cs = c.begin_parse(); + var res = ( + cs~load_uint(256), + cs~load_msg_addr() + ); + cs.end_parse(); + return res; +} + +cell pack_item_config(int item_index, slice collection_address) inline { + return begin_cell() + .store_uint(item_index, 256) + .store_slice(collection_address) + .end_cell(); +} + +(cell, cell) unpack_item_data() inline { + var cs = get_data().begin_parse(); + var res = (cs~load_ref(), cs~load_maybe_ref()); + cs.end_parse(); + return res; +} + +cell pack_nft_royalty_params(int numerator, int denominator, slice destination) inline { + return begin_cell() + .store_uint(numerator, 16) + .store_uint(denominator, 16) + .store_slice(destination) + .end_cell(); +} + +(int, int, slice) unpack_nft_royalty_params(cell c) inline { + var cs = c.begin_parse(); + var res = ( + cs~load_uint(16), + cs~load_uint(16), + cs~load_msg_addr() + ); + cs.end_parse(); + return res; +} + +cell pack_item_data(cell config, cell state) inline { + return begin_cell() + .store_ref(config) + .store_maybe_ref(state) + .end_cell(); +} + +cell pack_item_content(cell nft_content, cell dns, cell token_info) inline { + return begin_cell() + .store_ref(nft_content) + .store_dict(dns) + .store_ref(token_info) + .end_cell(); +} + +(cell, cell, cell) unpack_item_content(cell c) inline { + var cs = c.begin_parse(); + var res = ( + cs~load_ref(), + cs~load_dict(), + cs~load_ref() + ); + cs.end_parse(); + return res; +} + +(slice, cell, cell, cell) unpack_item_state(cell c) inline { + var cs = c.begin_parse(); + var res = ( + cs~load_msg_addr(), + cs~load_ref(), + cs~load_maybe_ref(), + cs~load_ref() + ); + cs.end_parse(); + return res; +} + +cell pack_item_state(slice owner_address, cell content, cell auction, cell royalty_params) inline { + return begin_cell() + .store_slice(owner_address) + .store_ref(content) + .store_maybe_ref(auction) + .store_ref(royalty_params) + .end_cell(); +} + +_ save_item_data(config, state) impure inline { + set_data(pack_item_data(config, state)); +} + +cell pack_item_state_init(int item_index, cell item_code) inline { + var item_config = pack_item_config(item_index, my_address()); + var item_data = pack_item_data(item_config, null()); + return pack_state_init(item_code, item_data); +} + +cell pack_teleitem_msg_deploy(slice sender_address, int bid, cell info, cell content, cell auction_config, cell royalty_params) inline { + return begin_cell() + .store_uint(op::teleitem_msg_deploy, 32) + .store_slice(sender_address) + .store_grams(bid) + .store_ref(info) + .store_ref(content) + .store_ref(auction_config) + .store_ref(royalty_params) + .end_cell(); +} + +(slice, int, cell, cell, cell, cell) unpack_teleitem_msg_deploy(slice cs) inline { + return (cs~load_msg_addr(), + cs~load_grams(), + cs~load_ref(), + cs~load_ref(), + cs~load_ref(), + cs~load_ref()); +} + +(int, int, int, cell, cell, slice, cell) unpack_collection_data() inline { + var cs = get_data().begin_parse(); + var res = ( + cs~load_int(1), ;; touched + cs~load_uint(32), ;; subwallet_id + cs~load_uint(256), ;; owner_key + cs~load_ref(), ;; content + cs~load_ref(), ;; item_code + cs~load_text_ref(), ;; full_domain + cs~load_ref() ;; royalty_params + ); + cs.end_parse(); + return res; +} + +_ save_collection_data(int touched, int subwallet_id, int owner_key, cell content, cell item_code, slice full_domain, cell royalty_params) impure inline { + cell data = begin_cell() + .store_int(touched, 1) + .store_uint(subwallet_id, 32) + .store_uint(owner_key, 256) + .store_ref(content) + .store_ref(item_code) + .store_ref(begin_cell().store_text(full_domain).end_cell()) + .store_ref(royalty_params) + .end_cell(); + set_data(data); +} + +_ unpack_signed_cmd(slice cs) inline { + return ( + cs~load_bits(512), ;; signature + slice_hash(cs), ;; hash + cs~load_uint(32), ;; subwallet_id + cs~load_uint(32), ;; valid_since + cs~load_uint(32), ;; valid_till + cs ;; cmd + ); +} + +_ unpack_deploy_msg(slice cs) inline { + var res = ( + cs~load_text(), ;; token_name + cs~load_ref(), ;; content + cs~load_ref(), ;; auction_config + cs~load_maybe_ref() ;; royalty + ); + cs.end_parse(); + return res; +} + +;;teleitem_last_bid bidder_address:MsgAddressInt bid:Grams bid_ts:uint32 = TeleitemLastBid; +(slice, int, int) unpack_last_bid(cell c) inline { + slice cs = c.begin_parse(); + var res = ( + cs~load_msg_addr(), ;; bidder_address + cs~load_grams(), ;; bid + cs~load_uint(32) ;; bid_ts + ); + cs.end_parse(); + return res; +} +cell pack_last_bid(slice bidder_address, int bid, int bid_ts) inline { + return begin_cell() + .store_slice(bidder_address) + .store_grams(bid) + .store_uint(bid_ts, 32) + .end_cell(); +} + +;;teleitem_auction_state$_ last_bid:(Maybe ^TeleitemLastBid) min_bid:Grams end_time:uint32 = TeleitemAuctionState; +(cell, int, int) unpack_auction_state(cell c) inline { + slice cs = c.begin_parse(); + var res = ( + cs~load_maybe_ref(), ;; maybe last_bid + cs~load_grams(), ;; min_bid + cs~load_uint(32) ;; end_time + ); + cs.end_parse(); + return res; +} +cell pack_auction_state(cell last_bid, int min_bid, int end_time) inline { + return begin_cell() + .store_maybe_ref(last_bid) + .store_grams(min_bid) + .store_uint(end_time, 32) + .end_cell(); +} + +(slice, int, int, int, int, int) unpack_auction_config(cell c) inline { + slice cs = c.begin_parse(); + var res = ( + cs~load_msg_addr(), ;; beneficiary address + cs~load_grams(), ;; initial_min_bid + cs~load_grams(), ;; max_bid + cs~load_uint(8), ;; min_bid_step + cs~load_uint(32), ;; min_extend_time + cs~load_uint(32) ;; duration + ); + cs.end_parse(); + return res; +} + +;;teleitem_auction$_ state:^TeleitemAuctionState config:^TeleitemConfig = TeleitemAuction; +(cell, cell) unpack_auction(cell c) inline { + slice cs = c.begin_parse(); + var res = ( + cs~load_ref(), + cs~load_ref() + ); + cs.end_parse(); + return res; +} + +cell pack_auction(cell state, cell config) inline { + return begin_cell() + .store_ref(state) + .store_ref(config) + .end_cell(); +} + +(int, slice, slice, cell, int, slice) unpack_nft_cmd_transfer(slice cs) inline { + return ( + cs~load_uint(64), + cs~load_msg_addr(), + cs~load_msg_addr(), + cs~load_maybe_ref(), + cs~load_grams(), + cs + ); +} diff --git a/crypto/func/auto-tests/legacy_tests/tele-nft-item/nft-item.fc b/crypto/func/auto-tests/legacy_tests/tele-nft-item/nft-item.fc new file mode 100644 index 00000000..93c18eda --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tele-nft-item/nft-item.fc @@ -0,0 +1,367 @@ +#include "stdlib.fc"; +#include "common.fc"; + +int send_money(int my_balance, slice address, int value) impure { + int amount_to_send = min(my_balance - min_tons_for_storage, value); + if (amount_to_send > 0) { + send_msg(address, amount_to_send, op::fill_up, cur_lt(), null(), 2); ;; ignore errors + my_balance -= amount_to_send; + } + return my_balance; +} + +(int, slice, cell) maybe_end_auction(int my_balance, slice owner, cell auction, cell royalty_params, int is_external) impure { + (cell auction_state, cell auction_config) = unpack_auction(auction); + (cell last_bid, int min_bid, int end_time) = unpack_auction_state(auction_state); + if (now() < end_time) { + return (my_balance, owner, auction); + } + if (is_external) { + accept_message(); + } + ;; should end auction + if (null?(last_bid)) { + ;; no stakes were made + ;; NB: owner is not null now + return (my_balance, owner, null()); + } + (slice beneficiary_address, _, _, _, _, _) = unpack_auction_config(auction_config); + (slice bidder_address, int bid, int bid_ts) = unpack_last_bid(last_bid); + (int royalty_num, int royalty_denom, slice royalty_address) = unpack_nft_royalty_params(royalty_params); + + send_msg(bidder_address, 0, op::ownership_assigned, cur_lt(), + begin_cell() + .store_slice(owner) + .store_int(0, 1) + .store_uint(op::teleitem_bid_info, 32) + .store_grams(bid) + .store_uint(bid_ts, 32), + 1); ;; paying fees, revert on errors + + if ((royalty_num > 0) & (royalty_denom > 0) & ~ equal_slices(royalty_address, beneficiary_address)) { + int royalty_value = min(bid, muldiv(bid, royalty_num, royalty_denom)); + bid -= royalty_value; + my_balance = send_money(my_balance, royalty_address, royalty_value); + } + + my_balance = send_money(my_balance, beneficiary_address, bid); + + return (my_balance, bidder_address, null()); +} + +(int, cell) process_new_bid(int my_balance, slice new_bid_address, int new_bid, cell auction) impure { + (cell auction_state, cell auction_config) = unpack_auction(auction); + (cell old_last_bid, int min_bid, int end_time) = unpack_auction_state(auction_state); + throw_if(err::too_small_stake, new_bid < min_bid); + (slice beneficiary_address, int initial_min_bid, int max_bid, int min_bid_step, int min_extend_time, _) = unpack_auction_config(auction_config); + cell new_last_bid = pack_last_bid(new_bid_address, new_bid, now()); + int new_end_time = max(end_time, now() + min_extend_time); + if ((max_bid > 0) & (new_bid >= max_bid)) { + ;; for maybe_end_auction + new_end_time = 0; + } + ;; step is at least GR$1 + int new_min_bid = max(new_bid + one_ton, (new_bid * (100 + min_bid_step) + 99) / 100); + ifnot (cell_null?(old_last_bid)) { + (slice old_bidder_address, int old_bid, _) = unpack_last_bid(old_last_bid); + int to_send = min(my_balance - min_tons_for_storage, old_bid); + if (to_send > 0) { + send_msg(old_bidder_address, to_send, op::outbid_notification, cur_lt(), null(), 1); + my_balance -= to_send; + } + } + cell new_auction_state = pack_auction_state(new_last_bid, new_min_bid, new_end_time); + return (my_balance, pack_auction(new_auction_state, auction_config)); +} + +cell prepare_auction(cell auction_config) { + (slice beneficiary_address, int initial_min_bid, int max_bid, int min_bid_step, int min_extend_time, int duration) = unpack_auction_config(auction_config); + ;; check beneficiary address + parse_std_addr(beneficiary_address); + if ((initial_min_bid < 2 * min_tons_for_storage) | ((max_bid != 0) & (max_bid < initial_min_bid)) | + (min_bid_step <= 0) | (min_extend_time > 60 * 60 * 24 * 7) | (duration > 60 * 60 * 24 * 365)) { + return null(); + } + cell auction_state = pack_auction_state(null(), initial_min_bid, now() + duration); + return pack_auction(auction_state, auction_config); +} + +cell deploy_item(int my_balance, slice msg) { + ;; Do not throw errors here! + (slice bidder_address, int bid, cell token_info, cell nft_content, cell auction_config, cell royalty_params) = unpack_teleitem_msg_deploy(msg); + cell auction = prepare_auction(auction_config); + if (cell_null?(auction)) { + return null(); + } + (my_balance, cell new_auction) = process_new_bid(my_balance, bidder_address, bid, auction); + (my_balance, slice owner, new_auction) = maybe_end_auction(my_balance, zero_address(), new_auction, royalty_params, 0); + cell content = pack_item_content(nft_content, null(), token_info); + return pack_item_state(owner, content, new_auction, royalty_params); + +} + +slice transfer_ownership(int my_balance, slice owner_address, slice in_msg_body, int fwd_fees) impure inline { + (int query_id, slice new_owner_address, slice response_destination, cell custom_payload, int forward_amount, slice forward_payload) + = unpack_nft_cmd_transfer(in_msg_body); + + force_chain(new_owner_address); + + int rest_amount = my_balance - min_tons_for_storage; + if (forward_amount) { + rest_amount -= (forward_amount + fwd_fees); + } + int need_response = response_destination.preload_uint(2) != 0; ;; if NOT addr_none: 00 + if (need_response) { + rest_amount -= fwd_fees; + } + + throw_unless(err::not_enough_funds, rest_amount >= 0); ;; base nft spends fixed amount of gas, will not check for response + + if (forward_amount) { + send_msg(new_owner_address, forward_amount, op::ownership_assigned, query_id, + begin_cell().store_slice(owner_address).store_slice(forward_payload), 1); ;; paying fees, revert on errors + + } + if (need_response) { + force_chain(response_destination); + send_msg(response_destination, rest_amount, op::excesses, query_id, null(), 1); ;; paying fees, revert on errors + } + + return new_owner_address; +} + +cell change_dns_record(cell dns, slice in_msg_body) { + int key = in_msg_body~load_uint(256); + int has_value = in_msg_body.slice_refs() > 0; + + if (has_value) { + cell value = in_msg_body~load_ref(); + dns~udict_set_ref(256, key, value); + } else { + dns~udict_delete?(256, key); + } + + return dns; +} + +() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { + int my_balance = pair_first(get_balance()); + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if (flags & 1) { ;; ignore all bounced messages + return (); + } + slice sender_address = cs~load_msg_addr(); + + cs~load_msg_addr(); ;; skip dst + cs~load_grams(); ;; skip value + cs~load_maybe_ref(); ;; skip extracurrency collection + cs~load_grams(); ;; skip ihr_fee + int fwd_fee = muldiv(cs~load_grams(), 3, 2); ;; we use message fwd_:fee for estimation of forward_payload costs + + int op = in_msg_body.slice_empty?() ? 0 : in_msg_body~load_uint(32); + + (cell config, cell state) = unpack_item_data(); + (int index, slice collection_address) = unpack_item_config(config); + + if (equal_slices(collection_address, sender_address)) { + throw_unless(err::forbidden_not_deploy, op == op::teleitem_msg_deploy); + if (cell_null?(state)) { + cell new_state = deploy_item(my_balance, in_msg_body); + ifnot (cell_null?(new_state)) { + return save_item_data(config, new_state); + } + } + slice bidder_address = in_msg_body~load_msg_addr(); ;; first field in teleitem_msg_deploy + send_msg(bidder_address, 0, op::teleitem_return_bid, cur_lt(), null(), 64); ;; carry all the remaining value of the inbound message + return (); + } + + throw_if(err::uninited, cell_null?(state)); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + + if (op == op::get_royalty_params) { + int query_id = in_msg_body~load_uint(64); + send_msg(sender_address, 0, op::report_royalty_params, query_id, begin_cell().store_slice(royalty_params.begin_parse()), 64); ;; carry all the remaining value of the inbound message + return (); + } + + if (op == op::nft_cmd_get_static_data) { + int query_id = in_msg_body~load_uint(64); + send_msg(sender_address, 0, op::report_static_data, query_id, begin_cell().store_uint(index, 256).store_slice(collection_address), 64); ;; carry all the remaining value of the inbound message + return (); + } + + int is_topup = (op == 0) & equal_slices(in_msg_body, "#topup") & (in_msg_body.slice_refs() == 0); + if (is_topup) { + return (); + } + + ifnot (cell_null?(auction)) { + ;; sender do not pay for auction with its message + my_balance -= msg_value; + (my_balance, owner_address, auction) = maybe_end_auction(my_balance, owner_address, auction, royalty_params, 0); + if (cell_null?(auction)) { + cell new_state = pack_item_state(owner_address, content, auction, royalty_params); + save_item_data(config, new_state); + } + my_balance += msg_value; + } + + if (op == op::teleitem_cancel_auction) { + throw_if(err::no_auction, cell_null?(auction)); + throw_unless(err::forbidden_auction, equal_slices(sender_address, owner_address)); + int query_id = in_msg_body~load_uint(64); + (cell auction_state, cell auction_config) = unpack_auction(auction); + (cell last_bid, int min_bid, int end_time) = unpack_auction_state(auction_state); + throw_unless(err::already_has_stakes, cell_null?(last_bid)); + cell new_state = pack_item_state(owner_address, content, null(), royalty_params); + if (query_id) { + send_msg(sender_address, 0, op::teleitem_ok, query_id, null(), 64); ;; carry all the remaining value of the inbound message + } + return save_item_data(config, new_state); + } + + ifnot (cell_null?(auction)) { + throw_unless(err::forbidden_not_stake, op == 0); + (my_balance, auction) = process_new_bid(my_balance, sender_address, msg_value, auction); + (my_balance, owner_address, auction) = maybe_end_auction(my_balance, owner_address, auction, royalty_params, 0); + cell new_state = pack_item_state(owner_address, content, auction, royalty_params); + return save_item_data(config, new_state); + } + + if (op == 0) { + throw_unless(err::forbidden_topup, equal_slices(sender_address, owner_address)); ;; only owner can fill-up balance, prevent coins lost right after the auction + ;; if owner send bid right after auction he can restore it by transfer response message + return (); + } + + if (op == op::teleitem_start_auction) { + throw_unless(err::forbidden_auction, equal_slices(sender_address, owner_address)); + int query_id = in_msg_body~load_uint(64); + cell new_auction_config = in_msg_body~load_ref(); + cell new_auction = prepare_auction(new_auction_config); + throw_if(err::invalid_auction_config, cell_null?(new_auction)); + cell new_state = pack_item_state(owner_address, content, new_auction, royalty_params); + if (query_id) { + send_msg(sender_address, 0, op::teleitem_ok, query_id, null(), 64); ;; carry all the remaining value of the inbound message + } + return save_item_data(config, new_state); + } + + if (op == op::nft_cmd_transfer) { + throw_unless(err::forbidden_transfer, equal_slices(sender_address, owner_address)); + slice new_owner_address = transfer_ownership(my_balance, owner_address, in_msg_body, fwd_fee); + cell new_state = pack_item_state(new_owner_address, content, auction, royalty_params); + return save_item_data(config, new_state); + } + + if (op == op::change_dns_record) { ;; change dns record + int query_id = in_msg_body~load_uint(64); + throw_unless(err::forbidden_change_dns, equal_slices(sender_address, owner_address)); + (cell nft_content, cell dns, cell token_info) = unpack_item_content(content); + cell new_dns = change_dns_record(dns, in_msg_body); + cell new_content = pack_item_content(nft_content, new_dns, token_info); + cell new_state = pack_item_state(owner_address, new_content, auction, royalty_params); + send_msg(sender_address, 0, op::teleitem_ok, query_id, null(), 64); ;; carry all the remaining value of the inbound message + return save_item_data(config, new_state); + } + throw(err::unknown_op); +} + +() recv_external(slice in_msg) impure { + int my_balance = pair_first(get_balance()); + (cell config, cell state) = unpack_item_data(); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + (my_balance, owner_address, auction) = maybe_end_auction(my_balance, owner_address, auction, royalty_params, -1); + cell new_state = pack_item_state(owner_address, content, auction, royalty_params); + return save_item_data(config, new_state); +} + +;; +;; GET Methods +;; + +(int, int, slice, slice, cell) get_nft_data() method_id { + (cell config, cell state) = unpack_item_data(); + (int item_index, slice collection_address) = unpack_item_config(config); + if (cell_null?(state)) { + return (0, item_index, collection_address, zero_address(), null()); + } + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + (cell nft_content, cell dns, cell token_info) = unpack_item_content(content); + return (-1, item_index, collection_address, owner_address, nft_content); +} + +slice get_full_domain() method_id { + (cell config, cell state) = unpack_item_data(); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + (cell nft_content, cell dns, cell token_info) = unpack_item_content(content); + (slice token_name, slice domain) = unpack_token_info(token_info); + return begin_cell().store_slice(domain).store_slice(token_name).store_int(0, 8).end_cell().begin_parse(); +} + +slice get_telemint_token_name() method_id { + (cell config, cell state) = unpack_item_data(); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + (cell nft_content, cell dns, cell token_info) = unpack_item_content(content); + (slice token_name, slice domain) = unpack_token_info(token_info); + return token_name; +} + +(slice, int, int, int, int) get_telemint_auction_state() method_id { + (cell config, cell state) = unpack_item_data(); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + throw_if (err::no_auction, cell_null?(auction)); + (cell auction_state, cell auction_config) = unpack_auction(auction); + (cell last_bid, int min_bid, int end_time) = unpack_auction_state(auction_state); + (slice bidder_address, int bid, int bid_ts) = (null(), 0, 0); + ifnot (cell_null?(last_bid)) { + (bidder_address, bid, bid_ts) = unpack_last_bid(last_bid); + } + return (bidder_address, bid, bid_ts, min_bid, end_time); +} + +(slice, int, int, int, int, int) get_telemint_auction_config() method_id { + (cell config, cell state) = unpack_item_data(); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + if (cell_null?(auction)) { + ;; Do not throw error, so it is easy to check if get_telemint_auction_config method exists + return (null(), 0, 0, 0, 0, 0); + } + (cell auction_state, cell auction_config) = unpack_auction(auction); + (slice beneficiary_address, int initial_min_bid, int max_bid, int min_bid_step, int min_extend_time, int duration) = + unpack_auction_config(auction_config); + return (beneficiary_address, initial_min_bid, max_bid, min_bid_step, min_extend_time, duration); +} + +(int, int, slice) royalty_params() method_id { + (cell config, cell state) = unpack_item_data(); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + (int numerator, int denominator, slice destination) = unpack_nft_royalty_params(royalty_params); + return (numerator, denominator, destination); +} + +(int, cell) dnsresolve(slice subdomain, int category) method_id { + (cell config, cell state) = unpack_item_data(); + (slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state); + (cell nft_content, cell dns, cell token_info) = unpack_item_content(content); + + int subdomain_bits = slice_bits(subdomain); + throw_unless(err::bad_subdomain_length, subdomain_bits % 8 == 0); + + int starts_with_zero_byte = subdomain.preload_int(8) == 0; + throw_unless(err::no_first_zero_byte, starts_with_zero_byte); + + if (subdomain_bits > 8) { ;; more than "." requested + category = "dns_next_resolver"H; + } + + if (category == 0) { ;; all categories are requested + return (8, dns); + } + + (cell value, int found) = dns.udict_get_ref?(256, category); + return (8, value); +} diff --git a/crypto/func/auto-tests/legacy_tests/tele-nft-item/stdlib.fc b/crypto/func/auto-tests/legacy_tests/tele-nft-item/stdlib.fc new file mode 100644 index 00000000..45d01f32 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/tele-nft-item/stdlib.fc @@ -0,0 +1,208 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; diff --git a/crypto/func/auto-tests/legacy_tests/uni-lock-wallet/stdlib.fc b/crypto/func/auto-tests/legacy_tests/uni-lock-wallet/stdlib.fc new file mode 100644 index 00000000..266b69ac --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/uni-lock-wallet/stdlib.fc @@ -0,0 +1,211 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; diff --git a/crypto/func/auto-tests/legacy_tests/uni-lock-wallet/uni-lockup-wallet.fc b/crypto/func/auto-tests/legacy_tests/uni-lock-wallet/uni-lockup-wallet.fc new file mode 100644 index 00000000..2fd10937 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/uni-lock-wallet/uni-lockup-wallet.fc @@ -0,0 +1,217 @@ +;; Restricted wallet initialized by a third party (a variant of restricted-wallet3-code.fc) +;; Allows to add more locked budget after initialization + +#include "stdlib.fc"; + +int err:wrong_signature() asm "31 PUSHINT"; +int err:wrong_config_signature() asm "32 PUSHINT"; +int err:value_is_too_small() asm "33 PUSHINT"; +int err:wrong_seqno() asm "34 PUSHINT"; +int err:wrong_subwallet_id() asm "35 PUSHINT"; +int err:replay_protection() asm "36 PUSHINT"; +int err:unknown_op() asm "40 PUSHINT"; +int err:unknown_cmd() asm "41 PUSHINT"; + +int op:rwallet_op() asm "0x82eaf9c4 PUSHINT"; +int cmd:restricted_transfer() asm "0x373aa9f4 PUSHINT"; + +_ is_whitelisted?(addr, allowed_destinations) { + (_, _, _, int found) = allowed_destinations.pfxdict_get?(addr.slice_bits(), addr); + return found; +} + +_ check_message_destination(msg, allowed_destinations) inline_ref { + var cs = msg.begin_parse(); + var flags = cs~load_uint(4); + if (flags & 8) { + ;; external messages are always valid + return true; + } + var (s_addr, d_addr) = (cs~load_msg_addr(), cs~load_msg_addr()); + + return is_whitelisted?(d_addr, allowed_destinations); +} + +_ unpack_data() { + var cs = get_data().begin_parse(); + var res = ( + cs~load_uint(32), + cs~load_uint(32), + cs~load_uint(256), + cs~load_uint(256), + cs~load_dict(), + cs~load_grams(), + cs~load_dict(), + cs~load_grams(), + cs~load_dict() + ); + cs.end_parse(); + return res; +} + +_ pack_data(int seqno, int subwallet_id, int public_key, int config_public_key, cell allowed_destinations, int total_locked_value, cell +locked, int total_restricted_value, cell restricted) { + return begin_cell() + .store_int(seqno, 32) + .store_int(subwallet_id, 32) + .store_uint(public_key, 256) + .store_uint(config_public_key, 256) + .store_dict(allowed_destinations) + .store_grams(total_locked_value) + .store_dict(locked) + .store_grams(total_restricted_value) + .store_dict(restricted).end_cell(); +} + +(cell, int) lock_grams(cell locked, int total, int ts, int value) { + total += value; + (slice found_cs, var found) = locked.udict_get?(32, ts); + if (found) { + var found_value = found_cs~load_grams(); + found_cs.end_parse(); + value += found_value; + } + locked~udict_set_builder(32, ts, begin_cell().store_grams(value)); + return (locked, total); +} + +(cell, int) unlock_grams(cell locked, int total, int now_ts) { + do { + var (locked', ts, value_cs, f) = locked.udict_delete_get_min(32); + f~touch(); + if (f) { + f = ts <= now_ts; + } + if (f) { + locked = locked'; + int value = value_cs~load_grams(); + value_cs.end_parse(); + total -= value; + } + } until (~ f); + return (locked, total); +} + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + if (flags & 1) { + ;; ignore all bounced messages + return (); + } + var s_addr = cs~load_msg_addr(); + if (in_msg.slice_empty?()) { + return(); + } + int op = in_msg~load_uint(32); + if (op <= 1) { + ;; simple transfer with comment, return + return (); + } + var (stored_seqno, stored_subwallet, public_key, config_public_key, allowed_destinations, total_locked_value, locked, + total_restricted_value, restricted) = unpack_data(); + + if is_whitelisted?(s_addr, allowed_destinations) & (op != op:rwallet_op()) { + return (); + } + + throw_unless(err:unknown_op(), op == op:rwallet_op()); + throw_unless(err:value_is_too_small(), msg_value >= 1000000000); + + + + var signature = in_msg~load_bits(512); + throw_unless(err:wrong_config_signature(), check_signature(slice_hash(in_msg), signature, config_public_key)); + int cmd = in_msg~load_uint(32); + throw_unless(err:unknown_cmd(), cmd == cmd:restricted_transfer()); + var (only_restrict, ts) = (in_msg~load_uint(1), in_msg~load_uint(32)); + if (only_restrict) { + (restricted, total_restricted_value) = lock_grams(restricted, total_restricted_value, ts, msg_value); + } else { + (locked, total_locked_value) = lock_grams(locked, total_locked_value, ts, msg_value); + } + + set_data(pack_data(stored_seqno, stored_subwallet, public_key, config_public_key, allowed_destinations, total_locked_value, locked, + total_restricted_value, restricted)); +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + throw_if(err:replay_protection(), valid_until <= now()); + var (stored_seqno, stored_subwallet, public_key, config_public_key, allowed_destinations, total_locked_value, locked, + total_restricted_value, restricted) = unpack_data(); + throw_unless(err:wrong_seqno(), msg_seqno == stored_seqno); + throw_unless(err:wrong_subwallet_id(), subwallet_id == stored_subwallet); + throw_unless(err:wrong_signature(), check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + + (restricted, total_restricted_value) = unlock_grams(restricted, total_restricted_value, now()); + (locked, total_locked_value) = unlock_grams(locked, total_locked_value, now()); + int effectively_locked = total_locked_value; + int can_use_restricted = 1; + var cs_copy = cs; + while (cs_copy.slice_refs()) { + var mode = cs_copy~load_uint(8); + var msg = cs_copy~load_ref(); + can_use_restricted &= check_message_destination(msg, allowed_destinations); + } + + ifnot (can_use_restricted) { + effectively_locked += total_restricted_value; + } + raw_reserve(effectively_locked, 2); + + cs~touch(); + while (cs.slice_refs()) { + var mode = cs~load_uint(8); + var msg = cs~load_ref(); + send_raw_message(msg, mode); + } + cs.end_parse(); + + set_data(pack_data(stored_seqno + 1, stored_subwallet, public_key, config_public_key, allowed_destinations, total_locked_value, locked, + total_restricted_value, restricted)); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int wallet_id() method_id { + var ds = get_data().begin_parse(); + ds~load_uint(32); + return ds.preload_uint(32); +} + +int get_public_key() method_id { + var ds = get_data().begin_parse(); + ds~load_uint(32 + 32); + return ds.preload_uint(256); +} + +;; the next three methods are mostly for testing + +(int, int, int) get_balances_at(int time) method_id { + var (stored_seqno, stored_subwallet, public_key, config_public_key, allowed_destinations, total_locked_value, locked, + total_restricted_value, restricted) = unpack_data(); + (restricted, total_restricted_value) = unlock_grams(restricted, total_restricted_value, time); + (locked, total_locked_value) = unlock_grams(locked, total_locked_value, time); + int ton_balance = get_balance().pair_first(); + return ( ton_balance, + total_restricted_value, + total_locked_value ); +} + +(int, int, int) get_balances() method_id { + return get_balances_at(now()); +} + +int check_destination(slice destination) method_id { + var (stored_seqno, stored_subwallet, public_key, config_public_key, allowed_destinations, total_locked_value, locked, + total_restricted_value, restricted) = unpack_data(); + return is_whitelisted?(destination, allowed_destinations); +} diff --git a/crypto/func/auto-tests/legacy_tests/wallet-v4/stdlib.fc b/crypto/func/auto-tests/legacy_tests/wallet-v4/stdlib.fc new file mode 100644 index 00000000..05c3a4c0 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/wallet-v4/stdlib.fc @@ -0,0 +1,215 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +builder store_coins(builder b, int x) asm "STVARUINT16"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; + +int equal_slices (slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; +builder store_builder(builder to, builder from) asm "STBR"; diff --git a/crypto/func/auto-tests/legacy_tests/wallet-v4/wallet-v4-code.fc b/crypto/func/auto-tests/legacy_tests/wallet-v4/wallet-v4-code.fc new file mode 100644 index 00000000..21245a67 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/wallet-v4/wallet-v4-code.fc @@ -0,0 +1,199 @@ +;; Wallet smart contract with plugins + +#include "stdlib.fc"; + +(slice, int) dict_get?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTGET" "NULLSWAPIFNOT"; +(cell, int) dict_add_builder?(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTADDB"; +(cell, int) dict_delete?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTDEL"; + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + if (flags & 1) { + ;; ignore all bounced messages + return (); + } + if (in_msg.slice_bits() < 32) { + ;; ignore simple transfers + return (); + } + int op = in_msg~load_uint(32); + if (op != 0x706c7567) & (op != 0x64737472) { ;; "plug" & "dstr" + ;; ignore all messages not related to plugins + return (); + } + slice s_addr = cs~load_msg_addr(); + (int wc, int addr_hash) = parse_std_addr(s_addr); + slice wc_n_address = begin_cell().store_int(wc, 8).store_uint(addr_hash, 256).end_cell().begin_parse(); + var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); + var plugins = ds~load_dict(); + var (_, success?) = plugins.dict_get?(8 + 256, wc_n_address); + if ~(success?) { + ;; it may be a transfer + return (); + } + int query_id = in_msg~load_uint(64); + var msg = begin_cell(); + if (op == 0x706c7567) { ;; request funds + + (int r_toncoins, cell r_extra) = (in_msg~load_grams(), in_msg~load_dict()); + + [int my_balance, _] = get_balance(); + throw_unless(80, my_balance - msg_value >= r_toncoins); + + msg = msg.store_uint(0x18, 6) + .store_slice(s_addr) + .store_grams(r_toncoins) + .store_dict(r_extra) + .store_uint(0, 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x706c7567 | 0x80000000, 32) + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 64); + + } + + if (op == 0x64737472) { ;; remove plugin by its request + + plugins~dict_delete?(8 + 256, wc_n_address); + var ds = get_data().begin_parse().first_bits(32 + 32 + 256); + set_data(begin_cell().store_slice(ds).store_dict(plugins).end_cell()); + ;; return coins only if bounce expected + if (flags & 2) { + msg = msg.store_uint(0x18, 6) + .store_slice(s_addr) + .store_grams(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x64737472 | 0x80000000, 32) + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 64); + } + } +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + throw_if(36, valid_until <= now()); + var ds = get_data().begin_parse(); + var (stored_seqno, stored_subwallet, public_key, plugins) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256), ds~load_dict()); + ds.end_parse(); + throw_unless(33, msg_seqno == stored_seqno); + throw_unless(34, subwallet_id == stored_subwallet); + throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .store_dict(plugins) + .end_cell()); + commit(); + cs~touch(); + int op = cs~load_uint(8); + + if (op == 0) { ;; simple send + while (cs.slice_refs()) { + var mode = cs~load_uint(8); + send_raw_message(cs~load_ref(), mode); + } + return (); ;; have already saved the storage + } + + if (op == 1) { ;; deploy and install plugin + int plugin_workchain = cs~load_int(8); + int plugin_balance = cs~load_grams(); + (cell state_init, cell body) = (cs~load_ref(), cs~load_ref()); + int plugin_address = cell_hash(state_init); + slice wc_n_address = begin_cell().store_int(plugin_workchain, 8).store_uint(plugin_address, 256).end_cell().begin_parse(); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(4, 3).store_slice(wc_n_address) + .store_grams(plugin_balance) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(body); + send_raw_message(msg.end_cell(), 3); + (plugins, int success?) = plugins.dict_add_builder?(8 + 256, wc_n_address, begin_cell()); + throw_unless(39, success?); + } + + if (op == 2) { ;; install plugin + slice wc_n_address = cs~load_bits(8 + 256); + int amount = cs~load_grams(); + int query_id = cs~load_uint(64); + + (plugins, int success?) = plugins.dict_add_builder?(8 + 256, wc_n_address, begin_cell()); + throw_unless(39, success?); + + builder msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(4, 3).store_slice(wc_n_address) + .store_grams(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x6e6f7465, 32) ;; op + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 3); + } + + if (op == 3) { ;; remove plugin + slice wc_n_address = cs~load_bits(8 + 256); + int amount = cs~load_grams(); + int query_id = cs~load_uint(64); + + (plugins, int success?) = plugins.dict_delete?(8 + 256, wc_n_address); + throw_unless(39, success?); + + builder msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(4, 3).store_slice(wc_n_address) + .store_grams(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x64737472, 32) ;; op + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 3); + } + + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .store_dict(plugins) + .end_cell()); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_subwallet_id() method_id { + return get_data().begin_parse().skip_bits(32).preload_uint(32); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse().skip_bits(64); + return cs.preload_uint(256); +} + +int is_plugin_installed(int wc, int addr_hash) method_id { + var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); + var plugins = ds~load_dict(); + var (_, success?) = plugins.dict_get?(8 + 256, begin_cell().store_int(wc, 8).store_uint(addr_hash, 256).end_cell().begin_parse()); + return success?; +} + +tuple get_plugin_list() method_id { + var list = null(); + var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); + var plugins = ds~load_dict(); + do { + var (wc_n_address, _, f) = plugins~dict::delete_get_min(8 + 256); + if (f) { + (int wc, int addr) = (wc_n_address~load_int(8), wc_n_address~load_uint(256)); + list = cons(pair(wc, addr), list); + } + } until (~ f); + return list; +} diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/LICENSE b/crypto/func/auto-tests/legacy_tests/whales-nominators/LICENSE new file mode 100644 index 00000000..70b5b1cf --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/LICENSE @@ -0,0 +1,676 @@ +AUTHOR: https://github.com/ton-foundation/ton-nominators + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/constants.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/constants.fc new file mode 100644 index 00000000..7187fef0 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/constants.fc @@ -0,0 +1,100 @@ +;; +;; Errors +;; + +int error::invalid_address() asm "70 PUSHINT"; +int error::invalid_message() asm "72 PUSHINT"; +int error::access_denied() asm "73 PUSHINT"; +int error::internal_error() asm "74 PUSHINT"; +int error::already_deployed() asm "75 PUSHINT"; +int error::too_low_value() asm "76 PUSHINT"; +int error::invalid_stake_value() asm "77 PUSHINT"; +int error::unknown_text_command() asm "78 PUSHINT"; + +;; +;; Members +;; + +int op::stake_deposit() asm "2077040623 PUSHINT"; +int op::stake_deposit::response() asm "3326208306 PUSHINT"; +int op::stake_withdraw() asm "3665837821 PUSHINT"; +int op::stake_withdraw::delayed() asm "1958425639 PUSHINT"; +int op::stake_withdraw::response() asm "601104865 PUSHINT"; +int op::donate() asm "1203495973 PUSHINT"; +int op::donate::response() asm "3095625004 PUSHINT"; +;; +;; Owner +;; + +int op::upgrade() asm "3690657815 PUSHINT"; +int op::upgrade::response() asm "1395540087 PUSHINT"; +int op::update() asm "37541164 PUSHINT"; +int op::update::response() asm "839996522 PUSHINT"; + +;; +;; Controller +;; + +int op::stake_send() asm "2718326572 PUSHINT"; +int op::accept_stakes() asm "2577928699 PUSHINT"; +int op::accept_withdraws() asm "2711607604 PUSHINT"; +int op::stake_recover() asm "1699565966 PUSHINT"; +int op::withdraw_unowned() asm "622684824 PUSHINT"; +int op::withdraw_unowned::response() asm "488052159 PUSHINT"; +int op::force_kick() asm "1396625244 PUSHINT"; +int op::force_kick::notification() asm "2060499266 PUSHINT"; + +;; +;; Elector +;; + +int elector::refund::request() asm "0x47657424 PUSHINT"; +int elector::refund::response() asm "0xf96f7324 PUSHINT"; + +int elector::stake::request() asm "0x4e73744b PUSHINT"; +int elector::stake::response() asm "0xf374484c PUSHINT"; +int elector::stake::response::fail() asm "0xee6f454c PUSHINT"; + +;; +;; Send Mode +;; + +int send_mode::default() asm "0 PUSHINT"; +int send_mode::separate_gas() asm "1 PUSHINT"; +int send_mode::ignore_errors() asm "2 PUSHINT"; +int send_mode::carry_remaining_balance() asm "128 PUSHINT"; +int send_mode::carry_remaining_value() asm "64 PUSHINT"; +int send_mode::destroy_if_zero() asm "64 PUSHINT"; + +;; +;; Coins +;; + +int coins::1() asm "1000000000 PUSHINT"; +int coins::100() asm "100000000000 PUSHINT"; + +;; +;; Fees +;; + +int fees::storage_reserve() asm "1000000000 PUSHINT"; ;; 1 TON +int fees::receipt() asm "100000000 PUSHINT"; ;; 0.1 TON +int fees::op() asm "100000000 PUSHINT"; ;; 0.1 TON +int fees::deploy() asm "5000000000 PUSHINT"; ;; 5 TON +int fees::stake_fees() asm "2000000000 PUSHINT"; ;; 2 TON + +;; +;; Parameters +;; + +int params::min_op() asm "100000000 PUSHINT"; ;; 0.1 TON +int params::min_stake() asm "1000000000 PUSHINT"; ;; 1 TON +int params::min_fee() asm "1000000000 PUSHINT"; ;; 1 TON +int params::pending_op() asm "5000000000 PUSHINT"; ;; 5 TON +int params::ppc_precision() asm "10000000000000 PUSHINT"; ;; 10^13 + +;; +;; Members +;; + +int owner_id() asm "0 PUSHINT"; \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/get.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/get.fc new file mode 100644 index 00000000..c9152e1c --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/get.fc @@ -0,0 +1,125 @@ +;; +;; Related contracts +;; + +_ get_proxy() method_id { + load_base_data(); + return ctx_proxy; +} + +_ get_owner() method_id { + load_base_data(); + return ctx_owner; +} + +_ get_controller() method_id { + load_base_data(); + return ctx_controller; +} + +;; +;; Balances for controller +;; + +_ get_unowned() method_id { + load_base_data(); + var [balance, extra] = get_balance(); + return max(balance - owned_balance(), 0); +} + +_ get_available() method_id { + load_base_data(); + return ctx_balance - ctx_balance_sent; +} + +;; +;; Pool and staking status +;; + +_ get_staking_status() method_id { + load_base_data(); + load_validator_data(); + + var querySent = proxy_stored_query_id != 0; + var unlocked = (proxy_stake_until == 0) | (proxy_stake_until < now()); + var until_val = proxy_stake_until; + if ((proxy_stake_at != 0) & (proxy_stake_until != 0)) { + until_val = lockup_lift_time(proxy_stake_at, proxy_stake_until); + unlocked = unlocked & (until_val < now()); + } + return (proxy_stake_at, until_val, proxy_stake_sent, querySent, unlocked, ctx_locked); +} + +_ get_pool_status() method_id { + load_base_data(); + load_member(owner_id()); + return (ctx_balance, ctx_balance_sent, ctx_balance_pending_deposits, ctx_balance_pending_withdraw, ctx_balance_withdraw); +} + +;; +;; Params +;; +_ get_params() method_id { + load_base_data(); + var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; + return (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price); +} + +;; +;; Members +;; + +_ get_member_balance(slice address) method_id { + load_base_data(); + load_member(parse_work_addr(address)); + + member_update_balance(); + return (ctx_member_balance, ctx_member_pending_deposit, ctx_member_pending_withdraw, ctx_member_withdraw); +} + +_ get_members_raw() method_id { + load_base_data(); + return ctx_nominators; +} + +_ get_members() method_id { + load_base_data(); + + ;; Init with owner + load_member(owner_id()); + member_update_balance(); + var list = nil; + list = cons([ctx_owner, ctx_member_balance, ctx_member_pending_deposit, ctx_member_pending_withdraw, ctx_member_withdraw], list); + + ;; Iterate all members + var id = -1; + do { + (id, var cs, var f) = ctx_nominators.udict_get_next?(256, id); + + ;; NOTE: One line condition doesn't work + if (f) { + if (id != owner_id()) { + ;; For some reason loading member from slice doesn't work + load_member(id); + member_update_balance(); + list = cons([serialize_work_addr(id), ctx_member_balance, ctx_member_pending_deposit, ctx_member_pending_withdraw, ctx_member_withdraw], list); + } + } + } until (~ f); + + return list; +} + +_ get_member(slice address) method_id { + load_base_data(); + load_member(parse_work_addr(address)); + member_update_balance(); + return (ctx_member_balance, ctx_member_pending_deposit, ctx_member_pending_withdraw, ctx_member_withdraw); +} + +_ supported_interfaces() method_id { + return ( + 123515602279859691144772641439386770278, ;; org.ton.introspection.v0 + 256184278959413194623484780286929323492 ;; com.tonwhales.nominators:v0 + ); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/model.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/model.fc new file mode 100644 index 00000000..9c28b42a --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/model.fc @@ -0,0 +1,297 @@ +;; +;; Low level operations +;; + +() add_member_pending_withdraw(int delta) impure inline { + ctx_balance_pending_withdraw = ctx_balance_pending_withdraw + delta; + ctx_member_pending_withdraw = ctx_member_pending_withdraw + delta; +} +() set_member_pending_withdraw(int value) impure inline { + add_member_pending_withdraw(value - ctx_member_pending_withdraw); +} + +() add_member_pending_deposit(int delta) impure inline { + ctx_member_pending_deposit = ctx_member_pending_deposit + delta; + ctx_balance_pending_deposits = ctx_balance_pending_deposits + delta; +} +() set_member_pending_deposit(int value) impure inline { + add_member_pending_deposit(value - ctx_member_pending_deposit); +} + +int compose_profit(int a, int b) { + ;; (a + 1) * (b + 1) - 1 + return (((a + params::ppc_precision()) * (b + params::ppc_precision())) / params::ppc_precision()) - params::ppc_precision(); ;; NOTE: Rounded down +} + +int apply_profit(int value, int value_profit, int profit) { + return ((params::ppc_precision() + profit) * value) / (params::ppc_precision() + value_profit); ;; NOTE: Rounded down +} + +;; +;; Deposit +;; + +() member_update_balance() impure { + + ;; Update profit (for non-owner) + if (ctx_member != owner_id()) { + if (ctx_profit_per_coin != ctx_member_profit_per_coin) { + int new_balance = apply_profit(ctx_member_balance, ctx_member_profit_per_coin, ctx_profit_per_coin); + int delta_balance = new_balance - ctx_member_balance; + ctx_member_balance = ctx_member_balance + delta_balance; + ctx_member_profit_per_coin = ctx_profit_per_coin; + } + } + + ;; Update pending withdraw + if (ctx_member_pending_withdraw_all) { + if (ctx_member_pending_withdraw != ctx_member_balance) { + set_member_pending_withdraw(ctx_member_balance); + } + } else { + if (ctx_member_pending_withdraw > ctx_member_balance) { + set_member_pending_withdraw(ctx_member_balance); + } + } +} + +() member_reset_pending_withdraw() impure { + set_member_pending_withdraw(0); + ctx_member_pending_withdraw_all = false; +} + +() member_stake_deposit(int value) impure { + throw_unless(error::invalid_stake_value(), value > 0); + + ;; Update balances + member_update_balance(); + + ;; Reset pending withdrawal + member_reset_pending_withdraw(); + + ;; Add deposit to pending + ;; NOTE: We are not adding directly deposit to member's balance + ;; and we are always confirming acception of deposit to a pool + ;; via sending accept message. This could be done on- and off-chain. + ;; This could be useful to make private nominator pools or black lists. + ;; Anyone always could withdraw their deposits though. + add_member_pending_deposit(value); +} + +() member_accept_stake() impure { + + ;; Checks if there are pending deposits + throw_unless(error::invalid_message(), ctx_member_pending_deposit > 0); + + ;; Check if not locked + throw_if(error::invalid_message(), ctx_locked); + + ;; Recalculate balance + member_update_balance(); + + ;; Move deposit to member's balance + var amount = ctx_member_pending_deposit; + set_member_pending_deposit(0); + + + ctx_member_balance = ctx_member_balance + amount; + ctx_balance = ctx_balance + amount; +} + +;; +;; Withdraw +;; + +(int, int) member_stake_withdraw(int value) impure { + + ;; Check input + throw_unless(error::invalid_stake_value(), value >= 0); + + ;; Update balances + member_update_balance(); + + ;; Reset pending withdrawal: would be overwritten later + member_reset_pending_withdraw(); + + ;; Pre-flight withdraw check + throw_unless(error::invalid_stake_value(), value >= 0); + throw_unless(error::invalid_stake_value(), ctx_member_balance + ctx_member_withdraw + ctx_member_pending_deposit >= value); + + ;; Check withdraw all + var withdraw_all = false; + if (value == 0) { + withdraw_all = true; + value = ctx_member_pending_deposit + ctx_member_balance + ctx_member_withdraw; + } + + ;; Trying to withdraw immediatelly + var remaining = value; + var withdrawed = 0; + + ;; Try to withdraw from pending deposit + if ((remaining > 0) & (ctx_member_pending_deposit >= 0)) { + int delta = min(ctx_member_pending_deposit, remaining); + add_member_pending_deposit(- delta); + withdrawed = withdrawed + delta; + remaining = remaining - delta; + } + + ;; Try to withdraw from withdraw balance + if ((remaining > 0) & ctx_member_withdraw > 0) { + int delta = min(ctx_member_withdraw, remaining); + ctx_member_withdraw = ctx_member_withdraw - delta; + ctx_balance_withdraw = ctx_balance_withdraw - delta; + withdrawed = withdrawed + delta; + remaining = remaining - delta; + } + + ;; Try to withdraw from balance + if ((remaining > 0) & (~ ctx_locked) & (ctx_member_balance > 0)) { + int delta = min(ctx_member_balance, remaining); + ctx_member_balance = ctx_member_balance - delta; + ctx_balance = ctx_balance - delta; + withdrawed = withdrawed + delta; + remaining = remaining - delta; + } + + ;; Add to pending withdrawals + if (remaining > 0) { + add_member_pending_withdraw(remaining); + ctx_member_pending_withdraw_all = withdraw_all; + } + + ;; Return withdraw result + return (withdrawed, remaining == 0); +} + +() member_accept_withdraw() impure { + + ;; Checks if there are pending withdrawals + throw_unless(error::invalid_message(), ctx_member_pending_withdraw > 0); + + ;; Check if not locked + throw_if(error::invalid_message(), ctx_locked); + + ;; Recalculate balance + member_update_balance(); + + ;; Move deposit to member's balance + var amount = ctx_member_pending_withdraw; + + ctx_member_balance = ctx_member_balance - amount; + ctx_member_withdraw = ctx_member_withdraw + amount; + ctx_balance = ctx_balance - amount; + ctx_balance_withdraw = ctx_balance_withdraw + amount; + ctx_balance_pending_withdraw = ctx_balance_pending_withdraw - amount; + ctx_member_pending_withdraw = 0; + ctx_member_pending_withdraw_all = false; +} + +() distribute_profit(int profit) impure { + + ;; Load extras + var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; + + ;; Load owner balances + load_member(0); + + ;; Loss + if (profit < 0) { + + ;; Stakes + var owner_stake = ctx_member_balance; + var nominators_stake = ctx_balance - owner_stake; + + ;; Distribute loss to everyone + var cycleProfitPerCoin = profit * params::ppc_precision() / ctx_balance; + var nominators_profit = (nominators_stake * cycleProfitPerCoin) / params::ppc_precision(); + var owner_profit = profit - nominators_profit; + + ;; Update balances + ctx_balance = ctx_balance + profit; + ctx_member_balance = ctx_member_balance + owner_profit; + ctx_profit_per_coin = compose_profit(ctx_profit_per_coin, cycleProfitPerCoin); + + ;; Persist + store_member(); + + return (); + } + + ;; Profit + if (profit > 0) { + + ;; Stakes + var owner_stake = ctx_member_balance; + var nominators_stake = ctx_balance - owner_stake; + + ;; Distribute profit + var cycleProfitPerCoin = profit * params::ppc_precision() * (100 * 100 - pool_fee) / (ctx_balance * 100 * 100); + var nominators_profit = (nominators_stake * cycleProfitPerCoin) / params::ppc_precision(); + var owner_profit = profit - nominators_profit; + + ;; Update balances + ctx_balance = ctx_balance + profit; + ctx_member_balance = ctx_member_balance + owner_profit; + ctx_profit_per_coin = compose_profit(ctx_profit_per_coin, cycleProfitPerCoin); + + ;; Persist + store_member(); + + return (); + } +} + +;; +;; Validator +;; + +() on_locked() impure { + if (~ ctx_locked) { + + ;; Allow locking only on no pending withdrawals + throw_unless(error::invalid_message(), ctx_balance_pending_withdraw == 0); + + ;; Update state + ctx_locked = true; + } +} + +() on_unlocked() impure { + if (ctx_locked) { + + ;; Update state + ctx_locked = false; + } +} + +int available_to_stake() { + return ctx_balance - ctx_balance_sent; +} + +int owned_balance() { + return ctx_balance - ctx_balance_sent + ctx_balance_pending_deposits + ctx_balance_withdraw + fees::storage_reserve(); +} + +() on_stake_sent(int stake) impure { + ctx_balance_sent = ctx_balance_sent + stake; +} + +() on_stake_sent_failed(int stake) impure { + ctx_balance_sent = ctx_balance_sent - stake; +} + +() on_stake_recovered(int stake) impure { + + ;; Calculate profit + ;; NOTE: ctx_locked is true meaning that ctx_balance + ;; have the same value as was when stake was sent + ;; balances are going to be unlocked after profit distribution + var profit = stake - ctx_balance_sent; + + ;; Distribute profit + distribute_profit(profit); + + ;; Reset sent amount + ctx_balance_sent = 0; +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-common.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-common.fc new file mode 100644 index 00000000..b83afdbd --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-common.fc @@ -0,0 +1,175 @@ +() op_deposit(int member, int value) impure { + + ;; Read extras + var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; + throw_unless(error::invalid_message(), enabled); + + ;; Read stake value + int fee = receipt_price + deposit_fee; + int stake = value - fee; + throw_unless(error::invalid_stake_value(), stake >= min_stake); + + ;; Load nominators + load_member(member); + + ;; Add deposit + member_stake_deposit(stake); + + ;; Resolve address + var address = ctx_owner; + if (member != owner_id()) { + address = serialize_work_addr(member); + } + + ;; Send receipt + if (ctx_query_id == 0) { + send_text_message( + address, + receipt_price, + send_mode::default(), + begin_cell() + .store_accepted_stake(stake) + ); + } else { + send_empty_std_message( + address, + receipt_price, + send_mode::default(), + op::stake_deposit::response(), + ctx_query_id + ); + } + + ;; Persist + store_member(); + store_base_data(); +} + +() op_withdraw(int member, int value, int stake) impure { + + ;; Read extras + var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; + + ;; Check fee + int fee = receipt_price + withdraw_fee; + + ;; Check value + throw_unless(error::too_low_value(), value == fee); + + ;; Load member + load_member(member); + + ;; Try to withdraw immediatelly + var (withdrawed, all) = member_stake_withdraw(stake); + + ;; Resolve address + var address = ctx_owner; + if (member != owner_id()) { + address = serialize_work_addr(member); + } + + ;; Send receipt + if (ctx_query_id == 0) { + send_text_message( + address, + withdrawed + receipt_price, + send_mode::default(), + all ? begin_cell().store_withdraw_completed() : begin_cell().store_withdraw_delayed() + ); + } else { + send_empty_std_message( + address, + withdrawed + receipt_price, + send_mode::default(), + all ? op::stake_withdraw::response() : op::stake_withdraw::delayed(), + ctx_query_id + ); + } + + ;; Persist + store_member(); + store_base_data(); +} + +() op_donate(int value) impure { + + ;; Check value + throw_unless(error::invalid_message(), value >= 2 * coins::1()); + + ;; Distribute profit to everyone + distribute_profit(value - coins::1()); + + ;; Persist + store_base_data(); +} + +() op_upgrade(int value, slice in_msg) impure { + + ;; Read extras + var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; + throw_unless(error::invalid_message(), udpates_enabled); + + ;; Check value + throw_unless(error::too_low_value(), value >= fees::deploy()); + + ;; Upgrade code + var code = in_msg~load_ref(); + in_msg.end_parse(); + set_code(code); + + ;; Send receipt + send_empty_std_message( + ctx_owner, + 0, + send_mode::carry_remaining_value(), + op::upgrade::response(), + ctx_query_id + ); +} + +() op_update(int value, slice in_msg) impure { + + ;; Read extras + var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; + + ;; Check value + throw_unless(error::too_low_value(), value >= fees::deploy()); + + ;; Check extras + var newExtras = in_msg~load_ref(); + var es = newExtras.begin_parse(); + var new_enabled = es~load_int(1); + var new_udpates_enabled = es~load_int(1); + var new_min_stake = es~load_coins(); + var new_deposit_fee = es~load_coins(); + var new_withdraw_fee = es~load_coins(); + var new_pool_fee = es~load_coins(); + var new_receipt_price = es~load_coins(); + es.end_parse(); + + ;; Once upgrades are disabled: prohibit re-enabling + throw_if(error::invalid_message(), (~ udpates_enabled) & new_udpates_enabled); + + ;; At least min_stake + throw_unless(error::invalid_message(), new_min_stake >= params::min_stake()); + ;; At least op fee + throw_unless(error::invalid_message(), new_deposit_fee >= fees::op()); + throw_unless(error::invalid_message(), new_withdraw_fee >= fees::op()); + ;; Must be in 0...10000 + throw_unless(error::invalid_message(), new_pool_fee <= 100 * 100); + ;; At least receipt price + throw_unless(error::invalid_message(), new_receipt_price >= fees::receipt()); + + ;; Persist extras + ctx_extras = (new_enabled, new_udpates_enabled, new_min_stake, new_deposit_fee, new_withdraw_fee, new_pool_fee, new_receipt_price); + store_base_data(); + + ;; Send receipt + send_empty_std_message( + ctx_owner, + 0, + send_mode::carry_remaining_value(), + op::update::response(), + ctx_query_id + ); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-controller.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-controller.fc new file mode 100644 index 00000000..98a8fe59 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-controller.fc @@ -0,0 +1,414 @@ +;; +;; Stake Sending +;; + +() op_controller_stake_send(int value, slice in_msg) impure { + + ;; Parse message + var stake = in_msg~load_coins(); + var validator_pubkey = in_msg~load_uint(256); + var stake_at = in_msg~load_uint(32); + var max_factor = in_msg~load_uint(32); + var adnl_addr = in_msg~load_uint(256); + var signature_ref = in_msg~load_ref(); + var signature = signature_ref.begin_parse().preload_bits(512); + in_msg.end_parse(); + + ;; Check message value + throw_unless(error::invalid_message(), value >= fees::stake_fees()); + + ;; Allow only single request to elector + if (proxy_stored_query_id != 0) { + throw(error::invalid_message()); + } + + ;; Allow update only for current stake + if ((proxy_stake_at != 0) & (proxy_stake_at != stake_at)) { + throw(error::invalid_message()); + } + + ;; Check stake value + var availableStake = available_to_stake(); + throw_unless(error::invalid_stake_value(), availableStake >= stake); + + ;; Parameters + var (electedFor, stakeHeldFor) = get_stake_parameters(); + + ;; Lock stakes + on_locked(); + + ;; Update operation state + proxy_stake_at = stake_at; + proxy_stake_until = stake_at + electedFor + stakeHeldFor; + proxy_stake_sent = proxy_stake_sent + stake; + proxy_stored_query_id = ctx_query_id; + proxy_stored_query_op = elector::stake::request(); + proxy_stored_query_stake = stake; + + ;; Update balances + on_stake_sent(stake); + + ;; Send message to elector + send_std_message( + ctx_proxy, + stake + coins::1(), + send_mode::separate_gas(), + elector::stake::request(), + proxy_stored_query_id, + begin_cell() + .store_uint(validator_pubkey, 256) + .store_uint(stake_at, 32) + .store_uint(max_factor, 32) + .store_uint(adnl_addr, 256) + .store_ref(signature_ref) + ); + + ;; Persist + store_validator_data(); + store_base_data(); +} + +() op_elector_stake_response(int value, slice in_msg) impure { + + ;; Check response + if (ctx_query_id != proxy_stored_query_id) { + ;; How to handle invalid? How it is possible? + return (); + } + if (proxy_stored_query_op != elector::stake::request()) { + ;; How to handle invalid? How it is possible? + return (); + } + + ;; Reset active query + proxy_stored_query_id = 0; + proxy_stored_query_op = 0; + proxy_stored_query_stake = 0; + + ;; Persist + store_validator_data(); + store_base_data(); +} + +() op_elector_stake_response_fail(int value, slice in_msg) impure { + + ;; Load reason + var reason = in_msg~load_uint(32); + + ;; Check response + if (ctx_query_id != proxy_stored_query_id) { + ;; How to handle invalid? How it is possible? + return (); + } + if (proxy_stored_query_op != elector::stake::request()) { + ;; How to handle invalid? How it is possible? + return (); + } + + ;; Update balances + on_stake_sent_failed(proxy_stored_query_stake); + + ;; Update proxy state + proxy_stake_sent = proxy_stake_sent - proxy_stored_query_stake; + + ;; Reset stake at since sent stake became zero + if (proxy_stake_sent == 0) { + proxy_stake_at = 0; + proxy_stake_until = 0; + proxy_stake_sent = 0; + on_unlocked(); + } + + ;; Reset query + proxy_stored_query_id = 0; + proxy_stored_query_op = 0; + proxy_stored_query_stake = 0; + + ;; Persist + store_validator_data(); + store_base_data(); +} + +;; +;; Recover +;; + +() op_stake_recover(int value) impure { + + ;; NOTE: We never block stake recover operation + ;; in case of misbehaviour of something anyone always can get + ;; coins from elector after lockup period is up + + ;; Allow request only if stake is exited lockup period + if ((proxy_stake_until != 0) & (now() < proxy_stake_until)) { + throw(error::invalid_message()); + } + + ;; Double check that validation session and lockup was lifted + if ((proxy_stake_until != 0) & (proxy_stake_at != 0)) { + throw_unless(error::invalid_message(), lockup_lift_time(proxy_stake_at, proxy_stake_until) <= now()); + } + + ;; Check value + throw_unless(error::too_low_value(), value >= fees::stake_fees()); + + ;; Send message to elector + send_empty_std_message( + ctx_proxy, + 0, + send_mode::carry_remaining_value(), + elector::refund::request(), + proxy_stored_query_id + ); + + ;; Persist + store_validator_data(); + store_base_data(); +} + +() op_elector_recover_response(int value, slice in_msg) impure { + + if ((proxy_stake_until != 0) & (now() > proxy_stake_until)) { + + ;; Reset state: all stake is returned + proxy_stake_sent = 0; + proxy_stake_at = 0; + proxy_stake_until = 0; + + ;; Reset query too + proxy_stored_query_id = 0; + proxy_stored_query_op = 0; + proxy_stored_query_stake = 0; + + ;; Handle stake recovered event + ;; NOTE: Any stake recovery outside this condition might be just a noise and + ;; effect of various race condirtions that doesn't carry any substantianal vakue + on_stake_recovered(value - fees::stake_fees()); + + ;; Reset lock state + ;; NOTE: MUST be after on_stake_recovered since it adjusts withdrawals and + ;; modifies global balance + on_unlocked(); + } + + ;; Persist + store_validator_data(); + store_base_data(); +} + +;; +;; Withdraw unowned +;; + +() op_controller_withdraw_unowned(int value, slice in_msg) impure { + + ;; Reserve owned + raw_reserve(owned_balance(), 0); + + ;; Send message to controller + send_empty_std_message( + ctx_controller, + 0, + send_mode::carry_remaining_balance(), + op::withdraw_unowned::response(), + ctx_query_id + ); +} + +;; +;; Process pending +;; + +() op_controller_accept_stakes(int value, slice in_msg) impure { + + ;; Check if enought value + throw_unless(error::invalid_message(), value >= params::pending_op()); + + ;; Check if not locked + throw_if(error::invalid_message(), ctx_locked); + + ;; Parse message + var members = in_msg~load_dict(); + in_msg.end_parse(); + + ;; Process operations + var member = -1; + do { + (member, var cs, var f) = members.udict_get_next?(256, member); + if (f) { + ;; Accept member's stake + load_member(member); + member_accept_stake(); + store_member(); + } + } until (~ f); + + ;; Persist + store_base_data(); +} + +() op_controller_accept_withdraws(int value, slice in_msg) impure { + + ;; Check if enought value + throw_unless(error::invalid_message(), value >= params::pending_op()); + + ;; Check if not locked + throw_if(error::invalid_message(), ctx_locked); + + ;; Parse message + var members = in_msg~load_dict(); + in_msg.end_parse(); + + ;; Process operations + var member = -1; + do { + (member, var cs, var f) = members.udict_get_next?(256, member); + if (f) { + ;; Accept member's stake + load_member(member); + member_accept_withdraw(); + store_member(); + } + } until (~ f); + + ;; Persist + store_base_data(); +} + +() op_controller_force_kick(int value, slice in_msg) impure { + + ;; Check if enought value + throw_unless(error::invalid_message(), value >= params::pending_op()); + + ;; Check if not locked + throw_if(error::invalid_message(), ctx_locked); + + ;; Parse message + var members = in_msg~load_dict(); + in_msg.end_parse(); + + ;; Process operations + var member = -1; + do { + (member, var cs, var f) = members.udict_get_next?(256, member); + if (f) { + + ;; Reject owner kicking + throw_if(error::invalid_message(), member == owner_id()); + + ;; Kick member from a pool + load_member(member); + + ;; Withdraw everything + var (withdrawed, all) = member_stake_withdraw(0); + throw_unless(error::invalid_message(), withdrawed > 0); + throw_unless(error::invalid_message(), all); + + ;; Forced kick + send_empty_std_message( + serialize_work_addr(member), + withdrawed, + send_mode::default(), + op::force_kick::notification(), + ctx_query_id + ); + + ;; Persist membership + store_member(); + } + } until (~ f); + + ;; Persist + store_base_data(); +} + +;; +;; Top Level +;; + +() op_controller(int flags, int value, slice in_msg) impure { + if (flags & 1) { + return (); + } + + ;; Check value + throw_unless(error::invalid_message(), value >= params::min_op()); + + ;; Parse operation + int op = in_msg~load_uint(32); + int query_id = in_msg~load_uint(64); + int gas_limit = in_msg~load_coins(); + set_gas_limit(gas_limit); + ctx_query_id = query_id; + throw_unless(error::invalid_message(), ctx_query_id > 0); + + ;; Send stake + if (op == op::stake_send()) { + op_controller_stake_send(value, in_msg); + return (); + } + + ;; Recover stake + if (op == op::stake_recover()) { + op_stake_recover(value); + return (); + } + + ;; Withdraw unowned + if (op == op::withdraw_unowned()) { + op_controller_withdraw_unowned(value, in_msg); + return (); + } + + ;; Accept stakes + if (op == op::accept_stakes()) { + op_controller_accept_stakes(value, in_msg); + return (); + } + + ;; Accept withdraws + if (op == op::accept_withdraws()) { + op_controller_accept_withdraws(value, in_msg); + return (); + } + + ;; Kick from pool + if (op == op::force_kick()) { + op_controller_force_kick(value, in_msg); + return (); + } + + ;; Unknown message + throw(error::invalid_message()); +} + +() op_elector(int flags, int value, slice in_msg) impure { + int op = in_msg~load_uint(32); + int query_id = in_msg~load_uint(64); + ctx_query_id = query_id; + + ;; Bounced + ;; It seems that handling doesn't make sence sicne there are no throws (?) + ;; in elector contract + if (flags & 1) { + return (); + } + + ;; Stake response + if (op == elector::stake::response()) { + op_elector_stake_response(value, in_msg); + return (); + } + if (op == elector::stake::response::fail()) { + op_elector_stake_response_fail(value, in_msg); + return (); + } + + ;; Refund response + if (op == elector::refund::response()) { + op_elector_recover_response(value, in_msg); + return (); + } + + ;; Ignore +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-nominators.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-nominators.fc new file mode 100644 index 00000000..233f1397 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-nominators.fc @@ -0,0 +1,104 @@ +(slice, (int)) ~parse_text_command(slice in_msg) { + int op = 0; + ;; 3 possible commands deposit, recover, withdraw + int first_char = in_msg~load_uint(8); + + ;; Deposit + if( first_char == 68 ) { ;; D + throw_unless(error::unknown_text_command(), in_msg~load_uint(48) == 111533580577140); ;; eposit + op = op::stake_deposit(); + } + + ;; Withdraw + if( first_char == 87 ) { ;; W + throw_unless(error::unknown_text_command(), in_msg~load_uint(56) == 29682864265257335); ;; ithdraw + op = op::stake_withdraw(); + } + + ;; Recover + if( first_char == 82 ) { ;; R + throw_unless(error::unknown_text_command(), in_msg~load_uint(48) == 111477746197874); ;; ecover + op = op::stake_recover(); + } + + return (in_msg, (op)); +} + +() op_nominators(int member, int flags, int value, slice in_msg) impure { + + ;; Ignore bounced + if (flags & 1) { + return (); + } + + ;; Check value + throw_unless(error::invalid_message(), value >= params::min_op()); + + ;; Parse operation + int op = in_msg~load_uint(32); + + ;; Text operations + if (op == 0) { + ctx_query_id = 0; + op = in_msg~parse_text_command(); + + ;; Deposit stake + if (op == op::stake_deposit()) { + op_deposit(member, value); + return (); + } + + ;; Withdraw stake + if (op == op::stake_withdraw()) { + op_withdraw(member, value, 0); + return (); + } + + ;; Recover stake + if (op == op::stake_recover()) { + load_validator_data(); + op_stake_recover(value); + return (); + } + + ;; Unknown message + throw(error::invalid_message()); + return (); + } + + int query_id = in_msg~load_uint(64); + int gas_limit = in_msg~load_coins(); + set_gas_limit(gas_limit); + ctx_query_id = query_id; + throw_unless(error::invalid_message(), ctx_query_id > 0); + + ;; Deposit stake + if (op == op::stake_deposit()) { + op_deposit(member, value); + return (); + } + + ;; Withdraw stake + if (op == op::stake_withdraw()) { + int stake = in_msg~load_coins(); + in_msg.end_parse(); + op_withdraw(member, value, stake); + return (); + } + + ;; Recover stake + if (op == op::stake_recover()) { + load_validator_data(); + op_stake_recover(value); + return (); + } + + ;; Donate stake + if (op == op::donate()) { + op_donate(value); + return (); + } + + ;; Unknown message + throw(error::invalid_message()); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-owner.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-owner.fc new file mode 100644 index 00000000..231e6889 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/op-owner.fc @@ -0,0 +1,60 @@ +() op_owner(int flags, int value, slice in_msg) impure { + + ;; Ignore bounced + if (flags & 1) { + return (); + } + + ;; Check value + throw_unless(error::invalid_message(), value >= params::min_op()); + + ;; Parse operation + int op = in_msg~load_uint(32); + int query_id = in_msg~load_uint(64); + int gas_limit = in_msg~load_coins(); + set_gas_limit(gas_limit); + ctx_query_id = query_id; + throw_unless(error::invalid_message(), ctx_query_id > 0); + + ;; Upgrade + if (op == op::upgrade()) { + op_upgrade(value, in_msg); + return (); + } + + ;; Upgrade + if (op == op::update()) { + op_update(value, in_msg); + return (); + } + + ;; Add stake + if (op == op::stake_deposit()) { + op_deposit(owner_id(), value); + return (); + } + + ;; Withdraw stake + if (op == op::stake_withdraw()) { + int stake = in_msg~load_coins(); + in_msg.end_parse(); + op_withdraw(owner_id(), value, stake); + return (); + } + + ;; Recover stake + if (op == op::stake_recover()) { + load_validator_data(); + op_stake_recover(value); + return (); + } + + ;; Donate stake + if (op == op::donate()) { + op_donate(value); + return (); + } + + ;; Unknown message + throw(error::invalid_message()); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-base.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-base.fc new file mode 100644 index 00000000..989c1fd0 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-base.fc @@ -0,0 +1,99 @@ +global int ctx_query_id; + +global int ctx_locked; +global slice ctx_owner; +global slice ctx_controller; +global slice ctx_proxy; +global cell ctx_proxy_state; + +global int ctx_profit_per_coin; +global int ctx_balance; +global int ctx_balance_sent; +global int ctx_balance_withdraw; +global int ctx_balance_pending_withdraw; +global int ctx_balance_pending_deposits; + +global cell ctx_nominators; + +;; var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; +global (int, int, int, int, int, int, int) ctx_extras; + +() load_base_data() impure { + var ds = get_data().begin_parse(); + ctx_locked = ds~load_int(1); + + ctx_owner = ds~load_msg_addr(); + ctx_controller = ds~load_msg_addr(); + ctx_proxy = ds~load_msg_addr(); + + cell balance_cell = ds~load_ref(); + ctx_nominators = ds~load_dict(); + ctx_proxy_state = ds~load_ref(); + cell extras_cell = null(); + if (ds.slice_refs() > 0) { + extras_cell = ds~load_ref(); + } + ds.end_parse(); + + var bs = balance_cell.begin_parse(); + ctx_profit_per_coin = bs~load_int(128); + ctx_balance = bs~load_coins(); + ctx_balance_sent = bs~load_coins(); + ctx_balance_withdraw = bs~load_coins(); + ctx_balance_pending_withdraw = bs~load_coins(); + ctx_balance_pending_deposits = bs~load_coins(); + bs.end_parse(); + + ;; Parsing extras (enabled, upgrades_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) + ctx_extras = ( + true, ;; Enabled + true, ;; Upgrades enabled + params::min_stake(), ;; Min Stake + fees::op(), ;; Deposit fee + fees::op(), ;; Withdraw fee + 10 * 100, ;; Pool fee (%), + fees::receipt() + ); + if (~ extras_cell.null?()) { + var ec = extras_cell.begin_parse(); + var enabled = ec~load_int(1); + var udpates_enabled = ec~load_int(1); + var min_stake = ec~load_coins(); + var deposit_fee = ec~load_coins(); + var withdraw_fee = ec~load_coins(); + var pool_fee = ec~load_coins(); + var receipt_price = ec~load_coins(); + ctx_extras = (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price); + ec.end_parse(); + } +} + +() store_base_data() impure { + var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras; + set_data(begin_cell() + .store_int(ctx_locked, 1) + .store_slice(ctx_owner) + .store_slice(ctx_controller) + .store_slice(ctx_proxy) + .store_ref(begin_cell() + .store_int(ctx_profit_per_coin, 128) + .store_coins(ctx_balance) + .store_coins(ctx_balance_sent) + .store_coins(ctx_balance_withdraw) + .store_coins(ctx_balance_pending_withdraw) + .store_coins(ctx_balance_pending_deposits) + .end_cell()) + .store_dict(ctx_nominators) + .store_ref(ctx_proxy_state) + .store_ref(begin_cell() + .store_int(enabled, 1) + .store_int(udpates_enabled, 1) + .store_coins(min_stake) + .store_coins(deposit_fee) + .store_coins(withdraw_fee) + .store_coins(pool_fee) + .store_coins(receipt_price) + .end_cell()) + .end_cell()); + commit(); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-nominators.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-nominators.fc new file mode 100644 index 00000000..aa5ba3c7 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-nominators.fc @@ -0,0 +1,62 @@ +;; +;; Members +;; + +global int ctx_member; +global int ctx_member_balance; +global int ctx_member_pending_withdraw; +global int ctx_member_pending_withdraw_all; +global int ctx_member_pending_deposit; +global int ctx_member_withdraw; +global int ctx_member_profit_per_coin; +global int ctx_member_exist; + +slice load_member_slice(slice cs) impure { + ctx_member_profit_per_coin = cs~load_int(128); + ctx_member_balance = cs~load_coins(); + ctx_member_pending_withdraw = cs~load_coins(); + ctx_member_pending_withdraw_all = cs~load_int(1); + ctx_member_pending_deposit = cs~load_coins(); + ctx_member_withdraw = cs~load_coins(); + ctx_member_exist = true; + return cs; +} + +() load_member(int member) impure { + var (cs, found) = ctx_nominators.udict_get?(256, member); + ctx_member = member; + if (found) { + cs = load_member_slice(cs); + cs.end_parse(); + ctx_member_exist = true; + } else { + ctx_member_balance = 0; + ctx_member_pending_withdraw = 0; + ctx_member_pending_withdraw_all = false; + ctx_member_pending_deposit = 0; + ctx_member_profit_per_coin = 0; + ctx_member_withdraw = 0; + ctx_member_exist = false; + } +} + +() store_member() impure { + var shouldExist = (ctx_member_balance > 0) | (ctx_member_pending_deposit > 0) | (ctx_member_withdraw > 0); + if ((~ shouldExist) & ctx_member_exist) { + ;; Compiler crashes when single lined + var (changed, _) = udict_delete?(ctx_nominators, 256, ctx_member); + ctx_nominators = changed; + } elseif (shouldExist) { + var data = begin_cell() + .store_int(ctx_member_profit_per_coin, 128) + .store_coins(ctx_member_balance) + .store_coins(ctx_member_pending_withdraw) + .store_int(ctx_member_pending_withdraw_all, 1) + .store_coins(ctx_member_pending_deposit) + .store_coins(ctx_member_withdraw); + + ;; Compiler crashes when single lined + var changed = udict_set_builder(ctx_nominators, 256, ctx_member, data); + ctx_nominators = changed; + } +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-validator.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-validator.fc new file mode 100644 index 00000000..a50cb79f --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/store-validator.fc @@ -0,0 +1,28 @@ +global int proxy_stake_at; +global int proxy_stake_until; +global int proxy_stake_sent; +global int proxy_stored_query_id; +global int proxy_stored_query_op; +global int proxy_stored_query_stake; + +() load_validator_data() impure { + var cs = ctx_proxy_state.begin_parse(); + proxy_stake_at = cs~load_uint(32); + proxy_stake_until = cs~load_uint(32); + proxy_stake_sent = cs~load_coins(); + proxy_stored_query_id = cs~load_uint(64); + proxy_stored_query_op = cs~load_uint(32); + proxy_stored_query_stake = cs~load_coins(); + cs.end_parse(); +} + +() store_validator_data() impure { + ctx_proxy_state = begin_cell() + .store_uint(proxy_stake_at, 32) + .store_uint(proxy_stake_until, 32) + .store_coins(proxy_stake_sent) + .store_uint(proxy_stored_query_id, 64) + .store_uint(proxy_stored_query_op, 32) + .store_coins(proxy_stored_query_stake) + .end_cell(); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils-config-mock.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils-config-mock.fc new file mode 100644 index 00000000..bc464217 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils-config-mock.fc @@ -0,0 +1,3 @@ +(int, int) get_stake_parameters() { + return (1000, 100); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils-config.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils-config.fc new file mode 100644 index 00000000..84e07e01 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils-config.fc @@ -0,0 +1,44 @@ +(int, int) get_stake_parameters() { + var cs = config_param(15).begin_parse(); + int electedFor = cs~load_uint(32); + cs~skip_bits(64); + int stakeHeldFor = cs~load_uint(32); + return (electedFor, stakeHeldFor); +} + +(int, int) get_previous_cycle() { + var cs = config_param(32).begin_parse(); + cs~skip_bits(8); ;; Header + int timeSince = cs~load_uint(32); + int timeUntil = cs~load_uint(32); + return (timeSince, timeUntil); +} + +(int, int) get_current_cycle() { + var cs = config_param(34).begin_parse(); + cs~skip_bits(8); ;; Header + int timeSince = cs~load_uint(32); + int timeUntil = cs~load_uint(32); + return (timeSince, timeUntil); +} + +int lockup_lift_time(int stake_at, int stake_untill) { + + ;; Resolve previous cycle parameters + var (timeSince, timeUntil) = get_previous_cycle(); + + ;; If previous cycle looks as a valid one + if (stake_at <= timeSince) { + return timeSince + (stake_untill - stake_at); + } + + ;; Check current cycle + var (timeSince, timeUntil) = get_current_cycle(); + + ;; If current cycle could be the one we joined validation + if (stake_at <= timeSince) { + return timeSince + (stake_untill - stake_at); + } + + return stake_untill; +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils.fc new file mode 100644 index 00000000..50e4e57c --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/modules/utils.fc @@ -0,0 +1,185 @@ +;; +;; Basic workchain addresses +;; + +int parse_work_addr(slice cs) { + (int sender_wc, slice sender_addr) = parse_var_addr(cs); + throw_unless(error::invalid_address(), 0 == sender_wc); + return sender_addr~load_uint(256); +} + +(slice) serialize_work_addr(int addr) { + return (begin_cell() + .store_uint(2, 2) ;; Is std address + .store_uint(0, 1) ;; Non-unicast + .store_uint(0, 8) ;; Basic workchain + .store_uint(addr, 256) ;; Address hash + ).end_cell().begin_parse(); +} + +;; +;; Custom Commands +;; + +(int) equal_slices (slice s1, slice s2) asm "SDEQ"; +builder store_builder(builder to, builder what) asm(what to) "STB"; +builder store_builder_ref(builder to, builder what) asm(what to) "STBREFR"; +(slice, cell) load_maybe_cell(slice s) asm( -> 1 0) "LDDICT"; +(int) mod (int x, int y) asm "MOD"; +builder store_coins(builder b, int x) asm "STGRAMS"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; + + +;; +;; Events +;; + +() send_std_message( + slice to_addr, + int value, + int mode, + int op, + int query_id, + builder content +) impure { + + var body = begin_cell() + .store_uint(op, 32) + .store_uint(query_id, 64) + .store_builder(content) + .end_cell(); + + var msg = begin_cell() + .store_uint(0x10, 6) + .store_slice(to_addr) + .store_coins(value) + .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_ref(body) + .end_cell(); + + send_raw_message(msg, mode); +} + +() send_empty_std_message( + slice to_addr, + int value, + int mode, + int op, + int query_id +) impure { + + var body = begin_cell() + .store_uint(op, 32) + .store_uint(query_id, 64) + .end_cell(); + + var msg = begin_cell() + .store_uint(0x10, 6) + .store_slice(to_addr) + .store_coins(value) + .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_ref(body) + .end_cell(); + + send_raw_message(msg, mode); +} + +() send_text_message( + slice to_addr, + int value, + int mode, + builder content +) impure { + + var body = begin_cell() + .store_uint(0, 32) + .store_builder(content) + .end_cell(); + + var msg = begin_cell() + .store_uint(0x10, 6) + .store_slice(to_addr) + .store_coins(value) + .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_ref(body) + .end_cell(); + + send_raw_message(msg, mode); +} + +;; +;; Generate +;; + +(int) new_query_id() inline { + return now() + mod(cur_lt(), 4294967296); +} + +;; +;; Text Utils +;; + +(int, int) encode_number_to_text(int number) { + int len = 0; + int value = 0; + int mult = 1; + do { + (number, int res) = number.divmod(10); + value = value + (res + 48) * mult; + mult = mult * 256; + len = len + 1; + } until (number == 0); + return (len, value); +} + +builder store_coins_string(builder msg, int amount) { + (int ceil, int res) = divmod(amount, 1000000000); + (int cl, int cv) = encode_number_to_text(ceil); + msg = msg.store_uint(cv, cl * 8 ); + msg = msg.store_uint(46, 8); ;; "." + (int rl, int rv) = encode_number_to_text(res); + ;; repeat( 9 - rl ) { + ;; msg = msg.store_uint(48, 8); ;; " " + ;; } + return msg.store_uint(rv, rl * 8); +} + + +;; 'Stake' +builder store_text_stake(builder b) inline { + return b.store_uint(358434827109, 40); +} + +;; ' ' +builder store_text_space(builder b) inline { + return b.store_uint(32, 8); +} + +;; 'accepted' +builder store_text_accepted(builder b) inline { + return b.store_uint(7017561931702887780, 64); +} + +;; Stake 123.333 accepted +builder store_accepted_stake(builder b, int amount) inline { + return b.store_text_stake() + .store_text_space() + .store_coins_string(amount) + .store_text_space() + .store_text_accepted(); +} + +;; 'Withdraw completed' +builder store_withdraw_completed(builder b) inline { + return b.store_uint(7614653257073527469736132165096662684165476, 144); +} + +;; 'Withdraw requested. Please, retry the command when your balance is ready.' +builder store_withdraw_delayed(builder b) inline { + return b + .store_uint(1949351233810823032252520485584178069312463918, 152) ;; 'Withdraw requested.' + .store_text_space() + .store_uint(555062058613674355757418046597367430905687018487295295368960255172568430, 240) ;; 'Please, retry the command when' + .store_text_space() + .store_uint(45434371896731988359547695118970428857702208118225198, 176); ;; 'your balance is ready.' +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/nominators.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/nominators.fc new file mode 100644 index 00000000..8dee4e5e --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/nominators.fc @@ -0,0 +1,52 @@ +#include "stdlib.fc"; +#include "modules/constants.fc"; +#include "modules/utils-config.fc"; +#include "modules/utils.fc"; +;; #include "modules/utils-config-mock.fc"; +#include "modules/store-base.fc"; +#include "modules/store-nominators.fc"; +#include "modules/store-validator.fc"; +#include "modules/model.fc"; +#include "modules/op-controller.fc"; +#include "modules/op-owner.fc"; +#include "modules/op-common.fc"; +#include "modules/op-nominators.fc"; +#include "modules/get.fc"; + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + + ;; Prepare message context + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + slice s_addr = cs~load_msg_addr(); + load_base_data(); + + ;; Handle controller messages + if (equal_slices(s_addr, ctx_controller)) { + load_validator_data(); + op_controller(flags, msg_value, in_msg); + return (); + } + + ;; Handle elector messages + if (equal_slices(s_addr, ctx_proxy)) { + load_validator_data(); + op_elector(flags, msg_value, in_msg); + return (); + } + + ;; Handle owner messages + if (equal_slices(s_addr, ctx_owner)) { + op_owner(flags, msg_value, in_msg); + return (); + } + + ;; Nominators + var address = parse_work_addr(s_addr); + op_nominators(address, flags, msg_value, in_msg); +} + +() recv_external(slice in_msg) impure { + ;; Do not accept external messages + throw(error::invalid_message()); +} diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/proxy.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/proxy.fc new file mode 100644 index 00000000..805b6098 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/proxy.fc @@ -0,0 +1,67 @@ +(int) equal_slices (slice s1, slice s2) asm "SDEQ"; + +() recv_internal(cell in_msg_cell, slice in_msg) { + + ;; Parse message + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + slice s_addr = cs~load_msg_addr(); + + ;; Parse data + var ds = get_data().begin_parse(); + slice address_0 = ds~load_msg_addr(); + slice address_1 = ds~load_msg_addr(); + ds~skip_bits(64); + ds.end_parse(); + + ;; Resolve addresses address + slice src = null(); + slice dst = null(); + if (equal_slices(s_addr, address_0)) { + src = address_0; + dst = address_1; + } elseif (equal_slices(s_addr, address_1)) { + src = address_1; + dst = address_0; + } + + ;; Bounce while keeping storage fee on unknown + ;; Useful fro deploy + if (null?(src)) { + raw_reserve(1000000000, 2); + var msg = begin_cell() + .store_uint(0x10, 6) + .store_slice(s_addr) + .store_grams(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .end_cell(); + send_raw_message(msg, 128); + return (); + } + + ;; Process messages + raw_reserve(1000000000, 2); + var msg = begin_cell() + .store_uint(flags, 4) + .store_uint(0, 2) + .store_slice(dst) + .store_grams(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1); + + ;; Content + if(msg.builder_bits() + 1 + in_msg.slice_bits() > 1023) { + msg = msg.store_uint(1,1) + .store_ref(begin_cell().store_slice(in_msg).end_cell()); + } else { + msg = msg.store_uint(0,1) + .store_slice(in_msg); + } + + ;; Send message + send_raw_message(msg.end_cell(), 128); +} + +() recv_external(slice in_msg) impure { + ;; Do not accept external messages + throw(72); +} \ No newline at end of file diff --git a/crypto/func/auto-tests/legacy_tests/whales-nominators/stdlib.fc b/crypto/func/auto-tests/legacy_tests/whales-nominators/stdlib.fc new file mode 100644 index 00000000..dcc5f423 --- /dev/null +++ b/crypto/func/auto-tests/legacy_tests/whales-nominators/stdlib.fc @@ -0,0 +1,212 @@ +;; Standard library for funC +;; + +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> X car(tuple list) asm "CAR"; +tuple cdr(tuple list) asm "CDR"; +tuple empty_tuple() asm "NIL"; +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +forall X -> [X] single(X x) asm "SINGLE"; +forall X -> X unsingle([X] t) asm "UNSINGLE"; +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +forall X -> X first(tuple t) asm "FIRST"; +forall X -> X second(tuple t) asm "SECOND"; +forall X -> X third(tuple t) asm "THIRD"; +forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +forall X -> X null() asm "PUSHNULL"; +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + +int now() asm "NOW"; +slice my_address() asm "MYADDR"; +[int, cell] get_balance() asm "BALANCE"; +int cur_lt() asm "LTIME"; +int block_lt() asm "BLOCKLT"; + +int cell_hash(cell c) asm "HASHCU"; +int slice_hash(slice s) asm "HASHSU"; +int string_hash(slice s) asm "SHA256U"; + +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +() dump_stack() impure asm "DUMPSTK"; + +cell get_data() asm "c4 PUSH"; +() set_data(cell c) impure asm "c4 POP"; +cont get_c3() impure asm "c3 PUSH"; +() set_c3(cont c) impure asm "c3 POP"; +cont bless(slice s) impure asm "BLESS"; + +() accept_message() impure asm "ACCEPT"; +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +() commit() impure asm "COMMIT"; +() buy_gas(int gram) impure asm "BUYGAS"; + +int min(int x, int y) asm "MIN"; +int max(int x, int y) asm "MAX"; +(int, int) minmax(int x, int y) asm "MINMAX"; +int abs(int x) asm "ABS"; + +slice begin_parse(cell c) asm "CTOS"; +() end_parse(slice s) impure asm "ENDS"; +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +cell preload_ref(slice s) asm "PLDREF"; +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +;; int preload_int(slice s, int len) asm "PLDIX"; +;; int preload_uint(slice s, int len) asm "PLDUX"; +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +slice slice_last(slice s, int len) asm "SDCUTLAST"; +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +cell preload_dict(slice s) asm "PLDDICT"; +slice skip_dict(slice s) asm "SKIPDICT"; + +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +int cell_depth(cell c) asm "CDEPTH"; + +int slice_refs(slice s) asm "SREFS"; +int slice_bits(slice s) asm "SBITS"; +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +int slice_empty?(slice s) asm "SEMPTY"; +int slice_data_empty?(slice s) asm "SDEMPTY"; +int slice_refs_empty?(slice s) asm "SREMPTY"; +int slice_depth(slice s) asm "SDEPTH"; + +int builder_refs(builder b) asm "BREFS"; +int builder_bits(builder b) asm "BBITS"; +int builder_depth(builder b) asm "BDEPTH"; + +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; + builder store_ref(builder b, cell c) asm(c b) "STREF"; +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +builder store_slice(builder b, slice s) asm "STSLICER"; +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell new_dict() asm "NEWDICT"; +int dict_empty?(cell c) asm "DICTEMPTY"; + +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +cell config_param(int x) asm "CONFIGOPTPARAM"; +int cell_null?(cell c) asm "ISNULL"; + +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +() set_code(cell new_code) impure asm "SETCODE"; + +int random() impure asm "RANDU256"; +int rand(int range) impure asm "RAND"; +int get_seed() impure asm "RANDSEED"; +int set_seed() impure asm "SETRAND"; +() randomize(int x) impure asm "ADDRAND"; +() randomize_lt() impure asm "LTIME" "ADDRAND"; + + +int equal_slices (slice a, slice b) asm "SDEQ"; +int builder_null?(builder b) asm "ISNULL"; diff --git a/crypto/func/auto-tests/run_tests.js b/crypto/func/auto-tests/run_tests.js new file mode 100644 index 00000000..f8e6c6a7 --- /dev/null +++ b/crypto/func/auto-tests/run_tests.js @@ -0,0 +1,77 @@ +const fs = require('fs/promises'); +const os = require('os'); +const path = require('path'); +const { compileWasm, compileFile } = require('./wasm_tests_common'); +const { execSync } = require('child_process'); + +async function main() { + const compiledPath = path.join(os.tmpdir(), 'compiled.fif'); + const runnerPath = path.join(os.tmpdir(), 'runner.fif'); + + const tests = (await fs.readdir('.')).filter(f => f.endsWith('.fc')).sort(); + + const mathChars = '0x123456789()+-*/<>'.split('') + + for (const testFile of tests) { + const mod = await compileWasm() + + const result = await compileFile(mod, testFile) + + if (result.status !== 'ok') { + console.error(result); + throw new Error('Could not compile ' + filename); + } + + const fileLines = (await fs.readFile(testFile)).toString('utf-8').split('\n'); + + const testCases = []; + + for (const line of fileLines) { + const parts = line.split('|').map(c => c.trim()); + + if (parts.length !== 4 || parts[0] !== 'TESTCASE') continue; + + const processedInputs = []; + + for (const input of parts[2].split(' ')) { + if (input.includes('x{')) { + processedInputs.push(input); + continue; + } + + if (input.length === 0) { + continue + } + + const replacedInput = input.split('').filter(c => mathChars.includes(c)).join('').replace('//', '/').replace(/([0-9a-f])($|[^0-9a-fx])/gmi, '$1n$2') + + processedInputs.push(eval(replacedInput).toString()); + } + + testCases.push([parts[1], processedInputs.join(' '), parts[3]]); + } + + await fs.writeFile(compiledPath, '"Asm.fif" include\n' + JSON.parse('"' + result.fiftCode + '"')); + await fs.writeFile(runnerPath, `"${compiledPath}" include `${t[1]} ${t[0]} code 1 runvmx abort"exitcode is not 0" .s cr { drop } depth 1- times`).join('\n')}`) + + const fiftResult = execSync(`${process.env.FIFT_EXECUTABLE || 'fift'} -I ${process.env.FIFT_LIBS} /tmp/runner.fif`, { + stdio: ['pipe', 'pipe', 'ignore'] + }).toString('utf-8') + + const testResults = fiftResult.split('\n').map(s => s.trim()).filter(s => s.length > 0) + + if (testResults.length !== testCases.length) { + throw new Error(`Got ${testResults.length} results but there are ${testCases.length} cases`) + } + + for (let i = 0; i < testResults.length; i++) { + if (testResults[i] !== testCases[i][2]) { + throw new Error(`Unequal result ${testResults[i]} and case ${testCases[i][2]}`) + } + } + + console.log(testFile, 'ok') + } +} + +main() \ No newline at end of file diff --git a/crypto/func/auto-tests/run_tests.py b/crypto/func/auto-tests/run_tests.py new file mode 100644 index 00000000..158e871b --- /dev/null +++ b/crypto/func/auto-tests/run_tests.py @@ -0,0 +1,110 @@ +import os +import os.path +import subprocess +import sys +import tempfile + + +def getenv(name, default=None): + if name in os.environ: + return os.environ[name] + if default is None: + print("Environment variable", name, "is not set", file=sys.stderr) + exit(1) + return default + + +FUNC_EXECUTABLE = getenv("FUNC_EXECUTABLE", "func") +FIFT_EXECUTABLE = getenv("FIFT_EXECUTABLE", "fift") +TMP_DIR = tempfile.mkdtemp() +COMPILED_FIF = os.path.join(TMP_DIR, "compiled.fif") +RUNNER_FIF = os.path.join(TMP_DIR, "runner.fif") + +if len(sys.argv) != 2: + print("Usage : run_tests.py tests_dir", file=sys.stderr) + exit(1) +TESTS_DIR = sys.argv[1] + + +class ExecutionError(Exception): + pass + + +def compile_func(f): + res = subprocess.run([FUNC_EXECUTABLE, "-o", COMPILED_FIF, "-SPA", f], capture_output=True, timeout=10) + if res.returncode != 0: + raise ExecutionError(str(res.stderr, "utf-8")) + + +def run_runner(): + res = subprocess.run([FIFT_EXECUTABLE, RUNNER_FIF], capture_output=True, timeout=10) + if res.returncode != 0: + raise ExecutionError(str(res.stderr, "utf-8")) + s = str(res.stdout, "utf-8") + s = [x.strip() for x in s.split("\n")] + return [x for x in s if x != ""] + + +tests = [s for s in os.listdir(TESTS_DIR) if s.endswith(".fc")] +tests.sort() +print("Found", len(tests), "tests", file=sys.stderr) +for ti, tf in enumerate(tests): + print("Running test %d/%d: %s" % (ti + 1, len(tests), tf), file=sys.stderr) + tf = os.path.join(TESTS_DIR, tf) + try: + compile_func(tf) + except ExecutionError as e: + print(file=sys.stderr) + print("Compilation error", file=sys.stderr) + print(e, file=sys.stderr) + exit(2) + with open(tf, "r") as fd: + lines = fd.readlines() + cases = [] + for s in lines: + s = [x.strip() for x in s.split("|")] + if len(s) == 4 and s[0].strip() == "TESTCASE": + cases.append(s[1:]) + if len(cases) == 0: + print(file=sys.stderr) + print("Error: no test cases", file=sys.stderr) + exit(2) + + # preprocess arithmetics in input + for i in range(len(cases)): + inputs = cases[i][1].split(" ") + processed_inputs = "" + for in_arg in inputs: + if "x{" in in_arg: + processed_inputs += in_arg + continue + # filter and execute + # is it safe enough? + filtered_in = "".join(filter(lambda x: x in "0x123456789()+-*/<>", in_arg)) + if filtered_in: + processed_inputs += str(eval(filtered_in)) + " " + cases[i][1] = processed_inputs.strip() + + with open(RUNNER_FIF, "w") as f: + print("\"%s\" include = %d);" % (var, self.n), file=f) + else: + var = gen_var_name() + print(" " * indent + "int %s = %d;" % (var, self.n - 1), file=f) + print(" " * indent + "while (%s >= 0) {" % var, file=f) + self.c.write(f, indent + 1) + print(" " * (indent + 1) + "%s -= 1;" % var, file=f) + print(" " * indent + "}", file=f) + +class CodeThrow(Code): + def __init__(self): + pass + + def execute(self, state): + return "EXCEPTION" + + def write(self, f, indent=0): + print(" " * indent + "throw(42);", file=f) + +class CodeTryCatch(Code): + def __init__(self, c1, c2): + self.c1 = c1 + self.c2 = c2 + + def execute(self, state): + state0 = state.copy() + res = self.c1.execute(state) + if res == "EXCEPTION": + state.copy_from(state0) + return self.c2.execute(state) + else: + return res + + def write(self, f, indent=0): + print(" " * indent + "try {", file=f) + self.c1.write(f, indent + 1) + print(" " * indent + "} catch (_, _) {", file=f) + self.c2.write(f, indent + 1) + print(" " * indent + "}", file=f) + +def write_function(f, name, body, inline=False, inline_ref=False, method_id=None): + print("_ %s(int x)" % name, file=f, end="") + if inline: + print(" inline", file=f, end="") + if inline_ref: + print(" inline_ref", file=f, end="") + if method_id is not None: + print(" method_id(%d)" % method_id, file=f, end="") + print(" {", file=f) + for i in range(VAR_CNT): + print(" int v%d = 0;" % i, file=f) + body.write(f, 1) + print("}", file=f) + +def gen_code(xl, xr, with_return, loop_depth=0, try_catch_depth=0, can_throw=False): + if try_catch_depth < 3 and random.randint(0, 5) == 0: + c1 = gen_code(xl, xr, with_return, loop_depth, try_catch_depth + 1, random.randint(0, 1) == 0) + c2 = gen_code(xl, xr, with_return, loop_depth, try_catch_depth + 1, can_throw) + return CodeTryCatch(c1, c2) + code = [] + for _ in range(random.randint(0, 2)): + if random.randint(0, 3) == 0 and loop_depth < 3: + c = gen_code(xl, xr, False, loop_depth + 1, try_catch_depth, can_throw) + code.append(CodeRepeat(random.randint(0, 3), c, random.randint(0, 3))) + elif xr - xl > 1: + xmid = random.randrange(xl + 1, xr) + ret = random.choice((0, 0, 0, 0, 0, 1, 2)) + c1 = gen_code(xl, xmid, ret == 1, loop_depth, try_catch_depth, can_throw) + if random.randrange(5) == 0: + c2 = CodeEmpty() + else: + c2 = gen_code(xmid, xr, ret == 2, loop_depth, try_catch_depth, can_throw) + code.append(CodeIfRange(xl, xmid, c1, c2)) + if xr - xl == 1 and can_throw and random.randint(0, 5) == 0: + code.append(CodeThrow()) + if with_return: + if xr - xl == 1: + code.append(CodeReturn(random.randrange(10**9))) + else: + xmid = random.randrange(xl + 1, xr) + c1 = gen_code(xl, xmid, True, loop_depth, try_catch_depth, can_throw) + c2 = gen_code(xmid, xr, True, loop_depth, try_catch_depth, can_throw) + code.append(CodeIfRange(xl, xmid, c1, c2)) + for _ in range(random.randint(0, 3)): + pos = random.randint(0, len(code)) + code.insert(pos, CodeAdd(random.randrange(VAR_CNT), random.randint(0, 10**6))) + if len(code) == 0: + return CodeEmpty() + return CodeBlock(code) + +class ExecutionError(Exception): + pass + +def compile_func(fc, fif): + res = subprocess.run([FUNC_EXECUTABLE, "-o", fif, "-SPA", fc], capture_output=True) + if res.returncode != 0: + raise ExecutionError(str(res.stderr, "utf-8")) + +def runvm(compiled_fif, xl, xr): + runner = os.path.join(TMP_DIR, "runner.fif") + with open(runner, "w") as f: + print("\"%s\" include a) { + x -= 1; + z = 1; + } + return (y, z); +} + +{- + method_id | in | out +TESTCASE | 0 | 101 15 | 100 1 +TESTCASE | 0 | 101 14 | 100 1 +TESTCASE | 0 | 101 10 | 100 0 +TESTCASE | 0 | 100 10 | 100 0 +-} diff --git a/crypto/func/auto-tests/tests/a6.fc b/crypto/func/auto-tests/tests/a6.fc new file mode 100644 index 00000000..05a49fab --- /dev/null +++ b/crypto/func/auto-tests/tests/a6.fc @@ -0,0 +1,89 @@ +(int, int) f(int a, int b, int c, int d, int e, int f) { + ;; solve a 2x2 linear equation + int D = a * d - b * c; + int Dx = e * d - b * f; + int Dy = a * f - e * c; + return (Dx / D, Dy / D); +} + +int calc_phi() { + var n = 1; + repeat (70) { n *= 10; } + var p = var q = 1; + do { + (p, q) = (q, p + q); + } until (q > n); + return muldivr(p, n, q); +} + +int calc_sqrt2() { + var n = 1; + repeat (70) { n *= 10; } + var p = var q = 1; + do { + var t = p + q; + (p, q) = (q, t + q); + } until (q > n); + return muldivr(p, n, q); +} + +var calc_root(m) { + int base = 1; + repeat(70) { base *= 10; } + var (a, b, c) = (1, 0, - m); + var (p1, q1, p2, q2) = (1, 0, 0, 1); + do { + int k = -1; + var (a1, b1, c1) = (0, 0, 0); + do { + k += 1; + (a1, b1, c1) = (a, b, c); + c += b; + c += b += a; + } until (c > 0); + (a, b, c) = (- c1, - b1, - a1); + (p1, q1) = (k * p1 + q1, p1); + (p2, q2) = (k * p2 + q2, p2); + } until (p1 > base); + return (p1, q1, p2, q2); +} + +{- +operator _/%_ infix 20; + +(int, int) ((int x) /% (int y)) { + return (x / y, x % y); +} + +(int, int) _/%_ (int x, int y) { + return (x / y, x % y); +} +-} + +int ataninv(int base, int q) { ;; computes base*atan(1/q) + base ~/= q; + q *= - q; + int sum = 0; + int n = 1; + do { + sum += base ~/ n; + base ~/= q; + n += 2; + } until base == 0; + return sum; +} + +int calc_pi() { + int base = 64; + repeat (70) { base *= 10; } + return (ataninv(base << 2, 5) - ataninv(base, 239)) ~>> 4; +} + +int main() { + return calc_pi(); +} + +{- + method_id | in | out +TESTCASE | 0 | | 31415926535897932384626433832795028841971693993751058209749445923078164 +-} diff --git a/crypto/func/auto-tests/tests/a6_1.fc b/crypto/func/auto-tests/tests/a6_1.fc new file mode 100644 index 00000000..b6341df0 --- /dev/null +++ b/crypto/func/auto-tests/tests/a6_1.fc @@ -0,0 +1,16 @@ +(int, int) main(int a, int b, int c, int d, int e, int f) { + int D = a * d - b * c; + int Dx = e * d - b * f; + int Dy = a * f - e * c; + return (Dx / D, Dy / D); +} + +{- + method_id | in | out +TESTCASE | 0 | 1 1 1 -1 10 6 | 8 2 +TESTCASE | 0 | 817 -31 624 -241 132272 272276 | 132 -788 +TESTCASE | 0 | -886 562 498 -212 -36452 -68958 | -505 -861 +TESTCASE | 0 | 448 -433 -444 792 150012 -356232 | -218 -572 +TESTCASE | 0 | -40 -821 433 -734 -721629 -741724 | -206 889 +TESTCASE | 0 | -261 -98 -494 868 -166153 733738 | 263 995 +-} diff --git a/crypto/func/auto-tests/tests/a6_5.fc b/crypto/func/auto-tests/tests/a6_5.fc new file mode 100644 index 00000000..06b5cc9d --- /dev/null +++ b/crypto/func/auto-tests/tests/a6_5.fc @@ -0,0 +1,24 @@ +var twice(f, x) { + return f (f x); +} + +_ sqr(x) { + return x * x; +} + +var main(x) { + var f = sqr; + return twice(f, x) * f(x); +} + +var pow6(x) method_id(4) { + return twice(sqr, x) * sqr(x); +} + +{- + method_id | in | out +TESTCASE | 0 | 3 | 729 +TESTCASE | 0 | 10 | 1000000 +TESTCASE | 4 | 3 | 729 +TESTCASE | 4 | 10 | 1000000 +-} diff --git a/crypto/func/auto-tests/tests/a7.fc b/crypto/func/auto-tests/tests/a7.fc new file mode 100644 index 00000000..356759d4 --- /dev/null +++ b/crypto/func/auto-tests/tests/a7.fc @@ -0,0 +1,24 @@ +() main() { } +int steps(int x) method_id(1) { + var n = 0; + while (x > 1) { + n += 1; + if (x & 1) { + x = 3 * x + 1; + } else { + x >>= 1; + } + } + return n; +} + +{- + method_id | in | out +TESTCASE | 1 | 1 | 0 +TESTCASE | 1 | 2 | 1 +TESTCASE | 1 | 5 | 5 +TESTCASE | 1 | 19 | 20 +TESTCASE | 1 | 27 | 111 +TESTCASE | 1 | 100 | 25 +-} + diff --git a/crypto/func/auto-tests/tests/allow_post_modification.fc b/crypto/func/auto-tests/tests/allow_post_modification.fc new file mode 100644 index 00000000..c6082252 --- /dev/null +++ b/crypto/func/auto-tests/tests/allow_post_modification.fc @@ -0,0 +1,78 @@ +#pragma allow-post-modification; + +forall X -> tuple unsafe_tuple(X x) asm "NOP"; + +(int, int) inc(int x, int y) { + return (x + y, y * 10); +} + +(int, int, int, int, int, int, int) test_return(int x) method_id(11) { + return (x, x~inc(x / 20), x, x = x * 2, x, x += 1, x); +} + +(int, int, int, int, int, int, int) test_assign(int x) method_id(12) { + (int x1, int x2, int x3, int x4, int x5, int x6, int x7) = (x, x~inc(x / 20), x, x = x * 2, x, x += 1, x); + return (x1, x2, x3, x4, x5, x6, x7); +} + +tuple test_tuple(int x) method_id(13) { + tuple t = unsafe_tuple([x, x~inc(x / 20), x, x = x * 2, x, x += 1, x]); + return t; +} + +(int, int, int, int, int, int, int) test_tuple_assign(int x) method_id(14) { + [int x1, int x2, int x3, int x4, int x5, int x6, int x7] = [x, x~inc(x / 20), x, x = x * 2, x, x += 1, x]; + return (x1, x2, x3, x4, x5, x6, x7); +} + +(int, int, int, int, int, int, int) foo1(int x1, int x2, int x3, int x4, int x5, int x6, int x7) { + return (x1, x2, x3, x4, x5, x6, x7); +} + +(int, int, int, int, int, int, int) test_call_1(int x) method_id(15) { + return foo1(x, x~inc(x / 20), x, x = x * 2, x, x += 1, x); +} + +(int, int, int, int, int, int, int) foo2(int x1, int x2, (int, int, int, int) x3456, int x7) { + (int x3, int x4, int x5, int x6) = x3456; + return (x1, x2, x3, x4, x5, x6, x7); +} + +(int, int, int, int, int, int, int) test_call_2(int x) method_id(16) { + return foo2(x, x~inc(x / 20), (x, x = x * 2, x, x += 1), x); +} + +(int, int, int, int, int, int, int) asm_func(int x1, int x2, int x3, int x4, int x5, int x6, int x7) asm + (x4 x5 x6 x7 x1 x2 x3 -> 0 1 2 3 4 5 6) "NOP"; + +(int, int, int, int, int, int, int) test_call_asm_old(int x) method_id(17) { + return asm_func(x, x += 1, x, x, x~inc(x / 20), x, x = x * 2); +} + +#pragma compute-asm-ltr; + +(int, int, int, int, int, int, int) test_call_asm_new(int x) method_id(18) { + return asm_func(x, x~inc(x / 20), x, x = x * 2, x, x += 1, x); +} + +global int xx; +(int, int, int, int, int, int, int) test_global(int x) method_id(19) { + xx = x; + return (xx, xx~inc(xx / 20), xx, xx = xx * 2, xx, xx += 1, xx); +} + +() main() { +} + +{- + method_id | in | out +TESTCASE | 11 | 100 | 100 50 105 210 210 211 211 +TESTCASE | 12 | 100 | 100 50 105 210 210 211 211 +TESTCASE | 13 | 100 | [ 100 50 105 210 210 211 211 ] +TESTCASE | 14 | 100 | 100 50 105 210 210 211 211 +TESTCASE | 15 | 100 | 100 50 105 210 210 211 211 +TESTCASE | 16 | 100 | 100 50 105 210 210 211 211 +TESTCASE | 17 | 100 | 100 50 105 210 210 211 211 +TESTCASE | 18 | 100 | 210 210 211 211 100 50 105 +TESTCASE | 19 | 100 | 100 50 105 210 210 211 211 +-} diff --git a/crypto/func/auto-tests/tests/asm_arg_order.fc b/crypto/func/auto-tests/tests/asm_arg_order.fc new file mode 100644 index 00000000..b53419e3 --- /dev/null +++ b/crypto/func/auto-tests/tests/asm_arg_order.fc @@ -0,0 +1,113 @@ +tuple empty_tuple() asm "NIL"; +forall X -> (tuple, ()) tpush(tuple t, X x) asm "TPUSH"; + +tuple asm_func_1(int x, int y, int z) asm "3 TUPLE"; +tuple asm_func_2(int x, int y, int z) asm (z y x -> 0) "3 TUPLE"; +tuple asm_func_3(int x, int y, int z) asm (y z x -> 0) "3 TUPLE"; +tuple asm_func_4(int a, (int, (int, int)) b, int c) asm (b a c -> 0) "5 TUPLE"; + +(tuple, ()) asm_func_modify(tuple a, int b, int c) asm (c b a -> 0) "SWAP TPUSH SWAP TPUSH"; + +global tuple t; + +int foo(int x) { + t~tpush(x); + return x * 10; +} + +(tuple, tuple) test_old_1() method_id(11) { + t = empty_tuple(); + tuple t2 = asm_func_1(foo(11), foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_old_2() method_id(12) { + t = empty_tuple(); + tuple t2 = asm_func_2(foo(11), foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_old_3() method_id(13) { + t = empty_tuple(); + tuple t2 = asm_func_3(foo(11), foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_old_4() method_id(14) { + t = empty_tuple(); + tuple t2 = empty_tuple(); + ;; This actually computes left-to-right even without compute-asm-ltr + tuple t2 = asm_func_4(foo(11), (foo(22), (foo(33), foo(44))), foo(55)); + return (t, t2); +} + +(tuple, tuple) test_old_modify() method_id(15) { + t = empty_tuple(); + tuple t2 = empty_tuple(); + t2~asm_func_modify(foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_old_dot() method_id(16) { + t = empty_tuple(); + tuple t2 = foo(11).asm_func_3(foo(22), foo(33)); + return (t, t2); +} + +#pragma compute-asm-ltr; + +(tuple, tuple) test_new_1() method_id(21) { + t = empty_tuple(); + tuple t2 = asm_func_1(foo(11), foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_new_2() method_id(22) { + t = empty_tuple(); + tuple t2 = asm_func_2(foo(11), foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_new_3() method_id(23) { + t = empty_tuple(); + tuple t2 = asm_func_3(foo(11), foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_new_4() method_id(24) { + t = empty_tuple(); + tuple t2 = asm_func_4(foo(11), (foo(22), (foo(33), foo(44))), foo(55)); + return (t, t2); +} + +(tuple, tuple) test_new_modify() method_id(25) { + t = empty_tuple(); + tuple t2 = empty_tuple(); + t2~asm_func_modify(foo(22), foo(33)); + return (t, t2); +} + +(tuple, tuple) test_new_dot() method_id(26) { + t = empty_tuple(); + tuple t2 = foo(11).asm_func_3(foo(22), foo(33)); + return (t, t2); +} + +() main() { +} + +{- + method_id | in | out +TESTCASE | 11 | | [ 11 22 33 ] [ 110 220 330 ] +TESTCASE | 12 | | [ 33 22 11 ] [ 330 220 110 ] +TESTCASE | 13 | | [ 22 33 11 ] [ 220 330 110 ] +TESTCASE | 14 | | [ 11 22 33 44 55 ] [ 220 330 440 110 550 ] +TESTCASE | 15 | | [ 33 22 ] [ 220 330 ] +TESTCASE | 16 | | [ 22 33 11 ] [ 220 330 110 ] +TESTCASE | 21 | | [ 11 22 33 ] [ 110 220 330 ] +TESTCASE | 22 | | [ 11 22 33 ] [ 330 220 110 ] +TESTCASE | 23 | | [ 11 22 33 ] [ 220 330 110 ] +TESTCASE | 24 | | [ 11 22 33 44 55 ] [ 220 330 440 110 550 ] +TESTCASE | 25 | | [ 22 33 ] [ 220 330 ] +TESTCASE | 26 | | [ 11 22 33 ] [ 220 330 110 ] +-} diff --git a/crypto/func/auto-tests/tests/c2.fc b/crypto/func/auto-tests/tests/c2.fc new file mode 100644 index 00000000..27f8b88f --- /dev/null +++ b/crypto/func/auto-tests/tests/c2.fc @@ -0,0 +1,17 @@ +global ((int, int) -> int) op; + +int check_assoc(int a, int b, int c) { + return op(op(a, b), c) == op(a, op(b, c)); +} + +int main(int x, int y, int z) { + op = _+_; + return check_assoc(x, y, z); +} + +{- + method_id | in | out +TESTCASE | 0 | 2 3 9 | -1 +TESTCASE | 0 | 11 22 44 | -1 +TESTCASE | 0 | -1 -10 -20 | -1 +-} diff --git a/crypto/func/auto-tests/tests/c2_1.fc b/crypto/func/auto-tests/tests/c2_1.fc new file mode 100644 index 00000000..ef7183b6 --- /dev/null +++ b/crypto/func/auto-tests/tests/c2_1.fc @@ -0,0 +1,14 @@ +_ check_assoc(op, a, b, c) { + return op(op(a, b), c) == op(a, op(b, c)); +} + +int main(int x, int y, int z) { + return check_assoc(_+_, x, y, z); +} + +{- + method_id | in | out +TESTCASE | 0 | 2 3 9 | -1 +TESTCASE | 0 | 11 22 44 | -1 +TESTCASE | 0 | -1 -10 -20 | -1 +-} diff --git a/crypto/func/auto-tests/tests/co1.fc b/crypto/func/auto-tests/tests/co1.fc new file mode 100644 index 00000000..44067243 --- /dev/null +++ b/crypto/func/auto-tests/tests/co1.fc @@ -0,0 +1,60 @@ +const int1 = 1, int2 = 2; + +const int int101 = 101; +const int int111 = 111; + +const int1r = int1; + +const str1 = "const1", str2 = "aabbcc"s; + +const slice str2r = str2; + +const str1int = 0x636f6e737431; +const str2int = 0xAABBCC; + +const int nibbles = 4; + +int iget1() { return int1; } +int iget2() { return int2; } +int iget3() { return int1 + int2; } + +int iget1r() { return int1r; } + +slice sget1() { return str1; } +slice sget2() { return str2; } +slice sget2r() { return str2r; } + +const int int240 = ((int1 + int2) * 10) << 3; + +int iget240() { return int240; } + +builder newc() asm "NEWC"; +slice endcs(builder b) asm "ENDC" "CTOS"; +int sdeq (slice s1, slice s2) asm "SDEQ"; +builder stslicer(builder b, slice s) asm "STSLICER"; + +_ main() { + int i1 = iget1(); + int i2 = iget2(); + int i3 = iget3(); + + throw_unless(int101, i1 == 1); + throw_unless(102, i2 == 2); + throw_unless(103, i3 == 3); + + slice s1 = sget1(); + slice s2 = sget2(); + slice s3 = newc().stslicer(str1).stslicer(str2r).endcs(); + + throw_unless(int111, sdeq(s1, newc().store_uint(str1int, 12 * nibbles).endcs())); + throw_unless(112, sdeq(s2, newc().store_uint(str2int, 6 * nibbles).endcs())); + throw_unless(113, sdeq(s3, newc().store_uint(0x636f6e737431AABBCC, 18 * nibbles).endcs())); + + int i4 = iget240(); + throw_unless(104, i4 == 240); + return 0; +} + +{- +TESTCASE | 0 | | 0 +-} diff --git a/crypto/func/auto-tests/tests/code_after_ifelse.fc b/crypto/func/auto-tests/tests/code_after_ifelse.fc new file mode 100644 index 00000000..49e082c7 --- /dev/null +++ b/crypto/func/auto-tests/tests/code_after_ifelse.fc @@ -0,0 +1,19 @@ +int foo(int x) inline method_id(1) { + if (x == 1) { + return 111; + } else { + x *= 2; + } + return x + 1; +} +(int, int) main(int x) { + return (foo(x), 222); +} + +{- + method_id | in | out +TESTCASE | 1 | 1 | 111 +TESTCASE | 1 | 3 | 7 +TESTCASE | 0 | 1 | 111 222 +TESTCASE | 0 | 3 | 7 222 +-} diff --git a/crypto/func/auto-tests/tests/inline_big.fc b/crypto/func/auto-tests/tests/inline_big.fc new file mode 100644 index 00000000..61ad436e --- /dev/null +++ b/crypto/func/auto-tests/tests/inline_big.fc @@ -0,0 +1,61 @@ +int foo(int x) inline { + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + x = x * 10 + 1; + return x; +} + +(int) main(int x) { + return foo(x) * 10 + 5; +} +{- + method_id | in | out +TESTCASE | 0 | 9 | 9111111111111111111111111111111111111111111111111115 +-} diff --git a/crypto/func/auto-tests/tests/inline_if.fc b/crypto/func/auto-tests/tests/inline_if.fc new file mode 100644 index 00000000..390f0fd3 --- /dev/null +++ b/crypto/func/auto-tests/tests/inline_if.fc @@ -0,0 +1,26 @@ +int foo1(int x) { + if (x == 1) { + return 1; + } + return 2; +} +int foo2(int x) inline { + if (x == 1) { + return 11; + } + return 22; +} +int foo3(int x) inline_ref { + if (x == 1) { + return 111; + } + return 222; +} +(int, int, int) main(int x) { + return (foo1(x) + 1, foo2(x) + 1, foo3(x) + 1); +} +{- + method_id | in | out +TESTCASE | 0 | 1 | 2 12 112 +TESTCASE | 0 | 2 | 3 23 223 +-} diff --git a/crypto/func/auto-tests/tests/inline_loops.fc b/crypto/func/auto-tests/tests/inline_loops.fc new file mode 100644 index 00000000..9f1f45fc --- /dev/null +++ b/crypto/func/auto-tests/tests/inline_loops.fc @@ -0,0 +1,43 @@ +global int g; + +_ foo_repeat() impure inline { + g = 1; + repeat(5) { + g *= 2; + } +} + +int foo_until() impure inline { + g = 1; + int i = 0; + do { + g *= 2; + i += 1; + } until (i >= 8); + return i; +} + +int foo_while() impure inline { + g = 1; + int i = 0; + while (i < 10) { + g *= 2; + i += 1; + } + return i; +} + +_ main() { + foo_repeat(); + int x = g; + foo_until(); + int y = g; + foo_while(); + int z = g; + return (x, y, z); +} + +{- + method_id | in | out +TESTCASE | 0 | | 32 256 1024 +-} diff --git a/crypto/func/auto-tests/tests/method_id.fc b/crypto/func/auto-tests/tests/method_id.fc new file mode 100644 index 00000000..b4e0cd3b --- /dev/null +++ b/crypto/func/auto-tests/tests/method_id.fc @@ -0,0 +1,12 @@ +int foo1() method_id(1) { return 111; } +int foo2() method_id(3) { return 222; } +int foo3() method_id(10) { return 333; } +int main() { return 999; } + +{- + method_id | in | out +TESTCASE | 1 | | 111 +TESTCASE | 3 | | 222 +TESTCASE | 10 | | 333 +TESTCASE | 0 | | 999 +-} diff --git a/crypto/func/auto-tests/tests/s1.fc b/crypto/func/auto-tests/tests/s1.fc new file mode 100644 index 00000000..1541943d --- /dev/null +++ b/crypto/func/auto-tests/tests/s1.fc @@ -0,0 +1,54 @@ +slice ascii_slice() method_id { + return "string"; +} + +slice raw_slice() method_id { + return "abcdef"s; +} + +slice addr_slice() method_id { + return "Ef8zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM0vF"a; +} + +int string_hex() method_id { + return "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"u; +} + +int string_minihash() method_id { + return "transfer(slice, int)"h; +} + +int string_maxihash() method_id { + return "transfer(slice, int)"H; +} + +int string_crc32() method_id { + return "transfer(slice, int)"c; +} + +builder newc() asm "NEWC"; +slice endcs(builder b) asm "ENDC" "CTOS"; +int sdeq (slice s1, slice s2) asm "SDEQ"; + +_ main() { + slice s_ascii = ascii_slice(); + slice s_raw = raw_slice(); + slice s_addr = addr_slice(); + int i_hex = string_hex(); + int i_mini = string_minihash(); + int i_maxi = string_maxihash(); + int i_crc = string_crc32(); + throw_unless(101, sdeq(s_ascii, newc().store_uint(0x737472696E67, 12 * 4).endcs())); + throw_unless(102, sdeq(s_raw, newc().store_uint(0xABCDEF, 6 * 4).endcs())); + throw_unless(103, sdeq(s_addr, newc().store_uint(4, 3).store_int(-1, 8) + .store_uint(0x3333333333333333333333333333333333333333333333333333333333333333, 256).endcs())); + throw_unless(104, i_hex == 0x4142434445464748494A4B4C4D4E4F505152535455565758595A303132333435); + throw_unless(105, i_mini == 0x7a62e8a8); + throw_unless(106, i_maxi == 0x7a62e8a8ebac41bd6de16c65e7be363bc2d2cbc6a0873778dead4795c13db979); + throw_unless(107, i_crc == 2235694568); + return 0; +} + +{- +TESTCASE | 0 | | 0 +-} diff --git a/crypto/func/auto-tests/tests/test-math.fc b/crypto/func/auto-tests/tests/test-math.fc new file mode 100644 index 00000000..e9651dc3 --- /dev/null +++ b/crypto/func/auto-tests/tests/test-math.fc @@ -0,0 +1,275 @@ +#include "../../../smartcont/mathlib.fc"; + +forall X -> (tuple, ()) ~tset(tuple t, int idx, X val) asm(t val idx) "SETINDEXVAR"; + +;; computes 1-acos(x)/Pi by a very simple, extremely slow (~70k gas) and imprecise method +;; fixed256 acos_prepare_slow(fixed255 x); +int acos_prepare_slow_f255(int x) inline { + x -= (x == 0); + int t = 1; + repeat (255) { + t = t * sgn(x) * 2 + 1; ;; decode Gray code (sgn(x_0), sgn(x_1), ...) + x = (-1 << 255) - muldivr(x, - x, 1 << 254); ;; iterate x := 2*x^2 - 1 = cos(2*acos(x)) + } + return abs(t); +} + +;; extremely slow (~70k gas) and somewhat imprecise (very imprecise when x is small), for testing only +;; fixed254 acos_slow(fixed255 x); +int acos_slow_f255(int x) inline_ref { + int t = acos_prepare_slow_f255(x); + return - mulrshiftr256(t + (-1 << 256), Pi_const_f254()); +} + +;; fixed255 asin_slow(fixed255 x); +int asin_slow_f255(int x) inline_ref { + int t = acos_prepare_slow_f255(abs(x)) % (1 << 255); + return muldivr(t, Pi_const_f254(), 1 << 255) * sgn(x); +} + +tuple test_nrand(int n) inline_ref { + tuple t = empty_tuple(); + repeat (255) { + t~tpush(0); + } + repeat (n) { + int x = fixed248::nrand(); + int bucket = (abs(x) >> 243); ;; 255 buckets starting from x=0, each 1/32 wide + t~tset(bucket, t.at(bucket) + 1); + } + return t; +} + +int geom_mean_test(int x, int y) method_id(10000) { + return geom_mean(x, y); +} +int tan_f260_test(int x) method_id(10001) { + return tan_f260(x); +} +(int, int) sincosm1_f259_test(int x) method_id(10002) { + return sincosm1_f259(x); +} +(int, int) sincosn_f256_test(int x, int y) method_id(10003) { + return sincosn_f256(x, y); +} +(int, int) sincosm1_f256_test(int x) method_id(10004) { + return sincosm1_f256(x); +} +(int, int) tan_aux_f256_test(x) method_id(10005) { + return tan_aux_f256(x); +} +(int) fixed248::tan_test(x) method_id(10006) { + return fixed248::tan(x); +} +{- + (int) atanh_alt_f258_test(x) method_id(10007) { + return atanh_alt_f258(x); + } +-} +(int) atanh_f258_test(x, y) method_id(10008) { + return atanh_f258(x, y); +} +(int) atanh_f261_test(x, y) method_id(10009) { + return atanh_f261(x, y); +} + +(int, int) log2_aux_f256_test(x) method_id(10010) { + return log2_aux_f256(x); +} +(int, int) log_aux_f256_test(x) method_id(10011) { + return log_aux_f256(x); +} +int fixed248::pow_test(x, y) method_id(10012) { + return fixed248::pow(x, y); +} +int exp_log_div(x, y) method_id(10013) { + return fixed248::exp(fixed248::log(x << 248) ~/ y); +} +int fixed248::log_test(x) method_id(10014) { + return fixed248::log(x); +} +(int,int) log_aux_f257_test(x) method_id(10015) { + return log_aux_f257(x); +} +(int,int) fixed248::sincos_test(x) method_id(10016) { + return fixed248::sincos(x); +} +int fixed248::exp_test(x) method_id(10017) { + return fixed248::exp(x); +} +int fixed248::exp2_test(x) method_id(10018) { + return fixed248::exp2(x); +} +int expm1_f257_test(x) method_id(10019) { + return expm1_f257(x); +} +int atan_f255_test(x) method_id(10020) { + return atan_f255(x); +} +int atan_f259_test(x, n) method_id(10021) { + return atan_f259(x, n); +} +(int, int) atan_aux_f256_test(x) method_id(10022) { + return atan_aux_f256(x); +} +int asin_f255_test(x) method_id(10023) { + return asin_f255(x); +} +int asin_slow_f255_test(x) method_id(10024) { + return asin_slow_f255(x); +} +int acos_f255_test(x) method_id(10025) { + return acos_f255(x); +} +int acos_slow_f255_test(x) method_id(10026) { + return acos_slow_f255(x); +} +int fixed248::atan_test(x) method_id(10027) { + return fixed248::atan(x); +} +int fixed248::acot_test(x) method_id(10028) { + return fixed248::acot(x); +} + +_ main() { + int One = 1; + ;; repeat(76 / 4) { One *= 10000; } + int sqrt2 = geom_mean(One, 2 * One); + int sqrt3 = geom_mean(One, 3 * One); + ;; return geom_mean(-1 - (-1 << 256), -1 - (-1 << 256)); + ;; return geom_mean(-1 - (-1 << 256), -2 - (-1 << 256)); + ;; return geom_mean(-1 - (-1 << 256), 1 << 255); + ;; return (sqrt2, geom_mean(sqrt2, One)); ;; (sqrt(2), 2^(1/4)) + ;; return (sqrt3, geom_mean(sqrt3, One)); ;; (sqrt(3), 3^(1/4)) + ;; return geom_mean(3 << 254, 1 << 254); + ;; return geom_mean(3, 5); + ;; return tan_f260(115641670674223639132965820642403718536242645001775371762318060545014644837101 - 1); + ;; return tan_f260(15 << 252); ;; tan(15/256) * 2^260 + ;; return sincosm1_f259(1 << 255); ;; (sin,1-cos)(1/16) * 2^259 + ;; return sincosm1_f259(115641670674223639132965820642403718536242645001775371762318060545014644837101 - 1); + ;; return sincosm1_f256((1 << 255) - 1 + (1 << 255)); ;; (sin,1-cos)(1-2^(-256)) + ;; return sincosm1_f256(Pi_const_f254()); ;; (sin,1-cos)(Pi/4) + ;; return sincosn_f256(Pi_const_f254(), 0); ;; (sin,-cos)(Pi/4) + ;; return sincosn_f256((1 << 255) + 1, 0); ;; (sin,-cos)(1/2+1/2^256) + ;; return sincosn_f256(1 << 254, 0); + ;; return sincosn_f256(touch(15) << 252, 0); ;; (sin,-cos)(15/16) + ;; return sincosm1_f256(touch(15) << 252); ;; (sin,1-cos)(15/16) + ;; return sincosn_f256(60628596148627720713372490462954977108898896221398738326462025186323149077698, 0); ;; (sin,-cos)(Pi/6) + ;; return sincosm1_f256(60628596148627720713372490462954977108898896221398738326462025186323149077698); ;; (sin,1-cos)(Pi/6) + ;; return tan_aux_f256(1899 << 245); ;; (p,q) such that p/q=tan(1899/2048) + ;; return fixed248::tan(11 << 248); ;; tan(11) + ;; return atanh_alt_f258(1 << 252); ;; atanh(1/64) * 2^258 + ;; return atanh_f258(1 << 252, 18); ;; atanh(1/64) * 2^258 + ;; return atanh_f261(muldivr(64, 1 << 255, 55), 18); ;; atanh(1/55) * 2^261 + ;; return log2_aux_f256(1 << 255); + ;; return log2_aux_f256(-1 - (-1 << 256)); ;; log2(2-1/2^255))*2^256 ~ 2^256 - 1.43 + ;; return log_aux_f256(-1 - (-1 << 256)); + ;; return log_aux_f256(3); ;; log(3/2)*2^256 + ;; return fixed248::pow(3 << 248, 3 << 248); ;; 3^3 + ;; return fixed248::exp(fixed248::log(5 << 248) ~/ 7); ;; exp(log(5)/7) = 5^(1/7) + ;; return fixed248::log(Pi_const_f254() ~>> 6); ;; log(Pi) + ;; return atanh_alt_f258(1 << 255); ;; atanh(1/8) * 2^258 + ;; return atanh_f258(1 << 255, 37); ;; atanh(1/8) * 2^258 + ;; return atanh_f258(81877371507464127617551201542979628307507432471243237061821853600756754782485, 36); ;; atanh(sqrt(2)/8) * 2^258 + ;; return log_aux_f257(Pi_const_f254()); ;; log(Pi/4) + ;; return log_aux_f257(3 << 254); ;; log(3) + ;; return atanh_alt_f258(81877371507464127617551201542979628307507432471243237061821853600756754782485); ;; atanh(sqrt(2)/8) * 2^258 + ;; return fixed248::sincos(Pi_const_f254() ~/ (64 * 3)); ;; (sin,cos)(Pi/3) + ;; return fixed248::exp(3 << 248); ;; exp(3)*2^248 + ;; return fixed248::exp2((1 << 248) ~/ 5); ;; 2^(1/5)*2^248 + ;; return fixed248::pow(3 << 248, -3 << 247); ;; 3^(-1.5) + ;; return fixed248::pow(10 << 248, -70 << 248); ;; 10^(-70) + ;; return fixed248::pow(fixed248::Pi_const(), touch(3) << 248); ;; Pi^3 ~ 31.006, computed more precisely + ;; return fixed248::pow(fixed248::Pi_const(), fixed248::Pi_const()); ;; Pi^Pi, more precisely + ;; return fixed248::exp(fixed248::log(fixed248::Pi_const()) * 3); ;; Pi^3 ~ 31.006 + ;; return fixed248::exp(muldivr(fixed248::log(fixed248::Pi_const()), fixed248::Pi_const(), 1 << 248)); ;; Pi^Pi + ;; return fixed248::sin(fixed248::log(fixed248::exp(fixed248::Pi_const()))); ;; sin(log(e^Pi)) + ;; return expm1_f257(1 << 255); ;; (exp(1/4)-1)*2^256 + ;; return expm1_f257(-1 << 256); ;; (exp(-1/2)-1)*2^256 (argument out of range, will overflow) + ;; return expm1_f257(log2_const_f256()); ;; (exp(log(2)/2)-1)*2^256 + ;; return expm1_f257(- log2_const_f256()); ;; (exp(-log(2)/2)-1)*2^256 + ;; return tanh_f258(log2_const_f256(), 17); ;; tanh(log(2)/4)*2^258 + ;; return atan_f255(0xa0 << 247); + ;; return atan_f259(1 << 255, 26); ;; atan(1/16) + ;; return atan_f259(touch(2273) << 244, 26); ;; atan(2273/2^15) + ;; return atan_aux_f256(0xa0 << 248); + ;; return atan_aux_f256(-1 - (-1 << 256)); + ;; return atan_aux_f256(-1 << 256); + ;; return atan_aux_f256(1); ;; atan(1/2^256)*2^261 = 32 + ;;return fixed248::nrand(); + ;; return test_nrand(100000); + int One = touch(1 << 255); + ;; return asin_f255(One); + ;; return asin_f255(-2 * One ~/ -3); + int arg = muldivr(12, One, 17); ;; 12/17 + ;; return [ asin_slow_f255(arg), asin_f255(arg) ]; + ;; return [ acos_slow_f255(arg), acos_f255(arg) ]; + ;; return 4 * atan_f255(One ~/ 5) - atan_f255(One ~/ 239); ;; 4 * atan(1/5) - atan(1/239) = Pi/4 as fixed255 + int One = touch(1 << 248); + ;; return fixed248::atan(One) ~/ 5); ;; atan(1/5) + ;; return fixed248::acot(One ~/ 239); ;; atan(1/5) +} + +{- + method_id | in | out +TESTCASE | 10000 | -1-(-1<<256) -1-(-1<<256) | 115792089237316195423570985008687907853269984665640564039457584007913129639935 +TESTCASE | 10000 | -1-(-1<<256) -2-(-1<<256) | 115792089237316195423570985008687907853269984665640564039457584007913129639934 +TESTCASE | 10000 | -1-(-1<<256) 1<<255 | 81877371507464127617551201542979628307507432471243237061821853600756754782485 +TESTCASE | 10000 | 1 2 | 1 +TESTCASE | 10000 | 1 3 | 2 +TESTCASE | 10000 | 3<<254, 1<<254 | 50139445418395255283694704271811692336355250894665672355503583528635147053497 +TESTCASE | 10000 | 3 5 | 4 +TESTCASE | 10001 | 115641670674223639132965820642403718536242645001775371762318060545014644837101-1 | 115792089237316195423570985008687907853269984665640564039457584007913129639935 +TESTCASE | 10001 | 15<<252 | 108679485937549714997960660780289583146059954551846264494610741505469565211201 + +TESTCASE | 10002 | 1<<255 | 57858359242454268843682786479537198006144860419130642837770554273561536355094 28938600351875109040123440645416448095273333920390487381363947585666516031269 +TESTCASE | 10002 | 90942894222941581070058735694432465663348344332098107489693037779484723616546 | 90796875678616203090520439851979829600860326752181983760731669850687818036503 71369031536005973567205947792557760023823761636922618688720973932041901854510 +TESTCASE | 10002 | 115641670674223639132965820642403718536242645001775371762318060545014644837100 | 115341536360906404779899502576747487978354537254490211650198994186870666100480 115341536360906404779899502576747487978354537254490211650198994186870666100479 +TESTCASE | 10003 | 90942894222941581070058735694432465663348344332098107489693037779484723616546 0 | 81877371507464127617551201542979628307507432471243237061821853600756754782485 -81877371507464127617551201542979628307507432471243237061821853600756754782486 +TESTCASE | 10003 | (1<<255)+1 0 | 55513684748706254392157395574451324146997108788015526773113170656738693667657 -101617118319522600545601981648807607350213579319835970884288805016705398675944 +TESTCASE | 10003 | 1<<254 0 | 28647421327665059059430596260119789787021370826354543144805343654507971817712 -112192393597863122712065585177748900737784171216163716639418346853706594800924 +TESTCASE | 10003 | 15<<252 0 | 93337815620236900315136494926097782162348358704087992554326802765553037216157 -68526346066204767396483080633934170508153877799043171682610011603005473885083 +TESTCASE | 10004 | 15<<252 | 93337815620236900315136494926097782162348358704087992554326802765553037216158 94531486342222856054175808749507474690232213733194784713695144809815311509707 +TESTCASE | 10003 | 60628596148627720713372490462954977108898896221398738326462025186323149077698 0 | 57896044618658097711785492504343953926634992332820282019728792003956564819968 -100278890836790510567389408543623384672710501789331344711007167057270294106993 +TESTCASE | 10004 | 60628596148627720713372490462954977108898896221398738326462025186323149077698 | 57896044618658097711785492504343953926634992332820282019728792003956564819968 31026396801051369712363152930129046361118965752618438656900833901285671065886 +TESTCASE | 10005 | 1899<<245 | -115784979074977116522606932816046735344768048129666123117516779696532375620701 -86847621900007587791673148476644866514014227467564880140262768165345715058771 +TESTCASE | 10006 | 11<<248 | -102200470999497240398685962406597118965525125432278008915850368651878945159221 +TESTCASE | 10008 | 1<<252 18 | 7237594612640731814076778712183932891481921212865048737772958953246047977071 +TESTCASE! | 10009 | 64*(1<<255)//55 18 | 67377367986958444187782963285047188951340314639925508148698906136973510008513 +TESTCASE | 10010 | 1<<255 | 0 255 +TESTCASE | 10011 | -1-(-1<<256) | 80260960185991308862233904206310070533990667611589946606122867505419956976171 255 +TESTCASE | 10012 | 3<<248 3<<248 | 12212446911748192486079752325135052781399568695204278238536542063334587891712 +TESTCASE | 10013 | 5 7 | 569235245303856216139605450142923208167703167128528666640203654338408315932 +TESTCASE | 10014 | 1420982722233462204219667745225507275989817880189032929526453715304448806508 | 517776035526939558040896860590142614178014859368681705591403663865964112176 +TESTCASE | 10008 | 1<<255 37 | 58200445412255555045265806996802932280233368707362818578692888102488340124094 +TESTCASE | 10008 | 81877371507464127617551201542979628307507432471243237061821853600756754782485 36 | 82746618329032515754939514227666784789465120373484337368014239356561508382845 +TESTCASE | 10015 | 90942894222941581070058735694432465663348344332098107489693037779484723616546 | -55942510554172181731996424203087263676819062449594753161692794122306202470292 256 +TESTCASE | 10015 | 3<<254 | -66622616410625360568738677407433830899150908037353507097280251369610028875158 256 +TESTCASE | 10016 | 90942894222941581070058735694432465663348344332098107489693037779484723616546//(64*3) | 391714417331212931903864877123528846377775397614575565277371746317462086355 226156424291633194186662080095093570025917938800079226639565593765455331328 +TESTCASE | 10017 | 3<<248 | 9084946421051389814103830025729847734065792062362132089390904679466687950835 +TESTCASE | 10018 | (1<<248)//5 | 519571025111621076330285524602776985448579272766894385941850747946908706857 +TESTCASE | 10012 | 3<<248 -3<<247 | 87047648295825095978636639360784188083950088358794570061638165848324908079 +TESTCASE | 10012 | 10<<248 -70<<248 | 45231 +TESTCASE | 10012 | 1420982722233462204219667745225507275989817880189032929526453715304448806508 3<<248 | 14024537329227316173680050897643053638073167245065581681188087336877135047241 +TESTCASE | 10012 | 1420982722233462204219667745225507275989817880189032929526453715304448806508 1420982722233462204219667745225507275989817880189032929526453715304448806508 | 16492303277433924047657446877966346821161732581471802839855102123372676002295 +TESTCASE | 10019 | 1<<255 | 65775792789545756849501669218806308540691279864498696756901136302101823231959 +TESTCASE | 10019 | -1<<255 | -51226238931640701466578648374135745377468902266335737558089915608594425303282 + +TESTCASE | 10020 | 160<<247 | 32340690885082755723307749066376646841771751777398167772823878380310576779097 +TESTCASE | 10021 | 1<<255 26 | 57820835337111819566482910321201859268121322500887685881159030272507322418551 +TESTCASE | 10021 | 2273<<244 26 | 64153929153128256059565403901040178355488584937372975321150754259394300105908 +TESTCASE | 10022 | 160<<248 | 18 -13775317617017974742132028403521581424991093186766868001115299479309514610238 +TESTCASE | 10022 | -1-(-1<<256) | 25 16312150880916231694896252427912541090503675654570543195394548083530005073282 +TESTCASE | 10022 | -1<<256 | -25 -16312150880916231694896252427912541090503675654570543195394548083530005073298 +TESTCASE | 10022 | 1 | 0 32 + +TESTCASE | 10023 | 1<<255 | 90942894222941581070058735694432465663348344332098107489693037779484723616546 +TESTCASE | 10023 | (1-(1<<255))//-3 | 19675212872822715586637341573564384553677006914302429002469838095945333339604 +TESTCASE | 10023 | 12*(1<<255)//17 | 45371280744427205854111943101074857545572584208710061167826656461897302968384 +TESTCASE | 10024 | 12*(1<<255)//17 | 45371280744427205854111943101074857545572584208710061167826656461897302968387 +TESTCASE | 10025 | 12*(1<<255)//17 | 22785806739257187607973396296678804058887880061694023160933190658793710324081 +TESTCASE | 10026 | 12*(1<<255)//17 | 22785806739257187607973396296678804058887880061694023160933190658793710324080 + +TESTCASE | 10027 | (1<<248)//5 | 89284547973388213553327350968415123522888028497458323165947767504203347189 +TESTCASE | 10028 | (1<<248)//239 | 708598849781543798951441405045469962900811296151941404481049216461523216127 +-} diff --git a/crypto/func/auto-tests/tests/try-func.fc b/crypto/func/auto-tests/tests/try-func.fc new file mode 100644 index 00000000..5678965e --- /dev/null +++ b/crypto/func/auto-tests/tests/try-func.fc @@ -0,0 +1,109 @@ +int foo(int x) method_id(11) { + try { + if (x == 7) { + throw(44); + } + return x; + } catch (_, _) { + return 2; + } +} + +int foo_inline(int x) inline method_id(12) { + try { + if (x == 7) { + throw(44); + } + return x; + } catch (_, _) { + return 2; + } +} + +int foo_inlineref(int x) inline_ref method_id(13) { + try { + if (x == 7) { + throw(44); + } + return x; + } catch (_, _) { + return 2; + } +} + +int test(int x, int y, int z) method_id(1) { + y = foo(y); + return x * 100 + y * 10 + z; +} + +int test_inline(int x, int y, int z) method_id(2) { + y = foo_inline(y); + return x * 100 + y * 10 + z; +} + +int test_inlineref(int x, int y, int z) method_id(3) { + y = foo_inlineref(y); + return x * 100 + y * 10 + z; +} + +int foo_inline_big( + int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8, int x9, int x10, + int x11, int x12, int x13, int x14, int x15, int x16, int x17, int x18, int x19, int x20 +) inline method_id(14) { + try { + if (x1 == 7) { + throw(44); + } + return x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 + x13 + x14 + x15 + x16 + x17 + x18 + x19 + x20; + } catch (_, _) { + return 1; + } +} + +int test_inline_big(int x, int y, int z) method_id(4) { + y = foo_inline_big( + y, y + 1, y + 2, y + 3, y + 4, y + 5, y + 6, y + 7, y + 8, y + 9, + y + 10, y + 11, y + 12, y + 13, y + 14, y + 15, y + 16, y + 17, y + 18, y + 19); + return x * 1000000 + y * 1000 + z; +} + +int foo_big( + int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8, int x9, int x10, + int x11, int x12, int x13, int x14, int x15, int x16, int x17, int x18, int x19, int x20 +) method_id(15) { + try { + if (x1 == 7) { + throw(44); + } + return x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 + x13 + x14 + x15 + x16 + x17 + x18 + x19 + x20; + } catch (_, _) { + return 1; + } +} + +int test_big(int x, int y, int z) method_id(5) { + y = foo_big( + y, y + 1, y + 2, y + 3, y + 4, y + 5, y + 6, y + 7, y + 8, y + 9, + y + 10, y + 11, y + 12, y + 13, y + 14, y + 15, y + 16, y + 17, y + 18, y + 19); + return x * 1000000 + y * 1000 + z; +} + +() main() { +} + +{- + method_id | in | out +TESTCASE | 1 | 1 2 3 | 123 +TESTCASE | 1 | 3 8 9 | 389 +TESTCASE | 1 | 3 7 9 | 329 +TESTCASE | 2 | 1 2 3 | 123 +TESTCASE | 2 | 3 8 9 | 389 +TESTCASE | 2 | 3 7 9 | 329 +TESTCASE | 3 | 1 2 3 | 123 +TESTCASE | 3 | 3 8 9 | 389 +TESTCASE | 3 | 3 7 9 | 329 +TESTCASE | 4 | 4 8 9 | 4350009 +TESTCASE | 4 | 4 7 9 | 4001009 +TESTCASE | 5 | 4 8 9 | 4350009 +TESTCASE | 5 | 4 7 9 | 4001009 +-} diff --git a/crypto/func/auto-tests/tests/unbalanced_ret.fc b/crypto/func/auto-tests/tests/unbalanced_ret.fc new file mode 100644 index 00000000..0e4ef6ae --- /dev/null +++ b/crypto/func/auto-tests/tests/unbalanced_ret.fc @@ -0,0 +1,17 @@ +(int, int) main(int x) { + int y = 5; + if (x < 0) { + x *= 2; + y += 1; + if (x == -10) { + return (111, 0); + } + } + return (x + 1, y); +} +{- + method_id | in | out +TESTCASE | 0 | 10 | 11 5 +TESTCASE | 0 | -5 | 111 0 +TESTCASE | 0 | -4 | -7 6 +-} diff --git a/crypto/func/auto-tests/tests/unbalanced_ret_inline.fc b/crypto/func/auto-tests/tests/unbalanced_ret_inline.fc new file mode 100644 index 00000000..6d169345 --- /dev/null +++ b/crypto/func/auto-tests/tests/unbalanced_ret_inline.fc @@ -0,0 +1,18 @@ +int foo(int x) inline { + if (x < 0) { + x *= 2; + if (x == -10) { + return 111; + } + } + return x + 1; +} +int main(int x) { + return foo(x) * 10; +} +{- + method_id | in | out +TESTCASE | 0 | 10 | 110 +TESTCASE | 0 | -5 | 1110 +TESTCASE | 0 | -4 | -70 +-} diff --git a/crypto/func/auto-tests/tests/unbalanced_ret_loops.fc b/crypto/func/auto-tests/tests/unbalanced_ret_loops.fc new file mode 100644 index 00000000..104ec00d --- /dev/null +++ b/crypto/func/auto-tests/tests/unbalanced_ret_loops.fc @@ -0,0 +1,48 @@ +_ main() { } + +int foo_repeat(int x) method_id(1) { + repeat(10) { + x += 10; + if (x >= 100) { + return x; + } + } + return -1; +} + +int foo_while(int x) method_id(2) { + int i = 0; + while (i < 10) { + x += 10; + if (x >= 100) { + return x; + } + i += 1; + } + return -1; +} + +int foo_until(int x) method_id(3) { + int i = 0; + do { + x += 10; + if (x >= 100) { + return x; + } + i += 1; + } until (i >= 10); + return -1; +} + +{- + method_id | in | out +TESTCASE | 1 | 40 | 100 +TESTCASE | 1 | 33 | 103 +TESTCASE | 1 | -5 | -1 +TESTCASE | 2 | 40 | 100 +TESTCASE | 2 | 33 | 103 +TESTCASE | 2 | -5 | -1 +TESTCASE | 3 | 40 | 100 +TESTCASE | 3 | 33 | 103 +TESTCASE | 3 | -5 | -1 +-} diff --git a/crypto/func/auto-tests/tests/unbalanced_ret_nested.fc b/crypto/func/auto-tests/tests/unbalanced_ret_nested.fc new file mode 100644 index 00000000..7ab4bdf0 --- /dev/null +++ b/crypto/func/auto-tests/tests/unbalanced_ret_nested.fc @@ -0,0 +1,35 @@ +int foo(int y) { + if (y < 0) { + y *= 2; + if (y == -10) { + return 111; + } + } + return y + 1; +} +(int, int) bar(int x, int y) { + if (x < 0) { + y = foo(y); + x *= 2; + if (x == -10) { + return (111, y); + } + } + return (x + 1, y); +} +(int, int) main(int x, int y) { + (x, y) = bar(x, y); + return (x, y * 10); +} +{- + method_id | in | out +TESTCASE | 0 | 3 3 | 4 30 +TESTCASE | 0 | 3 -5 | 4 -50 +TESTCASE | 0 | 3 -4 | 4 -40 +TESTCASE | 0 | -5 3 | 111 40 +TESTCASE | 0 | -5 -5 | 111 1110 +TESTCASE | 0 | -5 -4 | 111 -70 +TESTCASE | 0 | -4 3 | -7 40 +TESTCASE | 0 | -4 -5 | -7 1110 +TESTCASE | 0 | -4 -4 | -7 -70 +-} diff --git a/crypto/func/auto-tests/tests/w1.fc b/crypto/func/auto-tests/tests/w1.fc new file mode 100644 index 00000000..3d6a8f69 --- /dev/null +++ b/crypto/func/auto-tests/tests/w1.fc @@ -0,0 +1,14 @@ +(int, int) main(int id) { + if (id > 0) { + if (id > 10) { + return (2 * id, 3 * id); + } + } + return (5, 6); +} +{- + method_id | in | out +TESTCASE | 0 | 0 | 5 6 +TESTCASE | 0 | 4 | 5 6 +TESTCASE | 0 | 11 | 22 33 +-} diff --git a/crypto/func/auto-tests/tests/w2.fc b/crypto/func/auto-tests/tests/w2.fc new file mode 100644 index 00000000..b9e5fb0e --- /dev/null +++ b/crypto/func/auto-tests/tests/w2.fc @@ -0,0 +1,18 @@ +_ f(cs) { + return (cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), + cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), + cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), + cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), + cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8)); +} + +_ main(cs) { + var (x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, + x11, x12, x13, x14, x15, x16, x17, x18, x19) = f(cs); + return x0 + x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + + x10 + x11 + x12 + x13 + x14 + x15 + x16 + x17 + x18 + x19; +} +{- + method_id | in | out +TESTCASE | 0 | x{000102030405060708090a0b0c0d0e0f10111213} | 190 +-} diff --git a/crypto/func/auto-tests/tests/w6.fc b/crypto/func/auto-tests/tests/w6.fc new file mode 100644 index 00000000..9b91cdf7 --- /dev/null +++ b/crypto/func/auto-tests/tests/w6.fc @@ -0,0 +1,17 @@ +int main(int x) { + int i = 0; + ;; int f = false; + do { + i = i + 1; + if (i > 5) { + return 1; + } + int f = (i * i == 64); + } until (f); + return -1; +} + +{- + method_id | in | out +TESTCASE | 0 | 0 | 1 +-} diff --git a/crypto/func/auto-tests/tests/w7.fc b/crypto/func/auto-tests/tests/w7.fc new file mode 100644 index 00000000..991b3e64 --- /dev/null +++ b/crypto/func/auto-tests/tests/w7.fc @@ -0,0 +1,24 @@ +int test(int y) method_id(1) { + int x = 1; + if (y > 0) { + return 1; + } + return x > 0; +} + +int f(int y) method_id(2) { + if (y > 0) { + return 1; + } + return 2; +} + +_ main() { } + +{- + method_id | in | out +TESTCASE | 1 | 10 | 1 +TESTCASE | 1 | -5 | -1 +TESTCASE | 2 | 10 | 1 +TESTCASE | 2 | -5 | 2 +-} diff --git a/crypto/func/auto-tests/tests/w9.fc b/crypto/func/auto-tests/tests/w9.fc new file mode 100644 index 00000000..f299dec1 --- /dev/null +++ b/crypto/func/auto-tests/tests/w9.fc @@ -0,0 +1,14 @@ +_ main(s) { + var (z, t) = (17, s); + while (z > 0) { + t = s; + z -= 1; + } + return ~ t; +} + +{- + method_id | in | out +TESTCASE | 0 | 1 | -2 +TESTCASE | 0 | 5 | -6 +-} diff --git a/crypto/func/auto-tests/wasm_tests_common.js b/crypto/func/auto-tests/wasm_tests_common.js new file mode 100644 index 00000000..84d8e0d5 --- /dev/null +++ b/crypto/func/auto-tests/wasm_tests_common.js @@ -0,0 +1,63 @@ +const fsSync = require('fs'); + +const copyToCString = (mod, str) => { + const len = mod.lengthBytesUTF8(str) + 1; + const ptr = mod._malloc(len); + mod.stringToUTF8(str, ptr, len); + return ptr; +}; + +const copyToCStringPtr = (mod, str, ptr) => { + const allocated = copyToCString(mod, str); + mod.setValue(ptr, allocated, '*'); + return allocated; +}; + +const copyFromCString = (mod, ptr) => { + return mod.UTF8ToString(ptr); +}; + +async function compileFile(mod, filename) { + const callbackPtr = mod.addFunction((_kind, _data, contents, error) => { + const kind = copyFromCString(mod, _kind); + const data = copyFromCString(mod, _data); + if (kind === 'realpath') { + copyToCStringPtr(mod, fsSync.realpathSync(data), contents); + } else if (kind === 'source') { + const path = fsSync.realpathSync(data); + try { + copyToCStringPtr(mod, fsSync.readFileSync(path).toString('utf-8'), contents); + } catch (err) { + copyToCStringPtr(mod, e.message, error); + } + } else { + copyToCStringPtr(mod, 'Unknown callback kind ' + kind, error); + } + }, 'viiii'); + + const config = { + optLevel: 2, + sources: [filename] + }; + + const configPtr = copyToCString(mod, JSON.stringify(config)); + + const responsePtr = mod._func_compile(configPtr, callbackPtr); + + return JSON.parse(copyFromCString(mod, responsePtr)); +} + +const wasmModule = require(process.env.FUNCFIFTLIB_MODULE) + +const wasmBinary = new Uint8Array(fsSync.readFileSync(process.env.FUNCFIFTLIB_WASM)) + +async function compileWasm() { + const mod = await wasmModule({ wasmBinary }) + + return mod +} + +module.exports = { + compileFile, + compileWasm +} diff --git a/crypto/func/builtins.cpp b/crypto/func/builtins.cpp index 73b5140f..3d191e7d 100644 --- a/crypto/func/builtins.cpp +++ b/crypto/func/builtins.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "func.h" @@ -27,10 +27,14 @@ using namespace std::literals::string_literals; * */ -int glob_func_cnt, undef_func_cnt, glob_var_cnt; +int glob_func_cnt, undef_func_cnt, glob_var_cnt, const_cnt; std::vector glob_func, glob_vars; +std::set prohibited_var_names; SymDef* predefine_builtin_func(std::string name, TypeExpr* func_type) { + if (name.back() == '_') { + prohibited_var_names.insert(name); + } sym_idx_t name_idx = sym::symbols.lookup(name, 1); if (sym::symbols.is_keyword(name_idx)) { std::cerr << "fatal: global function `" << name << "` already defined as a keyword" << std::endl; @@ -82,9 +86,10 @@ SymDef* define_builtin_const(std::string name, TypeExpr* const_type, Args&&... a define_builtin_func(name, TypeExpr::new_map(TypeExpr::new_unit(), const_type), std::forward(args)...)); } -bool SymValAsmFunc::compile(AsmOpList& dest, std::vector& out, std::vector& in) const { +bool SymValAsmFunc::compile(AsmOpList& dest, std::vector& out, std::vector& in, + const SrcLocation& where) const { if (simple_compile) { - return dest.append(simple_compile(out, in)); + return dest.append(simple_compile(out, in, where)); } else if (ext_compile) { return ext_compile(dest, out, in); } else { @@ -173,6 +178,83 @@ int emulate_mul(int a, int b) { return r; } +int emulate_and(int a, int b) { + int both = a & b, any = a | b; + int r = VarDescr::_Int; + if (any & VarDescr::_Nan) { + return r | VarDescr::_Nan; + } + r |= VarDescr::_Finite; + if (any & VarDescr::_Zero) { + return VarDescr::ConstZero; + } + r |= both & (VarDescr::_Even | VarDescr::_Odd); + r |= both & (VarDescr::_Bit | VarDescr::_Bool); + if (both & VarDescr::_Odd) { + r |= VarDescr::_NonZero; + } + return r; +} + +int emulate_or(int a, int b) { + if (b & VarDescr::_Zero) { + return a; + } else if (a & VarDescr::_Zero) { + return b; + } + int both = a & b, any = a | b; + int r = VarDescr::_Int; + if (any & VarDescr::_Nan) { + return r | VarDescr::_Nan; + } + r |= VarDescr::_Finite; + r |= any & VarDescr::_NonZero; + r |= any & VarDescr::_Odd; + r |= both & VarDescr::_Even; + return r; +} + +int emulate_xor(int a, int b) { + if (b & VarDescr::_Zero) { + return a; + } else if (a & VarDescr::_Zero) { + return b; + } + int both = a & b, any = a | b; + int r = VarDescr::_Int; + if (any & VarDescr::_Nan) { + return r | VarDescr::_Nan; + } + r |= VarDescr::_Finite; + r |= both & VarDescr::_Even; + if (both & VarDescr::_Odd) { + r |= VarDescr::_Even; + } + return r; +} + +int emulate_not(int a) { + if ((a & VarDescr::ConstZero) == VarDescr::ConstZero) { + return VarDescr::ConstTrue; + } + if ((a & VarDescr::ConstTrue) == VarDescr::ConstTrue) { + return VarDescr::ConstZero; + } + int a2 = a; + int f = VarDescr::_Even | VarDescr::_Odd; + if ((a2 & f) && (~a2 & f)) { + a2 ^= f; + } + a2 &= ~(VarDescr::_Zero | VarDescr::_NonZero | VarDescr::_Bit | VarDescr::_Pos | VarDescr::_Neg); + if ((a & VarDescr::_Neg) && (a & VarDescr::_NonZero)) { + a2 |= VarDescr::_Pos; + } + if (a & VarDescr::_Pos) { + a2 |= VarDescr::_Neg; + } + return a2; +} + int emulate_lshift(int a, int b) { if (((a | b) & VarDescr::_Nan) || !(~b & (VarDescr::_Neg | VarDescr::_NonZero))) { return VarDescr::_Int | VarDescr::_Nan; @@ -189,7 +271,7 @@ int emulate_div(int a, int b) { if ((b & (VarDescr::_NonZero | VarDescr::_Bit)) == (VarDescr::_NonZero | VarDescr::_Bit)) { return a; } else if ((b & (VarDescr::_NonZero | VarDescr::_Bool)) == (VarDescr::_NonZero | VarDescr::_Bool)) { - return emulate_negate(b); + return emulate_negate(a); } if (b & VarDescr::_Zero) { return VarDescr::_Int | VarDescr::_Nan; @@ -346,11 +428,14 @@ AsmOp push_const(td::RefInt256 x) { return AsmOp::IntConst(std::move(x)); } -AsmOp compile_add(std::vector& res, std::vector& args) { - assert(res.size() == 1 && args.size() == 2); +AsmOp compile_add(std::vector& res, std::vector& args, const SrcLocation& where) { + func_assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; if (x.is_int_const() && y.is_int_const()) { r.set_const(x.int_const + y.int_const); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, "integer overflow"); + } x.replaced(); y.replaced(); return push_const(r.int_const); @@ -385,11 +470,14 @@ AsmOp compile_add(std::vector& res, std::vector& args) { return exec_op("ADD", 2); } -AsmOp compile_sub(std::vector& res, std::vector& args) { - assert(res.size() == 1 && args.size() == 2); +AsmOp compile_sub(std::vector& res, std::vector& args, const SrcLocation& where) { + func_assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; if (x.is_int_const() && y.is_int_const()) { r.set_const(x.int_const - y.int_const); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, "integer overflow"); + } x.replaced(); y.replaced(); return push_const(r.int_const); @@ -415,11 +503,14 @@ AsmOp compile_sub(std::vector& res, std::vector& args) { return exec_op("SUB", 2); } -AsmOp compile_negate(std::vector& res, std::vector& args) { - assert(res.size() == 1 && args.size() == 1); +AsmOp compile_negate(std::vector& res, std::vector& args, const SrcLocation& where) { + func_assert(res.size() == 1 && args.size() == 1); VarDescr &r = res[0], &x = args[0]; if (x.is_int_const()) { r.set_const(-x.int_const); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, "integer overflow"); + } x.replaced(); return push_const(r.int_const); } @@ -427,11 +518,63 @@ AsmOp compile_negate(std::vector& res, std::vector& args) { return exec_op("NEGATE", 1); } -AsmOp compile_mul(std::vector& res, std::vector& args) { - assert(res.size() == 1 && args.size() == 2); +AsmOp compile_and(std::vector& res, std::vector& args, const SrcLocation& where) { + func_assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; + if (x.is_int_const() && y.is_int_const()) { + r.set_const(x.int_const & y.int_const); + x.replaced(); + y.replaced(); + return push_const(r.int_const); + } + r.val = emulate_and(x.val, y.val); + return exec_op("AND", 2); +} + +AsmOp compile_or(std::vector& res, std::vector& args, const SrcLocation& where) { + func_assert(res.size() == 1 && args.size() == 2); + VarDescr &r = res[0], &x = args[0], &y = args[1]; + if (x.is_int_const() && y.is_int_const()) { + r.set_const(x.int_const | y.int_const); + x.replaced(); + y.replaced(); + return push_const(r.int_const); + } + r.val = emulate_or(x.val, y.val); + return exec_op("OR", 2); +} + +AsmOp compile_xor(std::vector& res, std::vector& args, const SrcLocation& where) { + func_assert(res.size() == 1 && args.size() == 2); + VarDescr &r = res[0], &x = args[0], &y = args[1]; + if (x.is_int_const() && y.is_int_const()) { + r.set_const(x.int_const ^ y.int_const); + x.replaced(); + y.replaced(); + return push_const(r.int_const); + } + r.val = emulate_xor(x.val, y.val); + return exec_op("XOR", 2); +} + +AsmOp compile_not(std::vector& res, std::vector& args, const SrcLocation& where) { + func_assert(res.size() == 1 && args.size() == 1); + VarDescr &r = res[0], &x = args[0]; + if (x.is_int_const()) { + r.set_const(~x.int_const); + x.replaced(); + return push_const(r.int_const); + } + r.val = emulate_not(x.val); + return exec_op("NOT", 1); +} + +AsmOp compile_mul_internal(VarDescr& r, VarDescr& x, VarDescr& y, const SrcLocation& where) { if (x.is_int_const() && y.is_int_const()) { r.set_const(x.int_const * y.int_const); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, "integer overflow"); + } x.replaced(); y.replaced(); return push_const(r.int_const); @@ -444,6 +587,7 @@ AsmOp compile_mul(std::vector& res, std::vector& args) { if (y.always_zero() && x.always_finite()) { // dubious optimization: NaN * 0 = ? r.set_const(y.int_const); + x.replaced(); return push_const(r.int_const); } if (*y.int_const == 1 && x.always_finite()) { @@ -470,6 +614,7 @@ AsmOp compile_mul(std::vector& res, std::vector& args) { if (x.always_zero() && y.always_finite()) { // dubious optimization: NaN * 0 = ? r.set_const(x.int_const); + y.replaced(); return push_const(r.int_const); } if (*x.int_const == 1 && y.always_finite()) { @@ -492,18 +637,23 @@ AsmOp compile_mul(std::vector& res, std::vector& args) { return exec_op("MUL", 2); } -AsmOp compile_lshift(std::vector& res, std::vector& args) { - assert(res.size() == 1 && args.size() == 2); +AsmOp compile_mul(std::vector& res, std::vector& args, const SrcLocation& where) { + func_assert(res.size() == 1 && args.size() == 2); + return compile_mul_internal(res[0], args[0], args[1], where); +} + +AsmOp compile_lshift(std::vector& res, std::vector& args, const SrcLocation& where) { + func_assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; if (y.is_int_const()) { auto yv = y.int_const->to_long(); if (yv < 0 || yv > 256) { - r.set_const_nan(); - x.replaced(); - y.replaced(); - return push_const(r.int_const); + throw src::ParseError(where, "lshift argument is out of range"); } else if (x.is_int_const()) { r.set_const(x.int_const << (int)yv); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, "integer overflow"); + } x.replaced(); y.replaced(); return push_const(r.int_const); @@ -528,22 +678,20 @@ AsmOp compile_lshift(std::vector& res, std::vector& args) { } if (xv == -1) { x.replaced(); - return exec_op("NEGPOW2", 1); + return exec_op("-1 PUSHINT SWAP LSHIFT", 1); } } return exec_op("LSHIFT", 2); } -AsmOp compile_rshift(std::vector& res, std::vector& args, int round_mode) { - assert(res.size() == 1 && args.size() == 2); +AsmOp compile_rshift(std::vector& res, std::vector& args, const SrcLocation& where, + int round_mode) { + func_assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; if (y.is_int_const()) { auto yv = y.int_const->to_long(); if (yv < 0 || yv > 256) { - r.set_const_nan(); - x.replaced(); - y.replaced(); - return push_const(r.int_const); + throw src::ParseError(where, "rshift argument is out of range"); } else if (x.is_int_const()) { r.set_const(td::rshift(x.int_const, (int)yv, round_mode)); x.replaced(); @@ -566,11 +714,12 @@ AsmOp compile_rshift(std::vector& res, std::vector& args, in return exec_op(rshift, 2); } -AsmOp compile_div(std::vector& res, std::vector& args, int round_mode) { - assert(res.size() == 1 && args.size() == 2); - VarDescr &r = res[0], &x = args[0], &y = args[1]; +AsmOp compile_div_internal(VarDescr& r, VarDescr& x, VarDescr& y, const SrcLocation& where, int round_mode) { if (x.is_int_const() && y.is_int_const()) { r.set_const(div(x.int_const, y.int_const, round_mode)); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, *y.int_const == 0 ? "division by zero" : "integer overflow"); + } x.replaced(); y.replaced(); return push_const(r.int_const); @@ -578,10 +727,7 @@ AsmOp compile_div(std::vector& res, std::vector& args, int r r.val = emulate_div(x.val, y.val); if (y.is_int_const()) { if (*y.int_const == 0) { - x.replaced(); - y.replaced(); - r.set_const(div(y.int_const, y.int_const)); - return push_const(r.int_const); + throw src::ParseError(where, "division by zero"); } if (*y.int_const == 1 && x.always_finite()) { y.replaced(); @@ -608,11 +754,20 @@ AsmOp compile_div(std::vector& res, std::vector& args, int r return exec_op(op, 2); } -AsmOp compile_mod(std::vector& res, std::vector& args, int round_mode) { - assert(res.size() == 1 && args.size() == 2); +AsmOp compile_div(std::vector& res, std::vector& args, const SrcLocation& where, int round_mode) { + func_assert(res.size() == 1 && args.size() == 2); + return compile_div_internal(res[0], args[0], args[1], where, round_mode); +} + +AsmOp compile_mod(std::vector& res, std::vector& args, const src::SrcLocation& where, + int round_mode) { + func_assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; if (x.is_int_const() && y.is_int_const()) { r.set_const(mod(x.int_const, y.int_const, round_mode)); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, *y.int_const == 0 ? "division by zero" : "integer overflow"); + } x.replaced(); y.replaced(); return push_const(r.int_const); @@ -620,15 +775,12 @@ AsmOp compile_mod(std::vector& res, std::vector& args, int r r.val = emulate_mod(x.val, y.val); if (y.is_int_const()) { if (*y.int_const == 0) { - x.replaced(); - y.replaced(); - r.set_const(mod(y.int_const, y.int_const)); - return push_const(r.int_const); + throw src::ParseError(where, "division by zero"); } if ((*y.int_const == 1 || *y.int_const == -1) && x.always_finite()) { x.replaced(); y.replaced(); - r.set_const(td::RefInt256{true, 0}); + r.set_const(td::zero_refint()); return push_const(r.int_const); } int k = is_pos_pow2(y.int_const); @@ -648,6 +800,87 @@ AsmOp compile_mod(std::vector& res, std::vector& args, int r return exec_op(op, 2); } +AsmOp compile_muldiv(std::vector& res, std::vector& args, const SrcLocation& where, + int round_mode) { + func_assert(res.size() == 1 && args.size() == 3); + VarDescr &r = res[0], &x = args[0], &y = args[1], &z = args[2]; + if (x.is_int_const() && y.is_int_const() && z.is_int_const()) { + r.set_const(muldiv(x.int_const, y.int_const, z.int_const, round_mode)); + if (!r.int_const->is_valid()) { + throw src::ParseError(where, *z.int_const == 0 ? "division by zero" : "integer overflow"); + } + x.replaced(); + y.replaced(); + z.replaced(); + return push_const(r.int_const); + } + if (x.always_zero() || y.always_zero()) { + // dubious optimization for z=0... + x.replaced(); + y.replaced(); + z.replaced(); + r.set_const(td::make_refint(0)); + return push_const(r.int_const); + } + char c = (round_mode < 0) ? 0 : (round_mode > 0 ? 'C' : 'R'); + r.val = emulate_div(emulate_mul(x.val, y.val), z.val); + if (z.is_int_const()) { + if (*z.int_const == 0) { + throw src::ParseError(where, "division by zero"); + } + if (*z.int_const == 1) { + z.unused(); + return compile_mul_internal(r, x, y, where); + } + } + if (y.is_int_const() && *y.int_const == 1) { + y.replaced(); + return compile_div_internal(r, x, z, where, round_mode); + } + if (x.is_int_const() && *x.int_const == 1) { + x.replaced(); + return compile_div_internal(r, y, z, where, round_mode); + } + if (z.is_int_const()) { + int k = is_pos_pow2(z.int_const); + if (k > 0) { + z.replaced(); + std::string op = "MULRSHIFT"; + if (c) { + op += c; + } + return exec_arg_op(op + '#', k, 2); + } + } + if (y.is_int_const()) { + int k = is_pos_pow2(y.int_const); + if (k > 0) { + y.replaced(); + std::string op = "LSHIFT#DIV"; + if (c) { + op += c; + } + return exec_arg_op(op, k, 2); + } + } + if (x.is_int_const()) { + int k = is_pos_pow2(x.int_const); + if (k > 0) { + x.replaced(); + std::string op = "LSHIFT#DIV"; + if (c) { + op += c; + } + return exec_arg_op(op, k, 2); + } + } + std::string op = "MULDIV"; + if (c) { + op += c; + } + return exec_op(op, 3); +} + int compute_compare(td::RefInt256 x, td::RefInt256 y, int mode) { int s = td::cmp(x, y); if (mode == 7) { @@ -690,8 +923,8 @@ int compute_compare(const VarDescr& x, const VarDescr& y, int mode) { } AsmOp compile_cmp_int(std::vector& res, std::vector& args, int mode) { - assert(mode >= 1 && mode <= 7); - assert(res.size() == 1 && args.size() == 2); + func_assert(mode >= 1 && mode <= 7); + func_assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; if (x.is_int_const() && y.is_int_const()) { int v = compute_compare(x.int_const, y.int_const, mode); @@ -702,7 +935,7 @@ AsmOp compile_cmp_int(std::vector& res, std::vector& args, i } int v = compute_compare(x, y, mode); // std::cerr << "compute_compare(" << x << ", " << y << ", " << mode << ") = " << v << std::endl; - assert(v); + func_assert(v); if (!(v & (v - 1))) { r.set_const(v - (v >> 2) - 2); x.replaced(); @@ -737,8 +970,8 @@ AsmOp compile_cmp_int(std::vector& res, std::vector& args, i return exec_op(cmp_names[mode], 2); } -AsmOp compile_throw(std::vector& res, std::vector& args) { - assert(res.empty() && args.size() == 1); +AsmOp compile_throw(std::vector& res, std::vector& args, const SrcLocation&) { + func_assert(res.empty() && args.size() == 1); VarDescr& x = args[0]; if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) { x.replaced(); @@ -749,7 +982,7 @@ AsmOp compile_throw(std::vector& res, std::vector& args) { } AsmOp compile_cond_throw(std::vector& res, std::vector& args, bool mode) { - assert(res.empty() && args.size() == 2); + func_assert(res.empty() && args.size() == 2); VarDescr &x = args[0], &y = args[1]; std::string suff = (mode ? "IF" : "IFNOT"); bool skip_cond = false; @@ -765,12 +998,44 @@ AsmOp compile_cond_throw(std::vector& res, std::vector& args x.replaced(); return skip_cond ? exec_arg_op("THROW", x.int_const, 0, 0) : exec_arg_op("THROW"s + suff, x.int_const, 1, 0); } else { - return skip_cond ? exec_op("THROWANY", 1, 0) : exec_arg_op("THROWANY"s + suff, 2, 0); + return skip_cond ? exec_op("THROWANY", 1, 0) : exec_op("THROWANY"s + suff, 2, 0); + } +} + +AsmOp compile_throw_arg(std::vector& res, std::vector& args, const SrcLocation&) { + func_assert(res.empty() && args.size() == 2); + VarDescr &x = args[1]; + if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) { + x.replaced(); + return exec_arg_op("THROWARG", x.int_const, 1, 0); + } else { + return exec_op("THROWARGANY", 2, 0); + } +} + +AsmOp compile_cond_throw_arg(std::vector& res, std::vector& args, bool mode) { + func_assert(res.empty() && args.size() == 3); + VarDescr &x = args[1], &y = args[2]; + std::string suff = (mode ? "IF" : "IFNOT"); + bool skip_cond = false; + if (y.always_true() || y.always_false()) { + y.replaced(); + skip_cond = true; + if (y.always_true() != mode) { + x.replaced(); + return AsmOp::Nop(); + } + } + if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) { + x.replaced(); + return skip_cond ? exec_arg_op("THROWARG", x.int_const, 1, 0) : exec_arg_op("THROWARG"s + suff, x.int_const, 2, 0); + } else { + return skip_cond ? exec_op("THROWARGANY", 2, 0) : exec_op("THROWARGANY"s + suff, 3, 0); } } AsmOp compile_bool_const(std::vector& res, std::vector& args, bool val) { - assert(res.size() == 1 && args.empty()); + func_assert(res.size() == 1 && args.empty()); VarDescr& r = res[0]; r.set_const(val ? -1 : 0); return AsmOp::Const(val ? "TRUE" : "FALSE"); @@ -781,7 +1046,7 @@ AsmOp compile_bool_const(std::vector& res, std::vector& args // int preload_int(slice s, int len) asm "PLDIX"; // int preload_uint(slice s, int len) asm "PLDUX"; AsmOp compile_fetch_int(std::vector& res, std::vector& args, bool fetch, bool sgnd) { - assert(args.size() == 2 && res.size() == 1 + (unsigned)fetch); + func_assert(args.size() == 2 && res.size() == 1 + (unsigned)fetch); auto &y = args[1], &r = res.back(); r.val = (sgnd ? VarDescr::FiniteInt : VarDescr::FiniteUInt); int v = -1; @@ -804,7 +1069,7 @@ AsmOp compile_fetch_int(std::vector& res, std::vector& args, // builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; // builder store_int(builder b, int x, int len) asm(x b len) "STIX"; AsmOp compile_store_int(std::vector& res, std::vector& args, bool sgnd) { - assert(args.size() == 3 && res.size() == 1); + func_assert(args.size() == 3 && res.size() == 1); auto& z = args[2]; if (z.is_int_const() && z.int_const > 0 && z.int_const <= 256) { z.replaced(); @@ -814,7 +1079,7 @@ AsmOp compile_store_int(std::vector& res, std::vector& args, } AsmOp compile_fetch_slice(std::vector& res, std::vector& args, bool fetch) { - assert(args.size() == 2 && res.size() == 1 + (unsigned)fetch); + func_assert(args.size() == 2 && res.size() == 1 + (unsigned)fetch); auto& y = args[1]; int v = -1; if (y.is_int_const() && y.int_const > 0 && y.int_const <= 256) { @@ -828,8 +1093,8 @@ AsmOp compile_fetch_slice(std::vector& res, std::vector& arg } // _at(tuple t, int index) asm "INDEXVAR"; -AsmOp compile_tuple_at(std::vector& res, std::vector& args) { - assert(args.size() == 2 && res.size() == 1); +AsmOp compile_tuple_at(std::vector& res, std::vector& args, const SrcLocation&) { + func_assert(args.size() == 2 && res.size() == 1); auto& y = args[1]; if (y.is_int_const() && y.int_const >= 0 && y.int_const < 16) { y.replaced(); @@ -839,8 +1104,8 @@ AsmOp compile_tuple_at(std::vector& res, std::vector& args) } // int null?(X arg) -AsmOp compile_is_null(std::vector& res, std::vector& args) { - assert(args.size() == 1 && res.size() == 1); +AsmOp compile_is_null(std::vector& res, std::vector& args, const SrcLocation&) { + func_assert(args.size() == 1 && res.size() == 1); auto &x = args[0], &r = res[0]; if (x.always_null() || x.always_not_null()) { x.replaced(); @@ -853,7 +1118,7 @@ AsmOp compile_is_null(std::vector& res, std::vector& args) { bool compile_run_method(AsmOpList& code, std::vector& res, std::vector& args, int n, bool has_value) { - assert(args.size() == (unsigned)n + 1 && res.size() == (unsigned)has_value); + func_assert(args.size() == (unsigned)n + 1 && res.size() == (unsigned)has_value); auto& x = args[0]; if (x.is_int_const() && x.int_const->unsigned_fits_bits(14)) { x.replaced(); @@ -894,47 +1159,50 @@ void define_builtins() { auto fetch_slice_op = TypeExpr::new_map(SliceInt, TypeExpr::new_tensor({Slice, Slice})); auto prefetch_slice_op = TypeExpr::new_map(SliceInt, Slice); //auto arith_null_op = TypeExpr::new_map(TypeExpr::new_unit(), Int); + auto throw_arg_op = TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({X, Int}), Unit)); + auto cond_throw_arg_op = TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({X, Int, Int}), Unit)); define_builtin_func("_+_", arith_bin_op, compile_add); define_builtin_func("_-_", arith_bin_op, compile_sub); define_builtin_func("-_", arith_un_op, compile_negate); define_builtin_func("_*_", arith_bin_op, compile_mul); - define_builtin_func("_/_", arith_bin_op, std::bind(compile_div, _1, _2, -1)); - define_builtin_func("_~/_", arith_bin_op, std::bind(compile_div, _1, _2, 0)); - define_builtin_func("_^/_", arith_bin_op, std::bind(compile_div, _1, _2, 1)); - define_builtin_func("_%_", arith_bin_op, std::bind(compile_mod, _1, _2, -1)); - define_builtin_func("_~%_", arith_bin_op, std::bind(compile_mod, _1, _2, 0)); - define_builtin_func("_^%_", arith_bin_op, std::bind(compile_mod, _1, _2, 1)); + define_builtin_func("_/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, -1)); + define_builtin_func("_~/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 0)); + define_builtin_func("_^/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 1)); + define_builtin_func("_%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1)); + define_builtin_func("_~%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 0)); + define_builtin_func("_^%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 1)); define_builtin_func("_/%_", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2)); define_builtin_func("divmod", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2)); define_builtin_func("~divmod", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2)); define_builtin_func("moddiv", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2), {}, {1, 0}); define_builtin_func("~moddiv", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2), {}, {1, 0}); define_builtin_func("_<<_", arith_bin_op, compile_lshift); - define_builtin_func("_>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, -1)); - define_builtin_func("_~>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, 0)); - define_builtin_func("_^>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, 1)); - define_builtin_func("_&_", arith_bin_op, AsmOp::Custom("AND", 2)); - define_builtin_func("_|_", arith_bin_op, AsmOp::Custom("OR", 2)); - define_builtin_func("_^_", arith_bin_op, AsmOp::Custom("XOR", 2)); - define_builtin_func("~_", arith_un_op, AsmOp::Custom("NOT", 1)); + define_builtin_func("_>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1)); + define_builtin_func("_~>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 0)); + define_builtin_func("_^>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 1)); + define_builtin_func("_&_", arith_bin_op, compile_and); + define_builtin_func("_|_", arith_bin_op, compile_or); + define_builtin_func("_^_", arith_bin_op, compile_xor); + define_builtin_func("~_", arith_un_op, compile_not); define_builtin_func("^_+=_", arith_bin_op, compile_add); define_builtin_func("^_-=_", arith_bin_op, compile_sub); define_builtin_func("^_*=_", arith_bin_op, compile_mul); - define_builtin_func("^_/=_", arith_bin_op, std::bind(compile_div, _1, _2, -1)); - define_builtin_func("^_~/=_", arith_bin_op, std::bind(compile_div, _1, _2, 0)); - define_builtin_func("^_^/=_", arith_bin_op, std::bind(compile_div, _1, _2, 1)); - define_builtin_func("^_%=_", arith_bin_op, std::bind(compile_mod, _1, _2, -1)); - define_builtin_func("^_~%=_", arith_bin_op, std::bind(compile_mod, _1, _2, 0)); - define_builtin_func("^_^%=_", arith_bin_op, std::bind(compile_mod, _1, _2, 1)); + define_builtin_func("^_/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, -1)); + define_builtin_func("^_~/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 0)); + define_builtin_func("^_^/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 1)); + define_builtin_func("^_%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1)); + define_builtin_func("^_~%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 0)); + define_builtin_func("^_^%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 1)); define_builtin_func("^_<<=_", arith_bin_op, compile_lshift); - define_builtin_func("^_>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, -1)); - define_builtin_func("^_~>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, 0)); - define_builtin_func("^_^>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, 1)); - define_builtin_func("^_&=_", arith_bin_op, AsmOp::Custom("AND", 2)); - define_builtin_func("^_|=_", arith_bin_op, AsmOp::Custom("OR", 2)); - define_builtin_func("^_^=_", arith_bin_op, AsmOp::Custom("XOR", 2)); - define_builtin_func("muldivr", TypeExpr::new_map(Int3, Int), AsmOp::Custom("MULDIVR", 3)); - define_builtin_func("muldiv", TypeExpr::new_map(Int3, Int), AsmOp::Custom("MULDIV", 3)); + define_builtin_func("^_>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1)); + define_builtin_func("^_~>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 0)); + define_builtin_func("^_^>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 1)); + define_builtin_func("^_&=_", arith_bin_op, compile_and); + define_builtin_func("^_|=_", arith_bin_op, compile_or); + define_builtin_func("^_^=_", arith_bin_op, compile_xor); + define_builtin_func("muldiv", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, -1)); + define_builtin_func("muldivr", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 0)); + define_builtin_func("muldivc", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 1)); define_builtin_func("muldivmod", TypeExpr::new_map(Int3, Int2), AsmOp::Custom("MULDIVMOD", 3, 2)); define_builtin_func("_==_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 2)); define_builtin_func("_!=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 5)); @@ -952,6 +1220,9 @@ void define_builtins() { define_builtin_func("throw", impure_un_op, compile_throw, true); define_builtin_func("throw_if", impure_bin_op, std::bind(compile_cond_throw, _1, _2, true), true); define_builtin_func("throw_unless", impure_bin_op, std::bind(compile_cond_throw, _1, _2, false), true); + define_builtin_func("throw_arg", throw_arg_op, compile_throw_arg, true); + define_builtin_func("throw_arg_if", cond_throw_arg_op, std::bind(compile_cond_throw_arg, _1, _2, true), true); + define_builtin_func("throw_arg_unless", cond_throw_arg_op, std::bind(compile_cond_throw_arg, _1, _2, false), true); define_builtin_func("load_int", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, true), {}, {1, 0}); define_builtin_func("load_uint", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, false), {}, {1, 0}); define_builtin_func("preload_int", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, true)); @@ -975,16 +1246,20 @@ void define_builtins() { AsmOp::Nop()); define_builtin_func("~dump", TypeExpr::new_forall({X}, TypeExpr::new_map(X, TypeExpr::new_tensor({X, Unit}))), AsmOp::Custom("s0 DUMP", 1, 1), true); - define_builtin_func("run_method0", TypeExpr::new_map(Int, Unit), - [](auto a, auto b, auto c) { return compile_run_method(a, b, c, 0, false); }, true); - define_builtin_func("run_method1", TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({Int, X}), Unit)), - [](auto a, auto b, auto c) { return compile_run_method(a, b, c, 1, false); }, {1, 0}, {}, true); + define_builtin_func("~strdump", TypeExpr::new_forall({X}, TypeExpr::new_map(X, TypeExpr::new_tensor({X, Unit}))), + AsmOp::Custom("STRDUMP", 1, 1), true); + define_builtin_func( + "run_method0", TypeExpr::new_map(Int, Unit), + [](AsmOpList& a, auto b, auto c) { return compile_run_method(a, b, c, 0, false); }, true); + define_builtin_func( + "run_method1", TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({Int, X}), Unit)), + [](AsmOpList& a, auto b, auto c) { return compile_run_method(a, b, c, 1, false); }, {1, 0}, {}, true); define_builtin_func( "run_method2", TypeExpr::new_forall({X, Y}, TypeExpr::new_map(TypeExpr::new_tensor({Int, X, Y}), Unit)), - [](auto a, auto b, auto c) { return compile_run_method(a, b, c, 2, false); }, {1, 2, 0}, {}, true); + [](AsmOpList& a, auto b, auto c) { return compile_run_method(a, b, c, 2, false); }, {1, 2, 0}, {}, true); define_builtin_func( "run_method3", TypeExpr::new_forall({X, Y, Z}, TypeExpr::new_map(TypeExpr::new_tensor({Int, X, Y, Z}), Unit)), - [](auto a, auto b, auto c) { return compile_run_method(a, b, c, 3, false); }, {1, 2, 3, 0}, {}, true); + [](AsmOpList& a, auto b, auto c) { return compile_run_method(a, b, c, 3, false); }, {1, 2, 3, 0}, {}, true); } } // namespace funC diff --git a/crypto/func/codegen.cpp b/crypto/func/codegen.cpp index 8e644564..c36be299 100644 --- a/crypto/func/codegen.cpp +++ b/crypto/func/codegen.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "func.h" @@ -165,7 +165,7 @@ void Stack::push_new_const(var_idx_t idx, const_idx_t cidx) { void Stack::assign_var(var_idx_t new_idx, var_idx_t old_idx) { int i = find(old_idx); - assert(i >= 0 && "variable not found in stack"); + func_assert(i >= 0 && "variable not found in stack"); if (new_idx != old_idx) { at(i).first = new_idx; modified(); @@ -174,10 +174,10 @@ void Stack::assign_var(var_idx_t new_idx, var_idx_t old_idx) { void Stack::do_copy_var(var_idx_t new_idx, var_idx_t old_idx) { int i = find(old_idx); - assert(i >= 0 && "variable not found in stack"); + func_assert(i >= 0 && "variable not found in stack"); if (find(old_idx, i + 1) < 0) { issue_push(i); - assert(at(0).first == old_idx); + func_assert(at(0).first == old_idx); } assign_var(new_idx, old_idx); } @@ -199,21 +199,21 @@ void Stack::enforce_state(const StackLayout& req_stack) { j = 0; } issue_xchg(j, depth() - i - 1); - assert(s[i].first == x); + func_assert(s[i].first == x); } while (depth() > k) { issue_pop(0); } - assert(depth() == k); + func_assert(depth() == k); for (int i = 0; i < k; i++) { - assert(s[i].first == req_stack[i]); + func_assert(s[i].first == req_stack[i]); } } void Stack::merge_const(const Stack& req_stack) { - assert(s.size() == req_stack.s.size()); + func_assert(s.size() == req_stack.s.size()); for (std::size_t i = 0; i < s.size(); i++) { - assert(s[i].first == req_stack.s[i].first); + func_assert(s[i].first == req_stack.s[i].first); if (s[i].second != req_stack.s[i].second) { s[i].second = not_const; } @@ -251,15 +251,15 @@ void Stack::rearrange_top(const StackLayout& top, std::vector last) { if (last[i]) { // rearrange x to be at s(ss-1) issue_xchg(--ss, j); - assert(get(ss).first == x); + func_assert(get(ss).first == x); } else { // create a new copy of x issue_push(j); issue_xchg(0, ss); - assert(get(ss).first == x); + func_assert(get(ss).first == x); } } - assert(!ss); + func_assert(!ss); } void Stack::rearrange_top(var_idx_t top, bool last) { @@ -269,25 +269,28 @@ void Stack::rearrange_top(var_idx_t top, bool last) { } else { issue_push(i); } - assert(get(0).first == top); + func_assert(get(0).first == top); } bool Op::generate_code_step(Stack& stack) { stack.opt_show(); stack.drop_vars_except(var_info); stack.opt_show(); - const auto& next_var_info = next->var_info; + bool inline_func = stack.mode & Stack::_InlineFunc; switch (cl) { case _Nop: case _Import: return true; case _Return: { stack.enforce_state(left); + if (stack.o.retalt_ && (stack.mode & Stack::_NeedRetAlt)) { + stack.o << "RETALT"; + } stack.opt_show(); return false; } case _IntConst: { - auto p = next_var_info[left[0]]; + auto p = next->var_info[left[0]]; if (!p || p->is_unused()) { return true; } @@ -297,16 +300,25 @@ bool Op::generate_code_step(Stack& stack) { stack.o << push_const(int_const); stack.push_new_const(left[0], cidx); } else { - assert(stack.at(i).second == cidx); + func_assert(stack.at(i).second == cidx); stack.do_copy_var(left[0], stack[i]); } return true; } + case _SliceConst: { + auto p = next->var_info[left[0]]; + if (!p || p->is_unused()) { + return true; + } + stack.o << AsmOp::Const("x{" + str_const + "} PUSHSLICE"); + stack.push_new_var(left[0]); + return true; + } case _GlobVar: if (dynamic_cast(fun_ref->value)) { bool used = false; for (auto i : left) { - auto p = next_var_info[i]; + auto p = next->var_info[i]; if (p && !p->is_unused()) { used = true; } @@ -317,16 +329,16 @@ bool Op::generate_code_step(Stack& stack) { std::string name = sym::symbols.get_name(fun_ref->sym_idx); stack.o << AsmOp::Custom(name + " GETGLOB", 0, 1); if (left.size() != 1) { - assert(left.size() <= 15); - stack.o << exec_arg_op("UNTUPLE", (int)left.size(), 1, (int)left.size()); + func_assert(left.size() <= 15); + stack.o << AsmOp::UnTuple((int)left.size()); } for (auto i : left) { stack.push_new_var(i); } return true; } else { - assert(left.size() == 1); - auto p = next_var_info[left[0]]; + func_assert(left.size() == 1); + auto p = next->var_info[left[0]]; if (!p || p->is_unused() || disabled()) { return true; } @@ -337,17 +349,17 @@ bool Op::generate_code_step(Stack& stack) { // TODO: create and compile a true lambda instead of this (so that arg_order and ret_order would work correctly) std::vector args0, res; TypeExpr::remove_indirect(func->sym_type); - assert(func->get_type()->is_map()); + func_assert(func->get_type()->is_map()); auto wr = func->get_type()->args.at(0)->get_width(); auto wl = func->get_type()->args.at(1)->get_width(); - assert(wl >= 0 && wr >= 0); + func_assert(wl >= 0 && wr >= 0); for (int i = 0; i < wl; i++) { res.emplace_back(0); } for (int i = 0; i < wr; i++) { args0.emplace_back(0); } - func->compile(stack.o, res, args0); // compile res := f (args0) + func->compile(stack.o, res, args0, where); // compile res := f (args0) } else { std::string name = sym::symbols.get_name(fun_ref->sym_idx); stack.o << AsmOp::Custom(name + " CALLDICT", (int)right.size(), (int)left.size()); @@ -358,14 +370,14 @@ bool Op::generate_code_step(Stack& stack) { return true; } case _Let: { - assert(left.size() == right.size()); + func_assert(left.size() == right.size()); int i = 0; std::vector active; active.reserve(left.size()); int unused = 0; for (std::size_t k = 0; k < left.size(); k++) { var_idx_t y = left[k]; // "y" = "x" - auto p = next_var_info[y]; + auto p = next->var_info[y]; active.push_back(p && !p->is_unused()); if (p && p->is_unused() && !p->is_replaced()) ++unused; @@ -409,6 +421,32 @@ bool Op::generate_code_step(Stack& stack) { } return true; } + case _Tuple: + case _UnTuple: { + if (disabled()) { + return true; + } + std::vector last; + for (var_idx_t x : right) { + last.push_back(var_info[x] && var_info[x]->is_last()); + } + stack.rearrange_top(right, std::move(last)); + stack.opt_show(); + int k = (int)stack.depth() - (int)right.size(); + func_assert(k >= 0); + if (cl == _Tuple) { + stack.o << AsmOp::Tuple((int)right.size()); + func_assert(left.size() == 1); + } else { + stack.o << AsmOp::UnTuple((int)left.size()); + func_assert(right.size() == 1); + } + stack.s.resize(k); + for (int i = 0; i < (int)left.size(); i++) { + stack.push_new_var(left.at(i)); + } + return true; + } case _Call: case _CallInd: { if (disabled()) { @@ -427,16 +465,16 @@ bool Op::generate_code_step(Stack& stack) { SymValFunc* func = (fun_ref ? dynamic_cast(fun_ref->value) : nullptr); auto arg_order = (func ? func->get_arg_order() : nullptr); auto ret_order = (func ? func->get_ret_order() : nullptr); - assert(!arg_order || arg_order->size() == right.size()); - assert(!ret_order || ret_order->size() == left.size()); + func_assert(!arg_order || arg_order->size() == right.size()); + func_assert(!ret_order || ret_order->size() == left.size()); std::vector right1; if (args.size()) { - assert(args.size() == right.size()); + func_assert(args.size() == right.size()); for (int i = 0; i < (int)right.size(); i++) { int j = arg_order ? arg_order->at(i) : i; const VarDescr& arg = args.at(j); if (!arg.is_unused()) { - assert(var_info[arg.idx] && !var_info[arg.idx]->is_unused()); + func_assert(var_info[arg.idx] && !var_info[arg.idx]->is_unused()); right1.push_back(arg.idx); } } @@ -454,17 +492,25 @@ bool Op::generate_code_step(Stack& stack) { stack.rearrange_top(right1, std::move(last)); stack.opt_show(); int k = (int)stack.depth() - (int)right1.size(); - assert(k >= 0); + func_assert(k >= 0); for (int i = 0; i < (int)right1.size(); i++) { if (stack.s[k + i].first != right1[i]) { std::cerr << stack.o; } - assert(stack.s[k + i].first == right1[i]); + func_assert(stack.s[k + i].first == right1[i]); } + auto exec_callxargs = [&](int args, int ret) { + if (args <= 15 && ret <= 15) { + stack.o << exec_arg2_op("CALLXARGS", args, ret, args + 1, ret); + } else { + func_assert(args <= 254 && ret <= 254); + stack.o << AsmOp::Const(PSTRING() << args << " PUSHINT"); + stack.o << AsmOp::Const(PSTRING() << ret << " PUSHINT"); + stack.o << AsmOp::Custom("CALLXVARARGS", args + 3, ret); + } + }; if (cl == _CallInd) { - // TODO: replace with exec_arg2_op() - stack.o << exec_arg2_op("CALLXARGS", (int)right.size() - 1, (int)left.size(), (int)right.size(), - (int)left.size()); + exec_callxargs((int)right.size() - 1, (int)left.size()); } else { auto func = dynamic_cast(fun_ref->value); if (func) { @@ -473,13 +519,19 @@ bool Op::generate_code_step(Stack& stack) { for (var_idx_t i : left) { res.emplace_back(i); } - func->compile(stack.o, res, args); // compile res := f (args) + func->compile(stack.o, res, args, where); // compile res := f (args) } else { auto fv = dynamic_cast(fun_ref->value); std::string name = sym::symbols.get_name(fun_ref->sym_idx); bool is_inline = (fv && (fv->flags & 3)); - stack.o << AsmOp::Custom(name + (is_inline ? " INLINECALLDICT" : " CALLDICT"), (int)right.size(), - (int)left.size()); + if (is_inline) { + stack.o << AsmOp::Custom(name + " INLINECALLDICT", (int)right.size(), (int)left.size()); + } else if (fv && fv->code && fv->code->require_callxargs) { + stack.o << AsmOp::Custom(name + (" PREPAREDICT"), 0, 2); + exec_callxargs((int)right.size() + 1, (int)left.size()); + } else { + stack.o << AsmOp::Custom(name + " CALLDICT", (int)right.size(), (int)left.size()); + } } } stack.s.resize(k); @@ -490,7 +542,7 @@ bool Op::generate_code_step(Stack& stack) { return true; } case _SetGlob: { - assert(fun_ref && dynamic_cast(fun_ref->value)); + func_assert(fun_ref && dynamic_cast(fun_ref->value)); std::vector last; for (var_idx_t x : right) { last.push_back(var_info[x] && var_info[x]->is_last()); @@ -498,15 +550,15 @@ bool Op::generate_code_step(Stack& stack) { stack.rearrange_top(right, std::move(last)); stack.opt_show(); int k = (int)stack.depth() - (int)right.size(); - assert(k >= 0); + func_assert(k >= 0); for (int i = 0; i < (int)right.size(); i++) { if (stack.s[k + i].first != right[i]) { std::cerr << stack.o; } - assert(stack.s[k + i].first == right[i]); + func_assert(stack.s[k + i].first == right[i]); } if (right.size() > 1) { - stack.o << exec_arg_op("TUPLE", (int)right.size(), (int)right.size(), 1); + stack.o << AsmOp::Tuple((int)right.size()); } if (!right.empty()) { std::string name = sym::symbols.get_name(fun_ref->sym_idx); @@ -520,81 +572,65 @@ bool Op::generate_code_step(Stack& stack) { return true; } if (!next->noreturn() && (block0->noreturn() != block1->noreturn())) { - // simple fix of unbalanced returns in if/else branches - // (to be replaced with a finer condition working in loop bodies) - throw src::ParseError{where, "`if` and `else` branches should both return or both not return"}; + stack.o.retalt_ = true; } var_idx_t x = left[0]; stack.rearrange_top(x, var_info[x] && var_info[x]->is_last()); - assert(stack[0] == x); + func_assert(stack[0] == x); stack.opt_show(); stack.s.pop_back(); stack.modified(); - if (block1->is_empty()) { + if (inline_func && (block0->noreturn() || block1->noreturn())) { + bool is0 = block0->noreturn(); + Op* block_noreturn = is0 ? block0.get() : block1.get(); + Op* block_other = is0 ? block1.get() : block0.get(); + stack.mode &= ~Stack::_InlineFunc; + stack.o << (is0 ? "IF:<{" : "IFNOT:<{"); + stack.o.indent(); + Stack stack_copy{stack}; + block_noreturn->generate_code_all(stack_copy); + stack.o.undent(); + stack.o << "}>ELSE<{"; + stack.o.indent(); + block_other->generate_code_all(stack); + if (!block_other->noreturn()) { + next->generate_code_all(stack); + } + stack.o.undent(); + stack.o << "}>"; + return false; + } + if (block1->is_empty() || block0->is_empty()) { + bool is0 = block1->is_empty(); + Op* block = is0 ? block0.get() : block1.get(); // if (left) block0; ... - if (block0->noreturn()) { - stack.o << "IFJMP:<{"; - stack.o.indent(); - Stack stack_copy{stack}; - block0->generate_code_all(stack_copy); - stack.o.undent(); - stack.o << "}>"; - return true; - } - stack.o << "IF:<{"; - stack.o.indent(); - Stack stack_copy{stack}, stack_target{stack}; - stack_target.disable_output(); - stack_target.drop_vars_except(next->var_info); - block0->generate_code_all(stack_copy); - stack_copy.drop_vars_except(var_info); - stack_copy.opt_show(); - if (stack_copy == stack) { - stack.o.undent(); - stack.o << "}>"; - return true; - } - // stack_copy.drop_vars_except(next->var_info); - stack_copy.enforce_state(stack_target.vars()); - stack_copy.opt_show(); - if (stack_copy.vars() == stack.vars()) { - stack.o.undent(); - stack.o << "}>"; - stack.merge_const(stack_copy); - return true; - } - stack.o.undent(); - stack.o << "}>ELSE<{"; - stack.o.indent(); - stack.merge_state(stack_copy); - stack.opt_show(); - stack.o.undent(); - stack.o << "}>"; - return true; - } - if (block0->is_empty()) { // if (!left) block1; ... - if (block1->noreturn()) { - stack.o << "IFNOTJMP:<{"; + if (block->noreturn()) { + stack.o << (is0 ? "IFJMP:<{" : "IFNOTJMP:<{"); stack.o.indent(); Stack stack_copy{stack}; - block1->generate_code_all(stack_copy); + stack_copy.mode &= ~Stack::_InlineFunc; + stack_copy.mode |= next->noreturn() ? 0 : Stack::_NeedRetAlt; + block->generate_code_all(stack_copy); stack.o.undent(); stack.o << "}>"; return true; } - stack.o << "IFNOT:<{"; + stack.o << (is0 ? "IF:<{" : "IFNOT:<{"); stack.o.indent(); Stack stack_copy{stack}, stack_target{stack}; stack_target.disable_output(); stack_target.drop_vars_except(next->var_info); - block1->generate_code_all(stack_copy); + stack_copy.mode &= ~Stack::_InlineFunc; + block->generate_code_all(stack_copy); stack_copy.drop_vars_except(var_info); stack_copy.opt_show(); - if (stack_copy.vars() == stack.vars()) { + if ((is0 && stack_copy == stack) || (!is0 && stack_copy.vars() == stack.vars())) { stack.o.undent(); stack.o << "}>"; - stack.merge_const(stack_copy); + if (!is0) { + stack.merge_const(stack_copy); + } return true; } // stack_copy.drop_vars_except(next->var_info); @@ -615,33 +651,32 @@ bool Op::generate_code_step(Stack& stack) { stack.o << "}>"; return true; } - if (block0->noreturn()) { - stack.o << "IFJMP:<{"; + if (block0->noreturn() || block1->noreturn()) { + bool is0 = block0->noreturn(); + Op* block_noreturn = is0 ? block0.get() : block1.get(); + Op* block_other = is0 ? block1.get() : block0.get(); + stack.o << (is0 ? "IFJMP:<{" : "IFNOTJMP:<{"); stack.o.indent(); Stack stack_copy{stack}; - block0->generate_code_all(stack_copy); + stack_copy.mode &= ~Stack::_InlineFunc; + stack_copy.mode |= (block_other->noreturn() || next->noreturn()) ? 0 : Stack::_NeedRetAlt; + block_noreturn->generate_code_all(stack_copy); stack.o.undent(); stack.o << "}>"; - return block1->generate_code_all(stack); - } - if (block1->noreturn()) { - stack.o << "IFNOTJMP:<{"; - stack.o.indent(); - Stack stack_copy{stack}; - block1->generate_code_all(stack_copy); - stack.o.undent(); - stack.o << "}>"; - return block0->generate_code_all(stack); + block_other->generate_code_all(stack); + return !block_other->noreturn(); } stack.o << "IF:<{"; stack.o.indent(); Stack stack_copy{stack}; + stack_copy.mode &= ~Stack::_InlineFunc; block0->generate_code_all(stack_copy); stack_copy.drop_vars_except(next->var_info); stack_copy.opt_show(); stack.o.undent(); stack.o << "}>ELSE<{"; stack.o.indent(); + stack.mode &= ~Stack::_InlineFunc; block1->generate_code_all(stack); stack.merge_state(stack_copy); stack.opt_show(); @@ -653,18 +688,31 @@ bool Op::generate_code_step(Stack& stack) { var_idx_t x = left[0]; //stack.drop_vars_except(block0->var_info, x); stack.rearrange_top(x, var_info[x] && var_info[x]->is_last()); - assert(stack[0] == x); + func_assert(stack[0] == x); stack.opt_show(); stack.s.pop_back(); stack.modified(); + if (block0->noreturn()) { + stack.o.retalt_ = true; + } if (true || !next->is_empty()) { stack.o << "REPEAT:<{"; stack.o.indent(); stack.forget_const(); - StackLayout layout1 = stack.vars(); - block0->generate_code_all(stack); - stack.enforce_state(std::move(layout1)); - stack.opt_show(); + if (block0->noreturn()) { + Stack stack_copy{stack}; + StackLayout layout1 = stack.vars(); + stack_copy.mode &= ~Stack::_InlineFunc; + stack_copy.mode |= Stack::_NeedRetAlt; + block0->generate_code_all(stack_copy); + } else { + StackLayout layout1 = stack.vars(); + stack.mode &= ~Stack::_InlineFunc; + stack.mode |= Stack::_NeedRetAlt; + block0->generate_code_all(stack); + stack.enforce_state(std::move(layout1)); + stack.opt_show(); + } stack.o.undent(); stack.o << "}>"; return true; @@ -681,11 +729,16 @@ bool Op::generate_code_step(Stack& stack) { case _Again: { stack.drop_vars_except(block0->var_info); stack.opt_show(); - if (!next->is_empty()) { + if (block0->noreturn()) { + stack.o.retalt_ = true; + } + if (!next->is_empty() || inline_func) { stack.o << "AGAIN:<{"; stack.o.indent(); stack.forget_const(); StackLayout layout1 = stack.vars(); + stack.mode &= ~Stack::_InlineFunc; + stack.mode |= Stack::_NeedRetAlt; block0->generate_code_all(stack); stack.enforce_state(std::move(layout1)); stack.opt_show(); @@ -705,11 +758,16 @@ bool Op::generate_code_step(Stack& stack) { case _Until: { // stack.drop_vars_except(block0->var_info); // stack.opt_show(); + if (block0->noreturn()) { + stack.o.retalt_ = true; + } if (true || !next->is_empty()) { stack.o << "UNTIL:<{"; stack.o.indent(); stack.forget_const(); auto layout1 = stack.vars(); + stack.mode &= ~Stack::_InlineFunc; + stack.mode |= Stack::_NeedRetAlt; block0->generate_code_all(stack); layout1.push_back(left[0]); stack.enforce_state(std::move(layout1)); @@ -737,9 +795,14 @@ bool Op::generate_code_step(Stack& stack) { stack.opt_show(); StackLayout layout1 = stack.vars(); bool next_empty = false && next->is_empty(); + if (block0->noreturn()) { + stack.o.retalt_ = true; + } stack.o << "WHILE:<{"; stack.o.indent(); stack.forget_const(); + stack.mode &= ~Stack::_InlineFunc; + stack.mode |= Stack::_NeedRetAlt; block0->generate_code_all(stack); stack.rearrange_top(x, !next->var_info[x] && !block1->var_info[x]); stack.opt_show(); @@ -763,27 +826,101 @@ bool Op::generate_code_step(Stack& stack) { return false; } } + case _TryCatch: { + if (block0->is_empty() && block1->is_empty()) { + return true; + } + if (block0->noreturn() || block1->noreturn()) { + stack.o.retalt_ = true; + } + Stack catch_stack{stack.o}; + std::vector catch_vars; + std::vector catch_last; + for (const VarDescr& var : block1->var_info.list) { + if (stack.find(var.idx) >= 0) { + catch_vars.push_back(var.idx); + catch_last.push_back(!block0->var_info[var.idx]); + } + } + const size_t block_size = 255; + for (size_t begin = catch_vars.size(), end = begin; end > 0; end = begin) { + begin = end >= block_size ? end - block_size : 0; + for (size_t i = begin; i < end; ++i) { + catch_stack.push_new_var(catch_vars[i]); + } + } + catch_stack.push_new_var(left[0]); + catch_stack.push_new_var(left[1]); + stack.rearrange_top(catch_vars, catch_last); + stack.opt_show(); + stack.o << "c4 PUSH"; + stack.o << "c5 PUSH"; + stack.o << "c7 PUSH"; + stack.o << "<{"; + stack.o.indent(); + if (block1->noreturn()) { + catch_stack.mode |= Stack::_NeedRetAlt; + } + block1->generate_code_all(catch_stack); + catch_stack.drop_vars_except(next->var_info); + catch_stack.opt_show(); + stack.o.undent(); + stack.o << "}>CONT"; + stack.o << "c7 SETCONT"; + stack.o << "c5 SETCONT"; + stack.o << "c4 SETCONT"; + for (size_t begin = catch_vars.size(), end = begin; end > 0; end = begin) { + begin = end >= block_size ? end - block_size : 0; + stack.o << std::to_string(end - begin) + " PUSHINT"; + stack.o << "-1 PUSHINT"; + stack.o << "SETCONTVARARGS"; + } + stack.s.erase(stack.s.end() - catch_vars.size(), stack.s.end()); + stack.modified(); + stack.o << "<{"; + stack.o.indent(); + if (block0->noreturn()) { + stack.mode |= Stack::_NeedRetAlt; + } + block0->generate_code_all(stack); + if (block0->noreturn()) { + stack.s = std::move(catch_stack.s); + } else if (!block1->noreturn()) { + stack.merge_state(catch_stack); + } + stack.opt_show(); + stack.o.undent(); + stack.o << "}>CONT"; + stack.o << "c1 PUSH"; + stack.o << "COMPOSALT"; + stack.o << "SWAP"; + stack.o << "TRY"; + return true; + } default: std::cerr << "fatal: unknown operation \n"; throw src::ParseError{where, "unknown operation in generate_code()"}; } } -bool Op::generate_code_all(Stack& stack) { - if (generate_code_step(stack) && next) { - return next->generate_code_all(stack); - } else { - return false; +void Op::generate_code_all(Stack& stack) { + int saved_mode = stack.mode; + auto cont = generate_code_step(stack); + stack.mode = (stack.mode & ~Stack::_ModeSave) | (saved_mode & Stack::_ModeSave); + if (cont && next) { + next->generate_code_all(stack); } } void CodeBlob::generate_code(AsmOpList& out, int mode) { Stack stack{out, mode}; - assert(ops && ops->cl == Op::_Import); + func_assert(ops && ops->cl == Op::_Import); + auto args = (int)ops->left.size(); for (var_idx_t x : ops->left) { stack.push_new_var(x); } ops->generate_code_all(stack); + stack.apply_wrappers(require_callxargs && (mode & Stack::_InlineAny) ? args : -1); if (!(mode & Stack::_DisableOpt)) { optimize_code(out); } diff --git a/crypto/func/func-main.cpp b/crypto/func/func-main.cpp new file mode 100644 index 00000000..7ba807d9 --- /dev/null +++ b/crypto/func/func-main.cpp @@ -0,0 +1,133 @@ +/* + This file is part of TON Blockchain source code. + + TON Blockchain is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + TON Blockchain 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TON Blockchain. If not, see . + + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement + from all source files in the program, then also delete it here. + + Copyright 2017-2020 Telegram Systems LLP +*/ +#include "func.h" +#include "parser/srcread.h" +#include "parser/lexer.h" +#include "parser/symtable.h" +#include +#include +#include "git.h" + +void usage(const char* progname) { + std::cerr + << "usage: " << progname + << " [-vIAPSR][-O][-i][-o][-W] { ...}\n" + "\tGenerates Fift TVM assembler code from a funC source\n" + "-I\tEnables interactive mode (parse stdin)\n" + "-o\tWrites generated code into specified file instead of stdout\n" + "-v\tIncreases verbosity level (extra information output into stderr)\n" + "-i\tSets indentation for the output code (in two-space units)\n" + "-A\tPrefix code with `\"Asm.fif\" include` preamble\n" + "-O\tSets optimization level (2 by default)\n" + "-P\tEnvelope code into PROGRAM{ ... }END>c\n" + "-S\tInclude stack layout comments in the output code\n" + "-R\tInclude operation rewrite comments in the output code\n" + "-W\tInclude Fift code to serialize and save generated code into specified BoC file. Enables " + "-A and -P.\n" + "-u\tEnable warnings about unused calls and variables (once for assigns, twice also for calls)\n" + "\t-s\tOutput semantic version of FunC and exit\n" + "\t-V\tShow func build information\n"; + std::exit(2); +} + +int main(int argc, char* const argv[]) { + int i; + std::string output_filename; + while ((i = getopt(argc, argv, "Ahi:Io:O:PRsSuvW:V")) != -1) { + switch (i) { + case 'A': + funC::asm_preamble = true; + break; + case 'I': + funC::interactive = true; + break; + case 'i': + funC::indent = std::max(0, atoi(optarg)); + break; + case 'o': + output_filename = optarg; + break; + case 'O': + funC::opt_level = std::max(0, atoi(optarg)); + break; + case 'P': + funC::program_envelope = true; + break; + case 'R': + funC::op_rewrite_comments = true; + break; + case 'S': + funC::stack_layout_comments = true; + break; + case 'u': + ++funC::warn_unused; + break; + case 'v': + ++funC::verbosity; + break; + case 'W': + funC::boc_output_filename = optarg; + funC::asm_preamble = funC::program_envelope = true; + break; + case 's': + std::cout << funC::func_version << "\n"; + std::exit(0); + break; + case 'V': + std::cout << "FunC semantic version: v" << funC::func_version << "\n"; + std::cout << "Build information: [ Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate() << "]\n"; + std::exit(0); + break; + case 'h': + default: + usage(argv[0]); + } + } + + std::ostream *outs = &std::cout; + + std::unique_ptr fs; + if (!output_filename.empty()) { + fs = std::make_unique(output_filename, std::fstream::trunc | std::fstream::out); + if (!fs->is_open()) { + std::cerr << "failed to create output file " << output_filename << '\n'; + return 2; + } + outs = fs.get(); + } + + std::vector sources; + + while (optind < argc) { + sources.push_back(std::string(argv[optind++])); + } + + funC::read_callback = funC::fs_read_callback; + + return funC::func_proceed(sources, *outs, std::cerr); +} diff --git a/crypto/func/func.cpp b/crypto/func/func.cpp index 6771e140..749cbefa 100644 --- a/crypto/func/func.cpp +++ b/crypto/func/func.cpp @@ -1,4 +1,4 @@ -/* +/* This file is part of TON Blockchain source code. TON Blockchain is free software; you can redistribute it and/or @@ -14,215 +14,205 @@ You should have received a copy of the GNU General Public License along with TON Blockchain. If not, see . - In addition, as a special exception, the copyright holders give permission - to link the code of portions of this program with the OpenSSL library. - You must obey the GNU General Public License in all respects for all - of the code used other than OpenSSL. If you modify file(s) with this - exception, you may extend this exception to your version of the file(s), - but you are not obligated to do so. If you do not wish to do so, delete this - exception statement from your version. If you delete this exception statement + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "func.h" #include "parser/srcread.h" #include "parser/lexer.h" -#include "parser/symtable.h" #include +#include "git.h" #include +#include "td/utils/port/path.h" namespace funC { int verbosity, indent, opt_level = 2, warn_unused; bool stack_layout_comments, op_rewrite_comments, program_envelope, asm_preamble; -std::ostream* outs = &std::cout; +bool interactive = false; +GlobalPragma pragma_allow_post_modification{"allow-post-modification"}; +GlobalPragma pragma_compute_asm_ltr{"compute-asm-ltr"}; std::string generated_from, boc_output_filename; +ReadCallback::Callback read_callback; -/* - * - * OUTPUT CODE GENERATOR - * - */ - -void generate_output_func(SymDef* func_sym) { - SymValCodeFunc* func_val = dynamic_cast(func_sym->value); - assert(func_val); - std::string name = sym::symbols.get_name(func_sym->sym_idx); - if (verbosity >= 2) { - std::cerr << "\n\n=========================\nfunction " << name << " : " << func_val->get_type() << std::endl; - } - if (!func_val->code) { - std::cerr << "( function `" << name << "` undefined )\n"; - } else { - CodeBlob& code = *(func_val->code); - if (verbosity >= 3) { - code.print(std::cerr, 9); - } - code.simplify_var_types(); - if (verbosity >= 5) { - std::cerr << "after simplify_var_types: \n"; - code.print(std::cerr, 0); - } - code.prune_unreachable_code(); - if (verbosity >= 5) { - std::cerr << "after prune_unreachable: \n"; - code.print(std::cerr, 0); - } - code.split_vars(true); - if (verbosity >= 5) { - std::cerr << "after split_vars: \n"; - code.print(std::cerr, 0); - } - for (int i = 0; i < 8; i++) { - code.compute_used_code_vars(); - if (verbosity >= 4) { - std::cerr << "after compute_used_vars: \n"; - code.print(std::cerr, 6); - } - code.fwd_analyze(); - if (verbosity >= 5) { - std::cerr << "after fwd_analyze: \n"; - code.print(std::cerr, 6); - } - code.prune_unreachable_code(); - if (verbosity >= 5) { - std::cerr << "after prune_unreachable: \n"; - code.print(std::cerr, 6); +td::Result fs_read_callback(ReadCallback::Kind kind, const char* query) { + switch (kind) { + case ReadCallback::Kind::ReadFile: { + std::ifstream ifs{query}; + if (ifs.fail()) { + auto msg = std::string{"cannot open source file `"} + query + "`"; + return td::Status::Error(msg); } + std::stringstream ss; + ss << ifs.rdbuf(); + return ss.str(); } - code.mark_noreturn(); - if (verbosity >= 3) { - code.print(std::cerr, 15); + case ReadCallback::Kind::Realpath: { + return td::realpath(td::CSlice(query)); } - if (verbosity >= 2) { - std::cerr << "\n---------- resulting code for " << name << " -------------\n"; - } - bool inline_ref = (func_val->flags & 2); - *outs << std::string(indent * 2, ' ') << name << " PROC" << (inline_ref ? "REF" : "") << ":<{\n"; - code.generate_code( - *outs, - (stack_layout_comments ? Stack::_StkCmt | Stack::_CptStkCmt : 0) | (opt_level < 2 ? Stack::_DisableOpt : 0), - indent + 1); - *outs << std::string(indent * 2, ' ') << "}>\n"; - if (verbosity >= 2) { - std::cerr << "--------------\n"; + default: { + return td::Status::Error("Unknown query kind"); } } } -int generate_output() { - if (asm_preamble) { - *outs << "\"Asm.fif\" include\n"; +/* + * + * OUTPUT CODE GENERATOR + * + */ + +void generate_output_func(SymDef* func_sym, std::ostream &outs, std::ostream &errs) { + SymValCodeFunc* func_val = dynamic_cast(func_sym->value); + func_assert(func_val); + std::string name = sym::symbols.get_name(func_sym->sym_idx); + if (verbosity >= 2) { + errs << "\n\n=========================\nfunction " << name << " : " << func_val->get_type() << std::endl; } - *outs << "// automatically generated from " << generated_from << std::endl; + if (!func_val->code) { + errs << "( function `" << name << "` undefined )\n"; + throw src::ParseError(func_sym->loc, name); + } else { + CodeBlob& code = *(func_val->code); + if (verbosity >= 3) { + code.print(errs, 9); + } + code.simplify_var_types(); + if (verbosity >= 5) { + errs << "after simplify_var_types: \n"; + code.print(errs, 0); + } + code.prune_unreachable_code(); + if (verbosity >= 5) { + errs << "after prune_unreachable: \n"; + code.print(errs, 0); + } + code.split_vars(true); + if (verbosity >= 5) { + errs << "after split_vars: \n"; + code.print(errs, 0); + } + for (int i = 0; i < 8; i++) { + code.compute_used_code_vars(); + if (verbosity >= 4) { + errs << "after compute_used_vars: \n"; + code.print(errs, 6); + } + code.fwd_analyze(); + if (verbosity >= 5) { + errs << "after fwd_analyze: \n"; + code.print(errs, 6); + } + code.prune_unreachable_code(); + if (verbosity >= 5) { + errs << "after prune_unreachable: \n"; + code.print(errs, 6); + } + } + code.mark_noreturn(); + if (verbosity >= 3) { + code.print(errs, 15); + } + if (verbosity >= 2) { + errs << "\n---------- resulting code for " << name << " -------------\n"; + } + bool inline_func = (func_val->flags & 1); + bool inline_ref = (func_val->flags & 2); + const char* modifier = ""; + if (inline_func) { + modifier = "INLINE"; + } else if (inline_ref) { + modifier = "REF"; + } + outs << std::string(indent * 2, ' ') << name << " PROC" << modifier << ":<{\n"; + int mode = 0; + if (stack_layout_comments) { + mode |= Stack::_StkCmt | Stack::_CptStkCmt; + } + if (opt_level < 2) { + mode |= Stack::_DisableOpt; + } + auto fv = dynamic_cast(func_sym->value); + // Flags: 1 - inline, 2 - inline_ref + if (fv && (fv->flags & 1) && code.ops->noreturn()) { + mode |= Stack::_InlineFunc; + } + if (fv && (fv->flags & 3)) { + mode |= Stack::_InlineAny; + } + code.generate_code(outs, mode, indent + 1); + outs << std::string(indent * 2, ' ') << "}>\n"; + if (verbosity >= 2) { + errs << "--------------\n"; + } + } +} + +int generate_output(std::ostream &outs, std::ostream &errs) { + if (asm_preamble) { + outs << "\"Asm.fif\" include\n"; + } + outs << "// automatically generated from " << generated_from << std::endl; if (program_envelope) { - *outs << "PROGRAM{\n"; + outs << "PROGRAM{\n"; } for (SymDef* func_sym : glob_func) { SymValCodeFunc* func_val = dynamic_cast(func_sym->value); - assert(func_val); + func_assert(func_val); std::string name = sym::symbols.get_name(func_sym->sym_idx); - *outs << std::string(indent * 2, ' '); + outs << std::string(indent * 2, ' '); if (func_val->method_id.is_null()) { - *outs << "DECLPROC " << name << "\n"; + outs << "DECLPROC " << name << "\n"; } else { - *outs << func_val->method_id << " DECLMETHOD " << name << "\n"; + outs << func_val->method_id << " DECLMETHOD " << name << "\n"; } } for (SymDef* gvar_sym : glob_vars) { - assert(dynamic_cast(gvar_sym->value)); + func_assert(dynamic_cast(gvar_sym->value)); std::string name = sym::symbols.get_name(gvar_sym->sym_idx); - *outs << std::string(indent * 2, ' ') << "DECLGLOBVAR " << name << "\n"; + outs << std::string(indent * 2, ' ') << "DECLGLOBVAR " << name << "\n"; } int errors = 0; for (SymDef* func_sym : glob_func) { try { - generate_output_func(func_sym); + generate_output_func(func_sym, outs, errs); } catch (src::Error& err) { - std::cerr << "cannot generate code for function `" << sym::symbols.get_name(func_sym->sym_idx) << "`:\n" + errs << "cannot generate code for function `" << sym::symbols.get_name(func_sym->sym_idx) << "`:\n" << err << std::endl; ++errors; } } if (program_envelope) { - *outs << "}END>c\n"; + outs << "}END>c\n"; } if (!boc_output_filename.empty()) { - *outs << "2 boc+>B \"" << boc_output_filename << "\" B>file\n"; + outs << "2 boc+>B \"" << boc_output_filename << "\" B>file\n"; } return errors; } -} // namespace funC - -void usage(const char* progname) { - std::cerr - << "usage: " << progname - << " [-vIAPSR][-O][-i][-o][-W] { ...}\n" - "\tGenerates Fift TVM assembler code from a funC source\n" - "-I\tEnables interactive mode (parse stdin)\n" - "-o\tWrites generated code into specified file instead of stdout\n" - "-v\tIncreases verbosity level (extra information output into stderr)\n" - "-i\tSets indentation for the output code (in two-space units)\n" - "-A\tPrefix code with `\"Asm.fif\" include` preamble\n" - "-O\tSets optimization level (2 by default)\n" - "-P\tEnvelope code into PROGRAM{ ... }END>c\n" - "-S\tInclude stack layout comments in the output code\n" - "-R\tInclude operation rewrite comments in the output code\n" - "-W\tInclude Fift code to serialize and save generated code into specified BoC file. Enables " - "-A and -P.\n" - "-u\tEnable warnings about unused calls and variables (once for assigns, twice also for calls)\n"; - std::exit(2); -} - -std::string output_filename; - -int main(int argc, char* const argv[]) { - int i; - bool interactive = false; - while ((i = getopt(argc, argv, "Ahi:Io:O:PRSuvW:")) != -1) { - switch (i) { - case 'A': - funC::asm_preamble = true; - break; - case 'I': - interactive = true; - break; - case 'i': - funC::indent = std::max(0, atoi(optarg)); - break; - case 'o': - output_filename = optarg; - break; - case 'O': - funC::opt_level = std::max(0, atoi(optarg)); - break; - case 'P': - funC::program_envelope = true; - break; - case 'R': - funC::op_rewrite_comments = true; - break; - case 'S': - funC::stack_layout_comments = true; - break; - case 'u': - ++funC::warn_unused; - break; - case 'v': - ++funC::verbosity; - break; - case 'W': - funC::boc_output_filename = optarg; - funC::asm_preamble = funC::program_envelope = true; - break; - case 'h': - default: - usage(argv[0]); +void output_inclusion_stack(std::ostream &errs) { + while (!funC::inclusion_locations.empty()) { + src::SrcLocation loc = funC::inclusion_locations.top(); + funC::inclusion_locations.pop(); + if (loc.fdescr) { + errs << "note: included from "; + loc.show(errs); + errs << std::endl; } } +} + +int func_proceed(const std::vector &sources, std::ostream &outs, std::ostream &errs) { if (funC::program_envelope && !funC::indent) { funC::indent = 1; } @@ -232,12 +222,11 @@ int main(int argc, char* const argv[]) { int ok = 0, proc = 0; try { - while (optind < argc) { - funC::generated_from += std::string{"`"} + argv[optind] + "` "; - ok += funC::parse_source_file(argv[optind++]); + for (auto src : sources) { + ok += funC::parse_source_file(src.c_str(), {}, true); proc++; } - if (interactive) { + if (funC::interactive) { funC::generated_from += "stdin "; ok += funC::parse_source_stdin(); proc++; @@ -248,26 +237,26 @@ int main(int argc, char* const argv[]) { if (!proc) { throw src::Fatal{"no source files, no output"}; } - std::unique_ptr fs; - if (!output_filename.empty()) { - fs = std::make_unique(output_filename, fs->trunc | fs->out); - if (!fs->is_open()) { - std::cerr << "failed to create output file " << output_filename << '\n'; - return 2; - } - funC::outs = fs.get(); - } - funC::generate_output(); + pragma_allow_post_modification.check_enable_in_libs(); + pragma_compute_asm_ltr.check_enable_in_libs(); + return funC::generate_output(outs, errs); } catch (src::Fatal& fatal) { - std::cerr << "fatal: " << fatal << std::endl; - std::exit(1); + errs << "fatal: " << fatal << std::endl; + output_inclusion_stack(errs); + return 2; } catch (src::Error& error) { - std::cerr << error << std::endl; - std::exit(1); + errs << error << std::endl; + output_inclusion_stack(errs); + return 2; } catch (funC::UnifyError& unif_err) { - std::cerr << "fatal: "; - unif_err.print_message(std::cerr); - std::cerr << std::endl; - std::exit(1); + errs << "fatal: "; + unif_err.print_message(errs); + errs << std::endl; + output_inclusion_stack(errs); + return 2; } + + return 0; } + +} // namespace funC \ No newline at end of file diff --git a/crypto/func/func.h b/crypto/func/func.h index f7e0601c..9b55466f 100644 --- a/crypto/func/func.h +++ b/crypto/func/func.h @@ -14,11 +14,12 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include #include +#include #include #include #include @@ -30,13 +31,21 @@ #include "parser/srcread.h" #include "parser/lexer.h" #include "parser/symtable.h" +#include "td/utils/Status.h" + +#define func_assert(expr) \ + (bool(expr) ? void(0) \ + : throw src::Fatal(PSTRING() << "Assertion failed at " << __FILE__ << ":" << __LINE__ << ": " << #expr)) namespace funC { extern int verbosity, warn_unused; extern bool op_rewrite_comments; +extern std::string generated_from; -constexpr int optimize_depth = 12; +constexpr int optimize_depth = 20; + +const std::string func_version{"0.4.4"}; enum Keyword { _Eof = -1, @@ -50,6 +59,8 @@ enum Keyword { _Do, _While, _Until, + _Try, + _Catch, _If, _Ifnot, _Then, @@ -101,11 +112,15 @@ enum Keyword { _Extern, _Inline, _InlineRef, + _AutoApply, _MethodId, _Operator, _Infix, _Infixl, - _Infixr + _Infixr, + _Const, + _PragmaHashtag, + _IncludeHashtag }; void define_keywords(); @@ -134,7 +149,7 @@ class IdSc { */ struct TypeExpr { - enum te_type { te_Unknown, te_Var, te_Indirect, te_Atomic, te_Tensor, te_Map, te_Type, te_ForAll } constr; + enum te_type { te_Unknown, te_Var, te_Indirect, te_Atomic, te_Tensor, te_Tuple, te_Map, te_Type, te_ForAll } constr; enum { _Int = Keyword::_Int, _Cell = Keyword::_Cell, @@ -148,6 +163,7 @@ struct TypeExpr { int minw, maxw; static constexpr int w_inf = 1023; std::vector args; + bool was_forall_var = false; TypeExpr(te_type _constr, int _val = 0) : constr(_constr), value(_val), minw(0), maxw(w_inf) { } TypeExpr(te_type _constr, int _val, int width) : constr(_constr), value(_val), minw(width), maxw(width) { @@ -160,6 +176,9 @@ struct TypeExpr { : constr(_constr), value((int)list.size()), args(std::move(list)) { compute_width(); } + TypeExpr(te_type _constr, TypeExpr* elem0) : constr(_constr), value(1), args{elem0} { + compute_width(); + } TypeExpr(te_type _constr, TypeExpr* elem0, std::vector list) : constr(_constr), value((int)list.size() + 1), args{elem0} { args.insert(args.end(), list.begin(), list.end()); @@ -185,6 +204,9 @@ struct TypeExpr { bool is_map() const { return constr == te_Map; } + bool is_tuple() const { + return constr == te_Tuple; + } bool has_fixed_width() const { return minw == maxw; } @@ -226,6 +248,15 @@ struct TypeExpr { static TypeExpr* new_tensor(TypeExpr* te1, TypeExpr* te2, TypeExpr* te3) { return new_tensor({te1, te2, te3}); } + static TypeExpr* new_tuple(TypeExpr* arg0) { + return new TypeExpr{te_Tuple, arg0}; + } + static TypeExpr* new_tuple(std::vector list, bool red = false) { + return new_tuple(new_tensor(std::move(list), red)); + } + static TypeExpr* new_tuple(std::initializer_list list) { + return new_tuple(new_tensor(std::move(list))); + } static TypeExpr* new_var() { return new TypeExpr{te_Var, --type_vars, 1}; } @@ -239,7 +270,7 @@ struct TypeExpr { return new TypeExpr{te_ForAll, body, std::move(list)}; } static bool remove_indirect(TypeExpr*& te, TypeExpr* forbidden = nullptr); - static bool remove_forall(TypeExpr*& te); + static std::vector remove_forall(TypeExpr*& te); static bool remove_forall_in(TypeExpr*& te, TypeExpr* te2, const std::vector& new_vars); }; @@ -282,10 +313,17 @@ struct TmpVar { sym_idx_t name; int coord; std::unique_ptr where; + std::vector> on_modification; + bool undefined = false; TmpVar(var_idx_t _idx, int _cls, TypeExpr* _type = 0, SymDef* sym = 0, const SrcLocation* loc = 0); void show(std::ostream& os, int omit_idx = 0) const; void dump(std::ostream& os) const; void set_location(const SrcLocation& loc); + std::string to_string() const { + std::ostringstream s; + show(s, 2); + return s.str(); + } }; struct VarDescr { @@ -317,6 +355,8 @@ struct VarDescr { static constexpr int FiniteUInt = FiniteInt | _Pos; int val; td::RefInt256 int_const; + std::string str_const; + VarDescr(var_idx_t _idx = -1, int _flags = 0, int _val = 0) : idx(_idx), flags(_flags), val(_val) { } bool operator<(var_idx_t other_idx) const { @@ -359,7 +399,7 @@ struct VarDescr { return val & _Const; } bool is_int_const() const { - return (val & (_Int | _Const)) == (_Int | _Const); + return (val & (_Int | _Const)) == (_Int | _Const) && int_const.not_null(); } bool always_nonpos() const { return val & _Neg; @@ -398,6 +438,7 @@ struct VarDescr { } void set_const(long long value); void set_const(td::RefInt256 value); + void set_const(std::string value); void set_const_nan(); void operator+=(const VarDescr& y) { flags &= y.flags; @@ -484,7 +525,7 @@ class ListIterator { ptr = ptr->next.get(); return *this; } - ListIterator& operator++(int) { + ListIterator operator++(int) { T* z = ptr; ptr = ptr->next.get(); return ListIterator{z}; @@ -517,11 +558,15 @@ struct Op { _SetGlob, _Import, _Return, + _Tuple, + _UnTuple, _If, _While, _Until, _Repeat, - _Again + _Again, + _TryCatch, + _SliceConst }; int cl; enum { _Disabled = 1, _Reachable = 2, _NoReturn = 4, _ImpureR = 8, _ImpureW = 16, _Impure = 24, _Replaced = 32 }; @@ -534,6 +579,7 @@ struct Op { std::vector left, right; std::unique_ptr block0, block1; td::RefInt256 int_const; + std::string str_const; Op(const SrcLocation& _where = {}, int _cl = _Undef) : cl(_cl), flags(0), fun_ref(nullptr), where(_where) { } Op(const SrcLocation& _where, int _cl, const std::vector& _left) @@ -545,6 +591,9 @@ struct Op { Op(const SrcLocation& _where, int _cl, const std::vector& _left, td::RefInt256 _const) : cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(_left), int_const(_const) { } + Op(const SrcLocation& _where, int _cl, const std::vector& _left, std::string _const) + : cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(_left), str_const(_const) { + } Op(const SrcLocation& _where, int _cl, const std::vector& _left, const std::vector& _right, SymDef* _fun = nullptr) : cl(_cl), flags(0), fun_ref(_fun), where(_where), left(_left), right(_right) { @@ -600,7 +649,7 @@ struct Op { return !(flags & _Impure); } bool generate_code_step(Stack& stack); - bool generate_code_all(Stack& stack); + void generate_code_all(Stack& stack); Op& last() { return next ? next->last() : *this; } @@ -659,6 +708,7 @@ typedef std::vector FormalArgList; struct AsmOpList; struct CodeBlob { + enum { _AllowPostModification = 1, _ComputeAsmLtr = 2 }; int var_cnt, in_var_cnt, op_cnt; TypeExpr* ret_type; std::string name; @@ -667,6 +717,8 @@ struct CodeBlob { std::unique_ptr ops; std::unique_ptr* cur_ops; std::stack*> cur_ops_stack; + int flags = 0; + bool require_callxargs = false; CodeBlob(TypeExpr* ret = nullptr) : var_cnt(0), in_var_cnt(0), op_cnt(0), ret_type(ret), cur_ops(&ops) { } template @@ -706,6 +758,12 @@ struct CodeBlob { void mark_noreturn(); void generate_code(AsmOpList& out_list, int mode = 0); void generate_code(std::ostream& os, int mode = 0, int indent = 0); + + void on_var_modification(var_idx_t idx, const SrcLocation& here) const { + for (auto& f : vars.at(idx).on_modification) { + f(here); + } + } }; /* @@ -782,8 +840,33 @@ struct SymValGlobVar : sym::SymValBase { } }; +struct SymValConst : sym::SymValBase { + td::RefInt256 intval; + std::string strval; + Keyword type; + SymValConst(int idx, td::RefInt256 value) + : sym::SymValBase(_Const, idx), intval(value) { + type = _Int; + } + SymValConst(int idx, std::string value) + : sym::SymValBase(_Const, idx), strval(value) { + type = _Slice; + } + ~SymValConst() override = default; + td::RefInt256 get_int_value() const { + return intval; + } + std::string get_str_value() const { + return strval; + } + Keyword get_type() const { + return type; + } +}; + extern int glob_func_cnt, undef_func_cnt, glob_var_cnt; extern std::vector glob_func, glob_vars; +extern std::set prohibited_var_names; /* * @@ -791,11 +874,42 @@ extern std::vector glob_func, glob_vars; * */ +class ReadCallback { +public: + /// Noncopyable. + ReadCallback(ReadCallback const&) = delete; + ReadCallback& operator=(ReadCallback const&) = delete; + + enum class Kind + { + ReadFile, + Realpath + }; + + static std::string kindString(Kind _kind) + { + switch (_kind) + { + case Kind::ReadFile: + return "source"; + case Kind::Realpath: + return "realpath"; + default: + throw ""; // todo ? + } + } + + /// File reading or generic query callback. + using Callback = std::function(ReadCallback::Kind, const char*)>; +}; + // defined in parse-func.cpp bool parse_source(std::istream* is, const src::FileDescr* fdescr); -bool parse_source_file(const char* filename); +bool parse_source_file(const char* filename, src::Lexem lex = {}, bool is_main = false); bool parse_source_stdin(); +extern std::stack inclusion_locations; + /* * * EXPRESSIONS @@ -808,7 +922,8 @@ struct Expr { _Apply, _VarApply, _TypeApply, - _Tuple, + _MkTuple, + _Tensor, _Const, _Var, _Glob, @@ -817,7 +932,8 @@ struct Expr { _LetFirst, _Hole, _Type, - _CondExpr + _CondExpr, + _SliceConst }; int cls; int val{0}; @@ -825,6 +941,7 @@ struct Expr { int flags{0}; SrcLocation here; td::RefInt256 intval; + std::string strval; SymDef* sym{nullptr}; TypeExpr* e_type{nullptr}; std::vector args; @@ -862,6 +979,12 @@ struct Expr { bool is_type() const { return flags & _IsType; } + bool is_type_apply() const { + return cls == _TypeApply; + } + bool is_mktuple() const { + return cls == _MkTuple; + } void chk_rvalue(const Lexem& lem) const; void chk_lvalue(const Lexem& lem) const; void chk_type(const Lexem& lem) const; @@ -874,7 +997,8 @@ struct Expr { } int define_new_vars(CodeBlob& code); int predefine_vars(); - std::vector pre_compile(CodeBlob& code) const; + std::vector pre_compile(CodeBlob& code, std::vector>* lval_globs = nullptr) const; + static std::vector pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rhs, const SrcLocation& here); var_idx_t new_tmp(CodeBlob& code) const; std::vector new_tmp_vect(CodeBlob& code) const { return {new_tmp(code)}; @@ -898,7 +1022,9 @@ struct AsmOp { int t{a_none}; int indent{0}; int a, b, c; + bool gconst{false}; std::string op; + td::RefInt256 origin; struct SReg { int idx; SReg(int _idx) : idx(_idx) { @@ -916,6 +1042,10 @@ struct AsmOp { AsmOp(int _t, int _a, int _b) : t(_t), a(_a), b(_b) { } AsmOp(int _t, int _a, int _b, std::string _op) : t(_t), a(_a), b(_b), op(std::move(_op)) { + compute_gconst(); + } + AsmOp(int _t, int _a, int _b, std::string _op, td::RefInt256 x) : t(_t), a(_a), b(_b), op(std::move(_op)), origin(x) { + compute_gconst(); } AsmOp(int _t, int _a, int _b, int _c) : t(_t), a(_a), b(_b), c(_c) { } @@ -924,6 +1054,9 @@ struct AsmOp { void out(std::ostream& os) const; void out_indent_nl(std::ostream& os, bool no_nl = false) const; std::string to_string() const; + void compute_gconst() { + gconst = (is_custom() && (op == "PUSHNULL" || op == "NEWC" || op == "NEWB" || op == "TRUE" || op == "FALSE" || op == "NOW")); + } bool is_nop() const { return t == a_none && op.empty(); } @@ -963,6 +1096,9 @@ struct AsmOp { *y = b; return is_xchg(); } + bool is_xchg_short() const { + return is_xchg() && (a <= 1 || b <= 1); + } bool is_swap() const { return is_xchg(0, 1); } @@ -970,7 +1106,7 @@ struct AsmOp { return t == a_const && !a && b == 1; } bool is_gconst() const { - return (t == a_const || t == a_custom) && !a && b == 1; + return !a && b == 1 && (t == a_const || gconst); } static AsmOp Nop() { return AsmOp(a_none); @@ -1023,15 +1159,16 @@ struct AsmOp { static AsmOp BlkSwap(int a, int b); static AsmOp BlkPush(int a, int b); static AsmOp BlkDrop(int a); + static AsmOp BlkDrop2(int a, int b); static AsmOp BlkReverse(int a, int b); static AsmOp make_stk2(int a, int b, const char* str, int delta); static AsmOp make_stk3(int a, int b, int c, const char* str, int delta); static AsmOp IntConst(td::RefInt256 value); static AsmOp BoolConst(bool f); - static AsmOp Const(std::string push_op) { - return AsmOp(a_const, 0, 1, std::move(push_op)); + static AsmOp Const(std::string push_op, td::RefInt256 origin = {}) { + return AsmOp(a_const, 0, 1, std::move(push_op), origin); } - static AsmOp Const(int arg, std::string push_op); + static AsmOp Const(int arg, std::string push_op, td::RefInt256 origin = {}); static AsmOp Comment(std::string comment) { return AsmOp(a_none, std::string{"// "} + comment); } @@ -1043,6 +1180,8 @@ struct AsmOp { return AsmOp(a_custom, args, retv, custom_op); } static AsmOp Parse(std::string custom_op, int args, int retv = 1); + static AsmOp Tuple(int a); + static AsmOp UnTuple(int a); }; inline std::ostream& operator<<(std::ostream& os, const AsmOp& op) { @@ -1057,12 +1196,13 @@ struct AsmOpList { int indent_{0}; const std::vector* var_names_{nullptr}; std::vector constants_; + bool retalt_{false}; void out(std::ostream& os, int mode = 0) const; AsmOpList(int indent = 0, const std::vector* var_names = nullptr) : indent_(indent), var_names_(var_names) { } template AsmOpList& add(Args&&... args) { - list_.emplace_back(std::forward(args)...); + append(AsmOp(std::forward(args)...)); adjust_last(); return *this; } @@ -1104,6 +1244,19 @@ struct AsmOpList { void set_indent(int new_indent) { indent_ = new_indent; } + void insert(size_t pos, std::string str) { + insert(pos, AsmOp(AsmOp::a_custom, 255, 255, str)); + } + void insert(size_t pos, const AsmOp& op) { + auto ip = list_.begin() + pos; + ip = list_.insert(ip, op); + ip->indent = (ip == list_.begin()) ? indent_ : (ip - 1)->indent; + } + void indent_all() { + for (auto &op : list_) { + ++op.indent; + } + } }; inline std::ostream& operator<<(std::ostream& os, const AsmOpList& op_list) { @@ -1250,12 +1403,22 @@ struct StackTransform { } bool is_xchg(int i, int j) const; bool is_xchg(int* i, int* j) const; + bool is_xchg_xchg(int i, int j, int k, int l) const; + bool is_xchg_xchg(int* i, int* j, int* k, int* l) const; bool is_push(int i) const; bool is_push(int* i) const; bool is_pop(int i) const; bool is_pop(int* i) const; + bool is_pop_pop(int i, int j) const; + bool is_pop_pop(int* i, int* j) const; bool is_rot() const; bool is_rotrev() const; + bool is_push_rot(int i) const; + bool is_push_rot(int* i) const; + bool is_push_rotrev(int i) const; + bool is_push_rotrev(int* i) const; + bool is_push_xchg(int i, int j, int k) const; + bool is_push_xchg(int* i, int* j, int* k) const; bool is_xchg2(int i, int j) const; bool is_xchg2(int* i, int* j) const; bool is_xcpu(int i, int j) const; @@ -1280,6 +1443,8 @@ struct StackTransform { bool is_blkpush(int i, int j) const; bool is_blkpush(int* i, int* j) const; bool is_blkdrop(int* i) const; + bool is_blkdrop2(int i, int j) const; + bool is_blkdrop2(int* i, int* j) const; bool is_reverse(int i, int j) const; bool is_reverse(int* i, int* j) const; bool is_nip_seq(int i, int j = 0) const; @@ -1289,7 +1454,12 @@ struct StackTransform { bool is_pop_blkdrop(int* i, int* k) const; bool is_2pop_blkdrop(int i, int j, int k) const; bool is_2pop_blkdrop(int* i, int* j, int* k) const; - bool is_const_rot() const; + bool is_const_rot(int c) const; + bool is_const_rot(int* c) const; + bool is_const_pop(int c, int i) const; + bool is_const_pop(int* c, int* i) const; + bool is_push_const(int i, int c) const; + bool is_push_const(int* i, int* c) const; void show(std::ostream& os, int mode = 0) const; @@ -1326,11 +1496,12 @@ struct Optimizer { AsmOpCons* op_cons_[n]; int offs_[n]; StackTransform tr_[n]; + int mode_{0}; Optimizer() { } - Optimizer(bool debug) : debug_(debug) { + Optimizer(bool debug, int mode = 0) : debug_(debug), mode_(mode) { } - Optimizer(AsmOpConsList code, bool debug = false) : Optimizer(debug) { + Optimizer(AsmOpConsList code, bool debug = false, int mode = 0) : Optimizer(debug, mode) { set_code(std::move(code)); } void set_code(AsmOpConsList code_); @@ -1346,25 +1517,28 @@ struct Optimizer { void show_head() const; void show_left() const; void show_right() const; - bool is_const_push_swap() const; - bool rewrite_const_push_swap(); + bool find_const_op(int* op_idx, int cst); + bool is_push_const(int* i, int* c) const; + bool rewrite_push_const(int i, int c); bool is_const_push_xchgs(); bool rewrite_const_push_xchgs(); - bool is_const_rot() const; - bool rewrite_const_rot(); - bool simple_rewrite(int p, AsmOp&& new_op); - bool simple_rewrite(int p, AsmOp&& new_op1, AsmOp&& new_op2); - bool simple_rewrite(int p, AsmOp&& new_op1, AsmOp&& new_op2, AsmOp&& new_op3); - bool simple_rewrite(AsmOp&& new_op) { - return simple_rewrite(p_, std::move(new_op)); + bool is_const_rot(int* c) const; + bool rewrite_const_rot(int c); + bool is_const_pop(int* c, int* i) const; + bool rewrite_const_pop(int c, int i); + bool rewrite(int p, AsmOp&& new_op); + bool rewrite(int p, AsmOp&& new_op1, AsmOp&& new_op2); + bool rewrite(int p, AsmOp&& new_op1, AsmOp&& new_op2, AsmOp&& new_op3); + bool rewrite(AsmOp&& new_op) { + return rewrite(p_, std::move(new_op)); } - bool simple_rewrite(AsmOp&& new_op1, AsmOp&& new_op2) { - return simple_rewrite(p_, std::move(new_op1), std::move(new_op2)); + bool rewrite(AsmOp&& new_op1, AsmOp&& new_op2) { + return rewrite(p_, std::move(new_op1), std::move(new_op2)); } - bool simple_rewrite(AsmOp&& new_op1, AsmOp&& new_op2, AsmOp&& new_op3) { - return simple_rewrite(p_, std::move(new_op1), std::move(new_op2), std::move(new_op3)); + bool rewrite(AsmOp&& new_op1, AsmOp&& new_op2, AsmOp&& new_op3) { + return rewrite(p_, std::move(new_op1), std::move(new_op2), std::move(new_op3)); } - bool simple_rewrite_nop(); + bool rewrite_nop(); bool is_pred(const std::function& pred, int min_p = 2); bool is_same_as(const StackTransform& trans, int min_p = 2); bool is_rot(); @@ -1375,9 +1549,14 @@ struct Optimizer { bool is_2swap(); bool is_2over(); bool is_xchg(int* i, int* j); + bool is_xchg_xchg(int* i, int* j, int* k, int* l); bool is_push(int* i); bool is_pop(int* i); + bool is_pop_pop(int* i, int* j); bool is_nop(); + bool is_push_rot(int* i); + bool is_push_rotrev(int* i); + bool is_push_xchg(int* i, int* j, int* k); bool is_xchg2(int* i, int* j); bool is_xcpu(int* i, int* j); bool is_puxc(int* i, int* j); @@ -1393,6 +1572,7 @@ struct Optimizer { bool is_blkswap(int* i, int* j); bool is_blkpush(int* i, int* j); bool is_blkdrop(int* i); + bool is_blkdrop2(int* i, int* j); bool is_reverse(int* i, int* j); bool is_nip_seq(int* i, int* j); bool is_pop_blkdrop(int* i, int* k); @@ -1400,14 +1580,19 @@ struct Optimizer { AsmOpConsList extract_code(); }; -AsmOpConsList optimize_code_head(AsmOpConsList op_list); -AsmOpConsList optimize_code(AsmOpConsList op_list); +AsmOpConsList optimize_code_head(AsmOpConsList op_list, int mode = 0); +AsmOpConsList optimize_code(AsmOpConsList op_list, int mode); void optimize_code(AsmOpList& ops); struct Stack { StackLayoutExt s; AsmOpList& o; - enum { _StkCmt = 1, _CptStkCmt = 2, _DisableOpt = 4, _DisableOut = 128, _Shown = 256, _Garbage = -0x10000 }; + enum { + _StkCmt = 1, _CptStkCmt = 2, _DisableOpt = 4, _DisableOut = 128, _Shown = 256, + _InlineFunc = 512, _NeedRetAlt = 1024, _InlineAny = 2048, + _ModeSave = _InlineFunc | _NeedRetAlt | _InlineAny, + _Garbage = -0x10000 + }; int mode; Stack(AsmOpList& _o, int _mode = 0) : o(_o), mode(_mode) { } @@ -1449,7 +1634,10 @@ struct Stack { int find_outside(var_idx_t var, int from, int to) const; void forget_const(); void validate(int i) const { - assert(i >= 0 && i < depth() && "invalid stack reference"); + if (i > 255) { + throw src::Fatal{"Too deep stack"}; + } + func_assert(i >= 0 && i < depth() && "invalid stack reference"); } void modified() { mode &= ~_Shown; @@ -1480,6 +1668,28 @@ struct Stack { bool operator==(const Stack& y) const & { return s == y.s; } + void apply_wrappers(int callxargs_count) { + bool is_inline = mode & _InlineFunc; + if (o.retalt_) { + o.insert(0, "SAMEALTSAVE"); + o.insert(0, "c2 SAVE"); + } + if (callxargs_count != -1 || (is_inline && o.retalt_)) { + o.indent_all(); + o.insert(0, "CONT:<{"); + o << "}>"; + if (callxargs_count != -1) { + if (callxargs_count <= 15) { + o << AsmOp::Custom(PSTRING() << callxargs_count << " -1 CALLXARGS"); + } else { + func_assert(callxargs_count <= 254); + o << AsmOp::Custom(PSTRING() << callxargs_count << " PUSHINT -1 PUSHINT CALLXVARARGS"); + } + } else { + o << "EXECUTE"; + } + } + } }; /* @@ -1489,11 +1699,11 @@ struct Stack { * */ -typedef std::function&, std::vector&)> simple_compile_func_t; +typedef std::function&, std::vector&, const SrcLocation)> simple_compile_func_t; typedef std::function&, std::vector&)> compile_func_t; inline simple_compile_func_t make_simple_compile(AsmOp op) { - return [op](std::vector& out, std::vector& in) -> AsmOp { return op; }; + return [op](std::vector& out, std::vector& in, const SrcLocation&) -> AsmOp { return op; }; } inline compile_func_t make_ext_compile(std::vector ops) { @@ -1510,6 +1720,7 @@ inline compile_func_t make_ext_compile(AsmOp op) { struct SymValAsmFunc : SymValFunc { simple_compile_func_t simple_compile; compile_func_t ext_compile; + td::uint64 crc; ~SymValAsmFunc() override = default; SymValAsmFunc(TypeExpr* ft, const AsmOp& _macro, bool impure = false) : SymValFunc(-1, ft, impure), simple_compile(make_simple_compile(_macro)) { @@ -1531,7 +1742,7 @@ struct SymValAsmFunc : SymValFunc { std::initializer_list ret_order = {}, bool impure = false) : SymValFunc(-1, ft, arg_order, ret_order, impure), ext_compile(std::move(_compile)) { } - bool compile(AsmOpList& dest, std::vector& out, std::vector& in) const; + bool compile(AsmOpList& dest, std::vector& out, std::vector& in, const SrcLocation& where) const; }; // defined in builtins.cpp @@ -1544,4 +1755,57 @@ AsmOp push_const(td::RefInt256 x); void define_builtins(); + +extern int verbosity, indent, opt_level; +extern bool stack_layout_comments, op_rewrite_comments, program_envelope, asm_preamble, interactive; +extern std::string generated_from, boc_output_filename; +extern ReadCallback::Callback read_callback; + +td::Result fs_read_callback(ReadCallback::Kind kind, const char* query); + +class GlobalPragma { + public: + explicit GlobalPragma(std::string name) : name_(std::move(name)) { + } + const std::string& name() const { + return name_; + } + bool enabled() const { + return enabled_; + } + void enable(SrcLocation loc) { + enabled_ = true; + locs_.push_back(std::move(loc)); + } + void check_enable_in_libs() { + if (locs_.empty()) { + return; + } + for (const SrcLocation& loc : locs_) { + if (loc.fdescr->is_main) { + return; + } + } + locs_[0].show_warning(PSTRING() << "#pragma " << name_ + << " is enabled in included libraries, it may change the behavior of your code. " + << "Add this #pragma to the main source file to suppress this warning."); + } + + private: + std::string name_; + bool enabled_ = false; + std::vector locs_; +}; +extern GlobalPragma pragma_allow_post_modification, pragma_compute_asm_ltr; + +/* + * + * OUTPUT CODE GENERATOR + * + */ + +int func_proceed(const std::vector &sources, std::ostream &outs, std::ostream &errs); + } // namespace funC + + diff --git a/crypto/func/gen-abscode.cpp b/crypto/func/gen-abscode.cpp index 87a4e2de..9989d10c 100644 --- a/crypto/func/gen-abscode.cpp +++ b/crypto/func/gen-abscode.cpp @@ -14,10 +14,13 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ +#include #include "func.h" +using namespace std::literals::string_literals; + namespace funC { /* @@ -89,7 +92,7 @@ bool Expr::deduce_type(const Lexem& lem) { return true; } case _VarApply: { - assert(args.size() == 2); + func_assert(args.size() == 2); TypeExpr* fun_type = TypeExpr::new_map(args[1]->e_type, TypeExpr::new_hole()); try { unify(fun_type, args[0]->e_type); @@ -104,7 +107,7 @@ bool Expr::deduce_type(const Lexem& lem) { return true; } case _Letop: { - assert(args.size() == 2); + func_assert(args.size() == 2); try { // std::cerr << "in assignment: " << args[0]->e_type << " from " << args[1]->e_type << std::endl; unify(args[0]->e_type, args[1]->e_type); @@ -119,7 +122,7 @@ bool Expr::deduce_type(const Lexem& lem) { return true; } case _LetFirst: { - assert(args.size() == 2); + func_assert(args.size() == 2); TypeExpr* rhs_type = TypeExpr::new_tensor({args[0]->e_type, TypeExpr::new_hole()}); try { // std::cerr << "in implicit assignment of a modifying method: " << rhs_type << " and " << args[1]->e_type << std::endl; @@ -137,7 +140,7 @@ bool Expr::deduce_type(const Lexem& lem) { return true; } case _CondExpr: { - assert(args.size() == 3); + func_assert(args.size() == 3); auto flag_type = TypeExpr::new_atomic(_Int); try { unify(args[0]->e_type, flag_type); @@ -164,7 +167,8 @@ bool Expr::deduce_type(const Lexem& lem) { int Expr::define_new_vars(CodeBlob& code) { switch (cls) { - case _Tuple: + case _Tensor: + case _MkTuple: case _TypeApply: { int res = 0; for (const auto& x : args) { @@ -189,7 +193,8 @@ int Expr::define_new_vars(CodeBlob& code) { int Expr::predefine_vars() { switch (cls) { - case _Tuple: + case _Tensor: + case _MkTuple: case _TypeApply: { int res = 0; for (const auto& x : args) { @@ -199,8 +204,13 @@ int Expr::predefine_vars() { } case _Var: if (!sym) { - assert(val < 0 && here.defined()); + func_assert(val < 0 && here.defined()); + if (prohibited_var_names.count(sym::symbols.get_name(~val))) { + throw src::ParseError{ + here, PSTRING() << "symbol `" << sym::symbols.get_name(~val) << "` cannot be redefined as a variable"}; + } sym = sym::define_symbol(~val, false, here); + // std::cerr << "predefining variable " << sym::symbols.get_name(~val) << std::endl; if (!sym) { throw src::ParseError{here, std::string{"redefined variable `"} + sym::symbols.get_name(~val) + "`"}; } @@ -216,34 +226,127 @@ var_idx_t Expr::new_tmp(CodeBlob& code) const { return code.create_tmp_var(e_type, &here); } -std::vector Expr::pre_compile(CodeBlob& code) const { - switch (cls) { - case _Tuple: { - std::vector res; - for (const auto& x : args) { - auto add = x->pre_compile(code); - res.insert(res.end(), add.cbegin(), add.cend()); - } - return res; +void add_set_globs(CodeBlob& code, std::vector>& globs, const SrcLocation& here) { + for (const auto& p : globs) { + auto& op = code.emplace_back(here, Op::_SetGlob, std::vector{}, std::vector{ p.second }, p.first); + op.flags |= Op::_Impure; + } +} + +std::vector Expr::pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rhs, const SrcLocation& here) { + while (lhs->is_type_apply()) { + lhs = lhs->args.at(0); + } + while (rhs->is_type_apply()) { + rhs = rhs->args.at(0); + } + if (lhs->is_mktuple()) { + if (rhs->is_mktuple()) { + return pre_compile_let(code, lhs->args.at(0), rhs->args.at(0), here); } - case _Apply: { - assert(sym); - std::vector res; - auto func = dynamic_cast(sym->value); - if (func && func->arg_order.size() == args.size()) { - //std::cerr << "!!! reordering " << args.size() << " arguments of " << sym->name() << std::endl; - std::vector> add_list(args.size()); - for (int i : func->arg_order) { - add_list[i] = args[i]->pre_compile(code); - } - for (const auto& add : add_list) { - res.insert(res.end(), add.cbegin(), add.cend()); + auto right = rhs->pre_compile(code); + TypeExpr::remove_indirect(rhs->e_type); + auto unpacked_type = rhs->e_type->args.at(0); + std::vector tmp{code.create_tmp_var(unpacked_type, &rhs->here)}; + code.emplace_back(lhs->here, Op::_UnTuple, tmp, std::move(right)); + auto tvar = new Expr{_Var}; + tvar->set_val(tmp[0]); + tvar->set_location(rhs->here); + tvar->e_type = unpacked_type; + pre_compile_let(code, lhs->args.at(0), tvar, here); + return tmp; + } + auto right = rhs->pre_compile(code); + std::vector> globs; + auto left = lhs->pre_compile(code, &globs); + for (var_idx_t v : left) { + code.on_var_modification(v, here); + } + code.emplace_back(here, Op::_Let, std::move(left), right); + add_set_globs(code, globs, here); + return right; +} + +std::vector pre_compile_tensor(const std::vector args, CodeBlob &code, + std::vector> *lval_globs, + std::vector arg_order) { + if (arg_order.empty()) { + arg_order.resize(args.size()); + std::iota(arg_order.begin(), arg_order.end(), 0); + } + func_assert(args.size() == arg_order.size()); + std::vector> res_lists(args.size()); + + struct ModifiedVar { + size_t i, j; + Op* op; + }; + auto modified_vars = std::make_shared>(); + for (size_t i : arg_order) { + res_lists[i] = args[i]->pre_compile(code, lval_globs); + for (size_t j = 0; j < res_lists[i].size(); ++j) { + TmpVar& var = code.vars.at(res_lists[i][j]); + if (code.flags & CodeBlob::_AllowPostModification) { + if (!lval_globs && (var.cls & TmpVar::_Named)) { + Op *op = &code.emplace_back(nullptr, Op::_Let, std::vector(), std::vector()); + op->flags |= Op::_Disabled; + var.on_modification.push_back([modified_vars, i, j, op, done = false](const SrcLocation &here) mutable { + if (!done) { + done = true; + modified_vars->push_back({i, j, op}); + } + }); + } else { + var.on_modification.push_back([](const SrcLocation &) { + }); } } else { - for (const auto& x : args) { - auto add = x->pre_compile(code); - res.insert(res.end(), add.cbegin(), add.cend()); - } + var.on_modification.push_back([name = var.to_string()](const SrcLocation &here) { + throw src::ParseError{here, PSTRING() << "Modifying local variable " << name + << " after using it in the same expression"}; + }); + } + } + } + for (const auto& list : res_lists) { + for (var_idx_t v : list) { + func_assert(!code.vars.at(v).on_modification.empty()); + code.vars.at(v).on_modification.pop_back(); + } + } + for (const ModifiedVar &m : *modified_vars) { + var_idx_t& v = res_lists[m.i][m.j]; + var_idx_t v2 = code.create_tmp_var(code.vars[v].v_type, code.vars[v].where.get()); + m.op->left = {v2}; + m.op->right = {v}; + m.op->flags &= ~Op::_Disabled; + v = v2; + } + std::vector res; + for (const auto& list : res_lists) { + res.insert(res.end(), list.cbegin(), list.cend()); + } + return res; +} + +std::vector Expr::pre_compile(CodeBlob& code, std::vector>* lval_globs) const { + if (lval_globs && !(cls == _Tensor || cls == _Var || cls == _Hole || cls == _TypeApply || cls == _GlobVar)) { + std::cerr << "lvalue expression constructor is " << cls << std::endl; + throw src::Fatal{"cannot compile lvalue expression with unknown constructor"}; + } + switch (cls) { + case _Tensor: { + return pre_compile_tensor(args, code, lval_globs, {}); + } + case _Apply: { + func_assert(sym); + auto func = dynamic_cast(sym->value); + std::vector res; + if (func && func->arg_order.size() == args.size() && !(code.flags & CodeBlob::_ComputeAsmLtr)) { + //std::cerr << "!!! reordering " << args.size() << " arguments of " << sym->name() << std::endl; + res = pre_compile_tensor(args, code, lval_globs, func->arg_order); + } else { + res = pre_compile_tensor(args, code, lval_globs, {}); } auto rvect = new_tmp_vect(code); auto& op = code.emplace_back(here, Op::_Call, rvect, std::move(res), sym); @@ -253,9 +356,12 @@ std::vector Expr::pre_compile(CodeBlob& code) const { return rvect; } case _TypeApply: - return args[0]->pre_compile(code); + return args[0]->pre_compile(code, lval_globs); case _Var: case _Hole: + if (val < 0) { + throw src::ParseError{here, "unexpected variable definition"}; + } return {val}; case _VarApply: if (args[0]->cls == _Glob) { @@ -285,32 +391,42 @@ std::vector Expr::pre_compile(CodeBlob& code) const { case _Glob: case _GlobVar: { auto rvect = new_tmp_vect(code); - code.emplace_back(here, Op::_GlobVar, rvect, std::vector{}, sym); - return rvect; + if (lval_globs) { + lval_globs->push_back({ sym, rvect[0] }); + return rvect; + } else { + code.emplace_back(here, Op::_GlobVar, rvect, std::vector{}, sym); + return rvect; + } } case _Letop: { - auto right = args[1]->pre_compile(code); - if (args[0]->cls == Expr::_GlobVar) { - assert(args[0]->sym); - auto& op = code.emplace_back(here, Op::_SetGlob, std::vector{}, right, args[0]->sym); - op.flags |= Op::_Impure; - } else { - auto left = args[0]->pre_compile(code); - code.emplace_back(here, Op::_Let, std::move(left), right); - } - return right; + return pre_compile_let(code, args.at(0), args.at(1), here); } case _LetFirst: { auto rvect = new_tmp_vect(code); auto right = args[1]->pre_compile(code); - auto left = args[0]->pre_compile(code); + std::vector> local_globs; + if (!lval_globs) { + lval_globs = &local_globs; + } + auto left = args[0]->pre_compile(code, lval_globs); left.push_back(rvect[0]); + for (var_idx_t v : left) { + code.on_var_modification(v, here); + } code.emplace_back(here, Op::_Let, std::move(left), std::move(right)); + add_set_globs(code, local_globs, here); return rvect; } + case _MkTuple: { + auto left = new_tmp_vect(code); + auto right = args[0]->pre_compile(code); + code.emplace_back(here, Op::_Tuple, left, std::move(right)); + return left; + } case _CondExpr: { auto cond = args[0]->pre_compile(code); - assert(cond.size() == 1); + func_assert(cond.size() == 1); auto rvect = new_tmp_vect(code); Op& if_op = code.emplace_back(here, Op::_If, cond); code.push_set_cur(if_op.block0); @@ -321,6 +437,11 @@ std::vector Expr::pre_compile(CodeBlob& code) const { code.close_pop_cur(args[2]->here); return rvect; } + case _SliceConst: { + auto rvect = new_tmp_vect(code); + code.emplace_back(here, Op::_SliceConst, rvect, strval); + return rvect; + } default: std::cerr << "expression constructor is " << cls << std::endl; throw src::Fatal{"cannot compile expression with unknown constructor"}; diff --git a/crypto/func/keywords.cpp b/crypto/func/keywords.cpp index 91559f96..fedce9db 100644 --- a/crypto/func/keywords.cpp +++ b/crypto/func/keywords.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "func.h" @@ -46,6 +46,8 @@ void define_keywords() { .add_kw_char(';') .add_kw_char('(') .add_kw_char(')') + .add_kw_char('[') + .add_kw_char(']') .add_kw_char('{') .add_kw_char('}') .add_kw_char('=') @@ -95,6 +97,8 @@ void define_keywords() { .add_keyword("do", Kw::_Do) .add_keyword("while", Kw::_While) .add_keyword("until", Kw::_Until) + .add_keyword("try", Kw::_Try) + .add_keyword("catch", Kw::_Catch) .add_keyword("if", Kw::_If) .add_keyword("ifnot", Kw::_Ifnot) .add_keyword("then", Kw::_Then) @@ -118,11 +122,16 @@ void define_keywords() { .add_keyword("impure", Kw::_Impure) .add_keyword("inline", Kw::_Inline) .add_keyword("inline_ref", Kw::_InlineRef) + .add_keyword("auto_apply", Kw::_AutoApply) .add_keyword("method_id", Kw::_MethodId) .add_keyword("operator", Kw::_Operator) .add_keyword("infix", Kw::_Infix) .add_keyword("infixl", Kw::_Infixl) - .add_keyword("infixr", Kw::_Infixr); + .add_keyword("infixr", Kw::_Infixr) + .add_keyword("const", Kw::_Const); + + sym::symbols.add_keyword("#pragma", Kw::_PragmaHashtag) + .add_keyword("#include", Kw::_IncludeHashtag); } } // namespace funC diff --git a/crypto/func/optimize.cpp b/crypto/func/optimize.cpp index cd27150c..74bb97ec 100644 --- a/crypto/func/optimize.cpp +++ b/crypto/func/optimize.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "func.h" @@ -61,9 +61,9 @@ void Optimizer::apply() { if (!p_ && !q_) { return; } - assert(p_ > 0 && p_ <= l_ && q_ >= 0 && q_ <= n && l_ <= n); + func_assert(p_ > 0 && p_ <= l_ && q_ >= 0 && q_ <= n && l_ <= n); for (int i = p_; i < l_; i++) { - assert(op_[i]); + func_assert(op_[i]); op_cons_[i]->car = std::move(op_[i]); op_cons_[i] = nullptr; } @@ -71,7 +71,7 @@ void Optimizer::apply() { code_ = std::move(code_->cdr); } for (int j = q_ - 1; j >= 0; j--) { - assert(oq_[j]); + func_assert(oq_[j]); oq_[j]->indent = indent_; code_ = AsmOpCons::cons(std::move(oq_[j]), std::move(code_)); } @@ -134,37 +134,74 @@ bool Optimizer::say(std::string str) const { return true; } -bool Optimizer::is_const_push_swap() const { - return l_ >= 3 && op_[0]->is_gconst() && op_[1]->is_push() && op_[1]->a >= 1 && op_[2]->is_swap(); +bool Optimizer::find_const_op(int* op_idx, int cst) { + for (int i = 0; i < l2_; i++) { + if (op_[i]->is_gconst() && tr_[i].get(0) == cst) { + *op_idx = i; + return true; + } + } + return false; +} + +bool Optimizer::is_push_const(int* i, int* c) const { + return pb_ >= 3 && pb_ <= l2_ && tr_[pb_ - 1].is_push_const(i, c); } // PUSHCONST c ; PUSH s(i+1) ; SWAP -> PUSH s(i) ; PUSHCONST c -bool Optimizer::rewrite_const_push_swap() { - p_ = 3; +bool Optimizer::rewrite_push_const(int i, int c) { + p_ = pb_; q_ = 2; + int idx = -1; + if (!(p_ >= 2 && find_const_op(&idx, c) && idx < p_)) { + return false; + } show_left(); - oq_[1] = std::move(op_[0]); - oq_[0] = std::move(op_[1]); - (oq_[0]->a)--; + oq_[1] = std::move(op_[idx]); + oq_[0] = std::move(op_[!idx]); + *oq_[0] = AsmOp::Push(i); show_right(); return true; } -bool Optimizer::is_const_rot() const { - return pb_ >= 3 && pb_ <= l2_ && op_[0]->is_gconst() && tr_[pb_ - 1].is_const_rot(); +bool Optimizer::is_const_rot(int* c) const { + return pb_ >= 3 && pb_ <= l2_ && tr_[pb_ - 1].is_const_rot(c); } -bool Optimizer::rewrite_const_rot() { +bool Optimizer::rewrite_const_rot(int c) { p_ = pb_; q_ = 2; + int idx = -1; + if (!(p_ >= 2 && find_const_op(&idx, c) && idx < p_)) { + return false; + } show_left(); - oq_[0] = std::move(op_[0]); - oq_[1] = std::move(op_[1]); + oq_[0] = std::move(op_[idx]); + oq_[1] = std::move(op_[!idx]); *oq_[1] = AsmOp::Custom("ROT", 3, 3); show_right(); return true; } +bool Optimizer::is_const_pop(int* c, int* i) const { + return pb_ >= 3 && pb_ <= l2_ && tr_[pb_ - 1].is_const_pop(c, i); +} + +bool Optimizer::rewrite_const_pop(int c, int i) { + p_ = pb_; + q_ = 2; + int idx = -1; + if (!(p_ >= 2 && find_const_op(&idx, c) && idx < p_)) { + return false; + } + show_left(); + oq_[0] = std::move(op_[idx]); + oq_[1] = std::move(op_[!idx]); + *oq_[1] = AsmOp::Pop(i); + show_right(); + return true; +} + bool Optimizer::is_const_push_xchgs() { if (!(pb_ >= 2 && pb_ <= l2_ && op_[0]->is_gconst())) { return false; @@ -209,7 +246,7 @@ bool Optimizer::rewrite_const_push_xchgs() { } show_left(); auto c_op = std::move(op_[0]); - assert(c_op->is_gconst()); + func_assert(c_op->is_gconst()); StackTransform t; q_ = 0; int pos = 0; @@ -228,31 +265,31 @@ bool Optimizer::rewrite_const_push_xchgs() { if (b > pos) { oq_[q_]->b = b - 1; } - assert(apply_op(t, *oq_[q_])); + func_assert(apply_op(t, *oq_[q_])); ++q_; } } else { - assert(op_[i]->is_push(&a)); - assert(a != pos); + func_assert(op_[i]->is_push(&a)); + func_assert(a != pos); oq_[q_] = std::move(op_[i]); if (a > pos) { oq_[q_]->a = a - 1; } - assert(apply_op(t, *oq_[q_])); + func_assert(apply_op(t, *oq_[q_])); ++q_; ++pos; } } - assert(!pos); + func_assert(!pos); t.apply_push_newconst(); - assert(t <= tr_[p_ - 1]); + func_assert(t <= tr_[p_ - 1]); oq_[q_++] = std::move(c_op); show_right(); return true; } -bool Optimizer::simple_rewrite(int p, AsmOp&& new_op) { - assert(p > 0 && p <= l_); +bool Optimizer::rewrite(int p, AsmOp&& new_op) { + func_assert(p > 0 && p <= l_); p_ = p; q_ = 1; show_left(); @@ -262,8 +299,8 @@ bool Optimizer::simple_rewrite(int p, AsmOp&& new_op) { return true; } -bool Optimizer::simple_rewrite(int p, AsmOp&& new_op1, AsmOp&& new_op2) { - assert(p > 1 && p <= l_); +bool Optimizer::rewrite(int p, AsmOp&& new_op1, AsmOp&& new_op2) { + func_assert(p > 1 && p <= l_); p_ = p; q_ = 2; show_left(); @@ -275,8 +312,8 @@ bool Optimizer::simple_rewrite(int p, AsmOp&& new_op1, AsmOp&& new_op2) { return true; } -bool Optimizer::simple_rewrite(int p, AsmOp&& new_op1, AsmOp&& new_op2, AsmOp&& new_op3) { - assert(p > 2 && p <= l_); +bool Optimizer::rewrite(int p, AsmOp&& new_op1, AsmOp&& new_op2, AsmOp&& new_op3) { + func_assert(p > 2 && p <= l_); p_ = p; q_ = 3; show_left(); @@ -290,8 +327,8 @@ bool Optimizer::simple_rewrite(int p, AsmOp&& new_op1, AsmOp&& new_op2, AsmOp&& return true; } -bool Optimizer::simple_rewrite_nop() { - assert(p_ > 0 && p_ <= l_); +bool Optimizer::rewrite_nop() { + func_assert(p_ > 0 && p_ <= l_); q_ = 0; show_left(); show_right(); @@ -356,6 +393,13 @@ bool Optimizer::is_xchg(int* i, int* j) { return is_pred([i, j](const auto& t) { return t.is_xchg(i, j) && ((*i < 16 && *j < 16) || (!*i && *j < 256)); }); } +bool Optimizer::is_xchg_xchg(int* i, int* j, int* k, int* l) { + return is_pred([i, j, k, l](const auto& t) { + return t.is_xchg_xchg(i, j, k, l) && (*i < 2 && *j < (*i ? 16 : 256) && *k < 2 && *l < (*k ? 16 : 256)); + }) && + (!(p_ == 2 && op_[0]->is_xchg(*i, *j) && op_[1]->is_xchg(*k, *l))); +} + bool Optimizer::is_push(int* i) { return is_pred([i](const auto& t) { return t.is_push(i) && *i < 256; }); } @@ -364,6 +408,23 @@ bool Optimizer::is_pop(int* i) { return is_pred([i](const auto& t) { return t.is_pop(i) && *i < 256; }); } +bool Optimizer::is_pop_pop(int* i, int* j) { + return is_pred([i, j](const auto& t) { return t.is_pop_pop(i, j) && *i < 256 && *j < 256; }, 3); +} + +bool Optimizer::is_push_rot(int* i) { + return is_pred([i](const auto& t) { return t.is_push_rot(i) && *i < 16; }, 3); +} + +bool Optimizer::is_push_rotrev(int* i) { + return is_pred([i](const auto& t) { return t.is_push_rotrev(i) && *i < 16; }, 3); +} + +bool Optimizer::is_push_xchg(int* i, int* j, int* k) { + return is_pred([i, j, k](const auto& t) { return t.is_push_xchg(i, j, k) && *i < 16 && *j < 16 && *k < 16; }) && + !(p_ == 2 && op_[0]->is_push() && op_[1]->is_xchg()); +} + bool Optimizer::is_xchg2(int* i, int* j) { return is_pred([i, j](const auto& t) { return t.is_xchg2(i, j) && *i < 16 && *j < 16; }); } @@ -397,7 +458,8 @@ bool Optimizer::is_xcpu2(int* i, int* j, int* k) { } bool Optimizer::is_puxc2(int* i, int* j, int* k) { - return is_pred([i, j, k](const auto& t) { return t.is_puxc2(i, j, k) && *i < 16 && *j < 15 && *k < 15; }); + return is_pred( + [i, j, k](const auto& t) { return t.is_puxc2(i, j, k) && *i < 16 && *j < 15 && *k < 15 && *j + *k != -1; }); } bool Optimizer::is_puxcpu(int* i, int* j, int* k) { @@ -424,6 +486,10 @@ bool Optimizer::is_blkdrop(int* i) { return is_pred([i](const auto& t) { return t.is_blkdrop(i) && *i > 0 && *i < 16; }); } +bool Optimizer::is_blkdrop2(int* i, int* j) { + return is_pred([i, j](const auto& t) { return t.is_blkdrop2(i, j) && *i > 0 && *i < 16 && *j > 0 && *j < 16; }); +} + bool Optimizer::is_reverse(int* i, int* j) { return is_pred([i, j](const auto& t) { return t.is_reverse(i, j) && *i >= 2 && *i <= 17 && *j < 16; }); } @@ -488,38 +554,41 @@ bool Optimizer::find_at_least(int pb) { p_ = q_ = 0; pb_ = pb; // show_stack_transforms(); - int i = -100, j = -100, k = -100; - return (is_const_push_swap() && 3 >= pb && rewrite_const_push_swap()) || (is_nop() && simple_rewrite_nop()) || - (is_const_rot() && rewrite_const_rot()) || (is_const_push_xchgs() && rewrite_const_push_xchgs()) || - (is_xchg(&i, &j) && simple_rewrite(AsmOp::Xchg(i, j))) || (is_push(&i) && simple_rewrite(AsmOp::Push(i))) || - (is_pop(&i) && simple_rewrite(AsmOp::Pop(i))) || (is_rot() && simple_rewrite(AsmOp::Custom("ROT", 3, 3))) || - (is_rotrev() && simple_rewrite(AsmOp::Custom("-ROT", 3, 3))) || - (is_2dup() && simple_rewrite(AsmOp::Custom("2DUP", 2, 4))) || - (is_2swap() && simple_rewrite(AsmOp::Custom("2SWAP", 2, 4))) || - (is_2over() && simple_rewrite(AsmOp::Custom("2OVER", 2, 4))) || - (is_tuck() && simple_rewrite(AsmOp::Custom("TUCK", 2, 3))) || - (is_2drop() && simple_rewrite(AsmOp::Custom("2DROP", 2, 0))) || - (is_xchg2(&i, &j) && simple_rewrite(AsmOp::Xchg2(i, j))) || - (is_xcpu(&i, &j) && simple_rewrite(AsmOp::XcPu(i, j))) || - (is_puxc(&i, &j) && simple_rewrite(AsmOp::PuXc(i, j))) || - (is_push2(&i, &j) && simple_rewrite(AsmOp::Push2(i, j))) || - (is_blkswap(&i, &j) && simple_rewrite(AsmOp::BlkSwap(i, j))) || - (is_blkpush(&i, &j) && simple_rewrite(AsmOp::BlkPush(i, j))) || - (is_blkdrop(&i) && simple_rewrite(AsmOp::BlkDrop(i))) || - (is_reverse(&i, &j) && simple_rewrite(AsmOp::BlkReverse(i, j))) || - (is_nip_seq(&i, &j) && simple_rewrite(AsmOp::Xchg(i, j), AsmOp::BlkDrop(i))) || - (is_pop_blkdrop(&i, &k) && simple_rewrite(AsmOp::Pop(i), AsmOp::BlkDrop(k))) || - (is_2pop_blkdrop(&i, &j, &k) && (k >= 3 && k <= 13 && i != j + 1 && i <= 15 && j <= 14 - ? simple_rewrite(AsmOp::Xchg2(j + 1, i), AsmOp::BlkDrop(k + 2)) - : simple_rewrite(AsmOp::Pop(i), AsmOp::Pop(j), AsmOp::BlkDrop(k)))) || - (is_xchg3(&i, &j, &k) && simple_rewrite(AsmOp::Xchg3(i, j, k))) || - (is_xc2pu(&i, &j, &k) && simple_rewrite(AsmOp::Xc2Pu(i, j, k))) || - (is_xcpuxc(&i, &j, &k) && simple_rewrite(AsmOp::XcPuXc(i, j, k))) || - (is_xcpu2(&i, &j, &k) && simple_rewrite(AsmOp::XcPu2(i, j, k))) || - (is_puxc2(&i, &j, &k) && simple_rewrite(AsmOp::PuXc2(i, j, k))) || - (is_puxcpu(&i, &j, &k) && simple_rewrite(AsmOp::PuXcPu(i, j, k))) || - (is_pu2xc(&i, &j, &k) && simple_rewrite(AsmOp::Pu2Xc(i, j, k))) || - (is_push3(&i, &j, &k) && simple_rewrite(AsmOp::Push3(i, j, k))); + int i, j, k, l, c; + return (is_push_const(&i, &c) && rewrite_push_const(i, c)) || (is_nop() && rewrite_nop()) || + (!(mode_ & 1) && is_const_rot(&c) && rewrite_const_rot(c)) || + (is_const_push_xchgs() && rewrite_const_push_xchgs()) || (is_const_pop(&c, &i) && rewrite_const_pop(c, i)) || + (is_xchg(&i, &j) && rewrite(AsmOp::Xchg(i, j))) || (is_push(&i) && rewrite(AsmOp::Push(i))) || + (is_pop(&i) && rewrite(AsmOp::Pop(i))) || (is_pop_pop(&i, &j) && rewrite(AsmOp::Pop(i), AsmOp::Pop(j))) || + (is_xchg_xchg(&i, &j, &k, &l) && rewrite(AsmOp::Xchg(i, j), AsmOp::Xchg(k, l))) || + (!(mode_ & 1) && + ((is_rot() && rewrite(AsmOp::Custom("ROT", 3, 3))) || (is_rotrev() && rewrite(AsmOp::Custom("-ROT", 3, 3))) || + (is_2dup() && rewrite(AsmOp::Custom("2DUP", 2, 4))) || + (is_2swap() && rewrite(AsmOp::Custom("2SWAP", 2, 4))) || + (is_2over() && rewrite(AsmOp::Custom("2OVER", 2, 4))) || + (is_tuck() && rewrite(AsmOp::Custom("TUCK", 2, 3))) || + (is_2drop() && rewrite(AsmOp::Custom("2DROP", 2, 0))) || (is_xchg2(&i, &j) && rewrite(AsmOp::Xchg2(i, j))) || + (is_xcpu(&i, &j) && rewrite(AsmOp::XcPu(i, j))) || (is_puxc(&i, &j) && rewrite(AsmOp::PuXc(i, j))) || + (is_push2(&i, &j) && rewrite(AsmOp::Push2(i, j))) || (is_blkswap(&i, &j) && rewrite(AsmOp::BlkSwap(i, j))) || + (is_blkpush(&i, &j) && rewrite(AsmOp::BlkPush(i, j))) || (is_blkdrop(&i) && rewrite(AsmOp::BlkDrop(i))) || + (is_push_rot(&i) && rewrite(AsmOp::Push(i), AsmOp::Custom("ROT"))) || + (is_push_rotrev(&i) && rewrite(AsmOp::Push(i), AsmOp::Custom("-ROT"))) || + (is_push_xchg(&i, &j, &k) && rewrite(AsmOp::Push(i), AsmOp::Xchg(j, k))) || + (is_reverse(&i, &j) && rewrite(AsmOp::BlkReverse(i, j))) || + (is_blkdrop2(&i, &j) && rewrite(AsmOp::BlkDrop2(i, j))) || + (is_nip_seq(&i, &j) && rewrite(AsmOp::Xchg(i, j), AsmOp::BlkDrop(i))) || + (is_pop_blkdrop(&i, &k) && rewrite(AsmOp::Pop(i), AsmOp::BlkDrop(k))) || + (is_2pop_blkdrop(&i, &j, &k) && (k >= 3 && k <= 13 && i != j + 1 && i <= 15 && j <= 14 + ? rewrite(AsmOp::Xchg2(j + 1, i), AsmOp::BlkDrop(k + 2)) + : rewrite(AsmOp::Pop(i), AsmOp::Pop(j), AsmOp::BlkDrop(k)))) || + (is_xchg3(&i, &j, &k) && rewrite(AsmOp::Xchg3(i, j, k))) || + (is_xc2pu(&i, &j, &k) && rewrite(AsmOp::Xc2Pu(i, j, k))) || + (is_xcpuxc(&i, &j, &k) && rewrite(AsmOp::XcPuXc(i, j, k))) || + (is_xcpu2(&i, &j, &k) && rewrite(AsmOp::XcPu2(i, j, k))) || + (is_puxc2(&i, &j, &k) && rewrite(AsmOp::PuXc2(i, j, k))) || + (is_puxcpu(&i, &j, &k) && rewrite(AsmOp::PuXcPu(i, j, k))) || + (is_pu2xc(&i, &j, &k) && rewrite(AsmOp::Pu2Xc(i, j, k))) || + (is_push3(&i, &j, &k) && rewrite(AsmOp::Push3(i, j, k))))); } bool Optimizer::find() { @@ -544,17 +613,17 @@ bool Optimizer::optimize() { return f; } -AsmOpConsList optimize_code_head(AsmOpConsList op_list) { - Optimizer opt(std::move(op_list), op_rewrite_comments); +AsmOpConsList optimize_code_head(AsmOpConsList op_list, int mode) { + Optimizer opt(std::move(op_list), op_rewrite_comments, mode); opt.optimize(); return opt.extract_code(); } -AsmOpConsList optimize_code(AsmOpConsList op_list) { +AsmOpConsList optimize_code(AsmOpConsList op_list, int mode) { std::vector> v; while (op_list) { if (!op_list->car->is_comment()) { - op_list = optimize_code_head(std::move(op_list)); + op_list = optimize_code_head(std::move(op_list), mode); } if (op_list) { v.push_back(std::move(op_list->car)); @@ -568,11 +637,13 @@ AsmOpConsList optimize_code(AsmOpConsList op_list) { } void optimize_code(AsmOpList& ops) { - std::unique_ptr op_list; + AsmOpConsList op_list; for (auto it = ops.list_.rbegin(); it < ops.list_.rend(); ++it) { op_list = AsmOpCons::cons(std::make_unique(std::move(*it)), std::move(op_list)); } - op_list = optimize_code(std::move(op_list)); + for (int mode : {1, 1, 1, 1, 0, 0, 0, 0}) { + op_list = optimize_code(std::move(op_list), mode); + } ops.list_.clear(); while (op_list) { ops.list_.push_back(std::move(*(op_list->car))); diff --git a/crypto/func/parse-func.cpp b/crypto/func/parse-func.cpp index 01a43f63..15794c42 100644 --- a/crypto/func/parse-func.cpp +++ b/crypto/func/parse-func.cpp @@ -14,11 +14,14 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "func.h" #include "td/utils/crypto.h" -#include +#include "common/refint.h" +#include "openssl/digest.hpp" +#include "block/block.h" +#include "block-parse.h" namespace sym { @@ -62,7 +65,7 @@ inline bool is_special_ident(sym_idx_t idx) { */ // TE ::= TA | TA -> TE -// TA ::= int | ... | cont | var | _ | () | ( TE { , TE } ) +// TA ::= int | ... | cont | var | _ | () | ( TE { , TE } ) | [ TE { , TE } ] TypeExpr* parse_type(Lexer& lex); TypeExpr* parse_type1(Lexer& lex) { @@ -99,14 +102,21 @@ TypeExpr* parse_type1(Lexer& lex) { lex.cur().error_at("`", "` is not a type identifier"); } } - lex.expect('('); - if (lex.tp() == ')') { + int c; + if (lex.tp() == '[') { lex.next(); - return TypeExpr::new_unit(); + c = ']'; + } else { + lex.expect('('); + c = ')'; + } + if (lex.tp() == c) { + lex.next(); + return c == ')' ? TypeExpr::new_unit() : TypeExpr::new_tuple({}); } auto t1 = parse_type(lex); - if (lex.tp() != ',') { - lex.expect(')'); + if (lex.tp() == ')') { + lex.expect(c); return t1; } std::vector tlist{1, t1}; @@ -114,8 +124,8 @@ TypeExpr* parse_type1(Lexer& lex) { lex.next(); tlist.push_back(parse_type(lex)); } - lex.expect(')'); - return TypeExpr::new_tensor(std::move(tlist)); + lex.expect(c); + return c == ')' ? TypeExpr::new_tensor(std::move(tlist)) : TypeExpr::new_tuple(std::move(tlist)); } TypeExpr* parse_type(Lexer& lex) { @@ -162,6 +172,10 @@ FormalArg parse_formal_arg(Lexer& lex, int fa_idx) { lex.expect(_Ident, "formal parameter name"); } loc = lex.cur().loc; + if (prohibited_var_names.count(sym::symbols.get_name(lex.cur().val))) { + throw src::ParseError{ + loc, PSTRING() << "symbol `" << sym::symbols.get_name(lex.cur().val) << "` cannot be redefined as a variable"}; + } SymDef* new_sym_def = sym::define_symbol(lex.cur().val, true, loc); if (!new_sym_def) { lex.cur().error_at("cannot define symbol `", "`"); @@ -221,6 +235,98 @@ void parse_global_var_decl(Lexer& lex) { lex.next(); } +extern int const_cnt; +Expr* parse_expr(Lexer& lex, CodeBlob& code, bool nv = false); + +void parse_const_decl(Lexer& lex) { + SrcLocation loc = lex.cur().loc; + int wanted_type = Expr::_None; + if (lex.tp() == _Int) { + wanted_type = Expr::_Const; + lex.next(); + } else if (lex.tp() == _Slice) { + wanted_type = Expr::_SliceConst; + lex.next(); + } + if (lex.tp() != _Ident) { + lex.expect(_Ident, "constant name"); + } + loc = lex.cur().loc; + SymDef* sym_def = sym::define_global_symbol(lex.cur().val, false, loc); + if (!sym_def) { + lex.cur().error_at("cannot define global symbol `", "`"); + } + Lexem ident = lex.cur(); + lex.next(); + if (lex.tp() != '=') { + lex.cur().error_at("expected = instead of ", ""); + } + lex.next(); + CodeBlob code; + if (pragma_allow_post_modification.enabled()) { + code.flags |= CodeBlob::_AllowPostModification; + } + if (pragma_compute_asm_ltr.enabled()) { + code.flags |= CodeBlob::_ComputeAsmLtr; + } + // Handles processing and resolution of literals and consts + auto x = parse_expr(lex, code, false); // also does lex.next() ! + if (x->flags != Expr::_IsRvalue) { + lex.cur().error("expression is not strictly Rvalue"); + } + if ((wanted_type == Expr::_Const) && (x->cls == Expr::_Apply)) + wanted_type = Expr::_None; // Apply is additionally checked to result in an integer + if ((wanted_type != Expr::_None) && (x->cls != wanted_type)) { + lex.cur().error("expression type does not match wanted type"); + } + SymValConst* new_value = nullptr; + if (x->cls == Expr::_Const) { // Integer constant + new_value = new SymValConst{const_cnt++, x->intval}; + } else if (x->cls == Expr::_SliceConst) { // Slice constant (string) + new_value = new SymValConst{const_cnt++, x->strval}; + } else if (x->cls == Expr::_Apply) { + code.emplace_back(loc, Op::_Import, std::vector()); + auto tmp_vars = x->pre_compile(code); + code.emplace_back(loc, Op::_Return, std::move(tmp_vars)); + code.emplace_back(loc, Op::_Nop); // This is neccessary to prevent SIGSEGV! + // It is REQUIRED to execute "optimizations" as in func.cpp + code.simplify_var_types(); + code.prune_unreachable_code(); + code.split_vars(true); + for (int i = 0; i < 16; i++) { + code.compute_used_code_vars(); + code.fwd_analyze(); + code.prune_unreachable_code(); + } + code.mark_noreturn(); + AsmOpList out_list(0, &code.vars); + code.generate_code(out_list); + if (out_list.list_.size() != 1) { + lex.cur().error("precompiled expression must result in single operation"); + } + auto op = out_list.list_[0]; + if (!op.is_const()) { + lex.cur().error("precompiled expression must result in compilation time constant"); + } + if (op.origin.is_null() || !op.origin->is_valid()) { + lex.cur().error("precompiled expression did not result in a valid integer constant"); + } + new_value = new SymValConst{const_cnt++, op.origin}; + } else { + lex.cur().error("integer or slice literal or constant expected"); + } + if (sym_def->value) { + SymValConst* old_value = dynamic_cast(sym_def->value); + Keyword new_type = new_value->get_type(); + if (!old_value || old_value->get_type() != new_type || + (new_type == _Int && *old_value->get_int_value() != *new_value->get_int_value()) || + (new_type == _Slice && old_value->get_str_value() != new_value->get_str_value())) { + ident.error_at("global symbol `", "` already exists"); + } + } + sym_def->value = new_value; +} + FormalArgList parse_formal_args(Lexer& lex) { FormalArgList args; lex.expect('(', "formal argument list"); @@ -238,6 +344,18 @@ FormalArgList parse_formal_args(Lexer& lex) { return args; } +void parse_const_decls(Lexer& lex) { + lex.expect(_Const); + while (true) { + parse_const_decl(lex); + if (lex.tp() != ',') { + break; + } + lex.expect(','); + } + lex.expect(';'); +} + TypeExpr* extract_total_arg_type(const FormalArgList& arg_list) { if (arg_list.empty()) { return TypeExpr::new_unit(); @@ -281,9 +399,9 @@ bool check_global_func(const Lexem& cur, sym_idx_t func_name = 0) { cur.loc.show_error(std::string{"undefined function `"} + symbols.get_name(func_name) + "`, defining a global function of unknown type"); def = sym::define_global_symbol(func_name, 0, cur.loc); - assert(def && "cannot define global function"); + func_assert(def && "cannot define global function"); ++undef_func_cnt; - make_new_glob_func(def, TypeExpr::new_hole()); // was: ... ::new_func() + make_new_glob_func(def, TypeExpr::new_func()); // was: ... ::new_func() return true; } SymVal* val = dynamic_cast(def->value); @@ -301,7 +419,7 @@ bool check_global_func(const Lexem& cur, sym_idx_t func_name = 0) { Expr* make_func_apply(Expr* fun, Expr* x) { Expr* res; if (fun->cls == Expr::_Glob) { - if (x->cls == Expr::_Tuple) { + if (x->cls == Expr::_Tensor) { res = new Expr{Expr::_Apply, fun->sym, x->args}; } else { res = new Expr{Expr::_Apply, fun->sym, {x}}; @@ -314,30 +432,36 @@ Expr* make_func_apply(Expr* fun, Expr* x) { return res; } -Expr* parse_expr(Lexer& lex, CodeBlob& code, bool nv = false); - -// parse ( E { , E } ) | () | id | num | _ +// parse ( E { , E } ) | () | [ E { , E } ] | [] | id | num | _ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { - if (lex.tp() == '(') { + if (lex.tp() == '(' || lex.tp() == '[') { + bool tf = (lex.tp() == '['); + int clbr = (tf ? ']' : ')'); SrcLocation loc{lex.cur().loc}; lex.next(); - if (lex.tp() == ')') { + if (lex.tp() == clbr) { lex.next(); - Expr* res = new Expr{Expr::_Tuple, {}}; + Expr* res = new Expr{Expr::_Tensor, {}}; res->flags = Expr::_IsRvalue; res->here = loc; res->e_type = TypeExpr::new_unit(); + if (tf) { + res = new Expr{Expr::_MkTuple, {res}}; + res->flags = Expr::_IsRvalue; + res->here = loc; + res->e_type = TypeExpr::new_tuple(res->args.at(0)->e_type); + } return res; } Expr* res = parse_expr(lex, code, nv); - if (lex.tp() != ',') { - lex.expect(')'); + if (lex.tp() == ')') { + lex.expect(clbr); return res; } std::vector type_list; type_list.push_back(res->e_type); int f = res->flags; - res = new Expr{Expr::_Tuple, {res}}; + res = new Expr{Expr::_Tensor, {res}}; while (lex.tp() == ',') { lex.next(); auto x = parse_expr(lex, code, nv); @@ -350,8 +474,14 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { } res->here = loc; res->flags = f; - res->e_type = TypeExpr::new_tensor(std::move(type_list)); - lex.expect(')'); + res->e_type = TypeExpr::new_tensor(std::move(type_list), !tf); + if (tf) { + res = new Expr{Expr::_MkTuple, {res}}; + res->flags = f; + res->here = loc; + res->e_type = TypeExpr::new_tuple(res->args.at(0)->e_type); + } + lex.expect(clbr); return res; } int t = lex.tp(); @@ -359,13 +489,94 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { Expr* res = new Expr{Expr::_Const, lex.cur().loc}; res->flags = Expr::_IsRvalue; res->intval = td::string_to_int256(lex.cur().str); - if (res->intval.is_null()) { + if (res->intval.is_null() || !res->intval->signed_fits_bits(257)) { lex.cur().error_at("invalid integer constant `", "`"); } res->e_type = TypeExpr::new_atomic(_Int); lex.next(); return res; } + if (t == Lexem::String) { + std::string str = lex.cur().str; + int str_type = lex.cur().val; + Expr* res; + switch (str_type) { + case 0: + case 's': + case 'a': + { + res = new Expr{Expr::_SliceConst, lex.cur().loc}; + res->e_type = TypeExpr::new_atomic(_Slice); + break; + } + case 'u': + case 'h': + case 'H': + case 'c': + { + res = new Expr{Expr::_Const, lex.cur().loc}; + res->e_type = TypeExpr::new_atomic(_Int); + break; + } + default: + { + res = new Expr{Expr::_Const, lex.cur().loc}; + res->e_type = TypeExpr::new_atomic(_Int); + lex.cur().error("invalid string type `" + std::string(1, static_cast(str_type)) + "`"); + return res; + } + } + res->flags = Expr::_IsRvalue; + switch (str_type) { + case 0: { + res->strval = td::hex_encode(str); + break; + } + case 's': { + res->strval = str; + unsigned char buff[128]; + int bits = (int)td::bitstring::parse_bitstring_hex_literal(buff, sizeof(buff), str.data(), str.data() + str.size()); + if (bits < 0) { + lex.cur().error_at("Invalid hex bitstring constant `", "`"); + } + break; + } + case 'a': { // MsgAddressInt + block::StdAddress a; + if (a.parse_addr(str)) { + res->strval = block::tlb::MsgAddressInt().pack_std_address(a)->as_bitslice().to_hex(); + } else { + lex.cur().error_at("invalid standard address `", "`"); + } + break; + } + case 'u': { + res->intval = td::hex_string_to_int256(td::hex_encode(str)); + if (!str.size()) { + lex.cur().error("empty integer ascii-constant"); + } + if (res->intval.is_null()) { + lex.cur().error_at("too long integer ascii-constant `", "`"); + } + break; + } + case 'h': + case 'H': + { + unsigned char hash[32]; + digest::hash_str(hash, str.data(), str.size()); + res->intval = td::bits_to_refint(hash, (str_type == 'h') ? 32 : 256, false); + break; + } + case 'c': + { + res->intval = td::make_refint(td::crc32(td::Slice{str})); + break; + } + } + lex.next(); + return res; + } if (t == '_') { Expr* res = new Expr{Expr::_Hole, lex.cur().loc}; res->val = -1; @@ -381,7 +592,7 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { lex.next(); return res; } - if (t == _Int || t == _Cell || t == _Slice || t == _Builder || t == _Cont || t == _Type) { + if (t == _Int || t == _Cell || t == _Slice || t == _Builder || t == _Cont || t == _Type || t == _Tuple) { Expr* res = new Expr{Expr::_Type, lex.cur().loc}; res->flags = Expr::_IsType; res->e_type = TypeExpr::new_atomic(t); @@ -407,6 +618,25 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { lex.next(); return res; } + if (sym && dynamic_cast(sym->value)) { + auto val = dynamic_cast(sym->value); + Expr* res = new Expr{Expr::_None, lex.cur().loc}; + res->flags = Expr::_IsRvalue; + if (val->type == _Int) { + res->cls = Expr::_Const; + res->intval = val->get_int_value(); + } + else if (val->type == _Slice) { + res->cls = Expr::_SliceConst; + res->strval = val->get_str_value(); + } + else { + lex.cur().error("Invalid symbolic constant type"); + } + res->e_type = TypeExpr::new_atomic(val->type); + lex.next(); + return res; + } bool auto_apply = false; Expr* res = new Expr{Expr::_Var, lex.cur().loc}; if (nv) { @@ -457,7 +687,7 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { // parse E { E } Expr* parse_expr90(Lexer& lex, CodeBlob& code, bool nv) { Expr* res = parse_expr100(lex, code, nv); - while (lex.tp() == '(' || (lex.tp() == _Ident && !is_special_ident(lex.cur().val))) { + while (lex.tp() == '(' || lex.tp() == '[' || (lex.tp() == _Ident && !is_special_ident(lex.cur().val))) { if (res->is_type()) { Expr* x = parse_expr100(lex, code, true); x->chk_lvalue(lex.cur()); // chk_lrvalue() ? @@ -522,7 +752,7 @@ Expr* parse_expr80(Lexer& lex, CodeBlob& code, bool nv) { lex.next(); auto x = parse_expr100(lex, code, false); x->chk_rvalue(lex.cur()); - if (x->cls == Expr::_Tuple) { + if (x->cls == Expr::_Tensor) { res = new Expr{Expr::_Apply, name, {obj}}; res->args.insert(res->args.end(), x->args.begin(), x->args.end()); } else { @@ -880,6 +1110,37 @@ blk_fl::val parse_do_stmt(Lexer& lex, CodeBlob& code) { return res & ~blk_fl::empty; } +blk_fl::val parse_try_catch_stmt(Lexer& lex, CodeBlob& code) { + code.require_callxargs = true; + lex.expect(_Try); + Op& try_catch_op = code.emplace_back(lex.cur().loc, Op::_TryCatch); + code.push_set_cur(try_catch_op.block0); + blk_fl::val res0 = parse_block_stmt(lex, code); + code.close_pop_cur(lex.cur().loc); + lex.expect(_Catch); + code.push_set_cur(try_catch_op.block1); + sym::open_scope(lex); + Expr* expr = parse_expr(lex, code, true); + expr->chk_lvalue(lex.cur()); + TypeExpr* tvm_error_type = TypeExpr::new_tensor(TypeExpr::new_var(), TypeExpr::new_atomic(_Int)); + try { + unify(expr->e_type, tvm_error_type); + } catch (UnifyError& ue) { + std::ostringstream os; + os << "`catch` arguments have incorrect type " << expr->e_type << ": " << ue; + lex.cur().error(os.str()); + } + expr->predefine_vars(); + expr->define_new_vars(code); + try_catch_op.left = expr->pre_compile(code); + func_assert(try_catch_op.left.size() == 2 || try_catch_op.left.size() == 1); + blk_fl::val res1 = parse_block_stmt(lex, code); + sym::close_scope(lex); + code.close_pop_cur(lex.cur().loc); + blk_fl::combine_parallel(res0, res1); + return res0; +} + blk_fl::val parse_if_stmt(Lexer& lex, CodeBlob& code, int first_lex = _If) { SrcLocation loc{lex.cur().loc}; lex.expect(first_lex); @@ -943,6 +1204,8 @@ blk_fl::val parse_stmt(Lexer& lex, CodeBlob& code) { return parse_do_stmt(lex, code); case _While: return parse_while_stmt(lex, code); + case _Try: + return parse_try_catch_stmt(lex, code); default: { auto expr = parse_expr(lex, code); expr->chk_rvalue(lex.cur()); @@ -956,6 +1219,12 @@ blk_fl::val parse_stmt(Lexer& lex, CodeBlob& code) { CodeBlob* parse_func_body(Lexer& lex, FormalArgList arg_list, TypeExpr* ret_type) { lex.expect('{'); CodeBlob* blob = new CodeBlob{ret_type}; + if (pragma_allow_post_modification.enabled()) { + blob->flags |= CodeBlob::_AllowPostModification; + } + if (pragma_compute_asm_ltr.enabled()) { + blob->flags |= CodeBlob::_ComputeAsmLtr; + } blob->import_params(std::move(arg_list)); blk_fl::val res = blk_fl::init; bool warned = false; @@ -1027,7 +1296,7 @@ SymValAsmFunc* parse_asm_func_body(Lexer& lex, TypeExpr* func_type, const Formal } lex.next(); } - assert(arg_order.size() == (unsigned)tot_width); + func_assert(arg_order.size() == (unsigned)tot_width); } if (lex.tp() == _Mapsto) { lex.expect(_Mapsto); @@ -1048,19 +1317,48 @@ SymValAsmFunc* parse_asm_func_body(Lexer& lex, TypeExpr* func_type, const Formal lex.expect(')'); } while (lex.tp() == _String) { - asm_ops.push_back(AsmOp::Parse(lex.cur().str, cnt, width)); - lex.next(); - if (asm_ops.back().is_custom()) { - cnt = width; + std::string ops = lex.cur().str; // \n\n... + std::string op; + for (const char& c : ops) { + if (c == '\n') { + if (!op.empty()) { + asm_ops.push_back(AsmOp::Parse(op, cnt, width)); + if (asm_ops.back().is_custom()) { + cnt = width; + } + op.clear(); + } + } else { + op.push_back(c); + } } + if (!op.empty()) { + asm_ops.push_back(AsmOp::Parse(op, cnt, width)); + if (asm_ops.back().is_custom()) { + cnt = width; + } + } + lex.next(); } if (asm_ops.empty()) { throw src::ParseError{lex.cur().loc, "string with assembler instruction expected"}; } lex.expect(';'); + std::string crc_s; + for (const AsmOp& asm_op : asm_ops) { + crc_s += asm_op.op; + } + crc_s.push_back(impure); + for (const int& x : arg_order) { + crc_s += std::string((const char*) (&x), (const char*) (&x + 1)); + } + for (const int& x : ret_order) { + crc_s += std::string((const char*) (&x), (const char*) (&x + 1)); + } auto res = new SymValAsmFunc{func_type, asm_ops, impure}; res->arg_order = std::move(arg_order); res->ret_order = std::move(ret_order); + res->crc = td::crc64(crc_s); return res; } @@ -1076,8 +1374,12 @@ std::vector parse_type_var_list(Lexer& lex) { throw src::ParseError{lex.cur().loc, "free type identifier expected"}; } auto loc = lex.cur().loc; + if (prohibited_var_names.count(sym::symbols.get_name(lex.cur().val))) { + throw src::ParseError{loc, PSTRING() << "symbol `" << sym::symbols.get_name(lex.cur().val) + << "` cannot be redefined as a variable"}; + } SymDef* new_sym_def = sym::define_symbol(lex.cur().val, true, loc); - if (new_sym_def->value) { + if (!new_sym_def || new_sym_def->value) { lex.cur().error_at("redefined type variable `", "`"); } auto var = TypeExpr::new_var(idx); @@ -1186,7 +1488,7 @@ void parse_func_def(Lexer& lex) { std::cerr << "function " << func_name.str << " : " << func_type << std::endl; } SymDef* func_sym = sym::define_global_symbol(func_name.val, 0, loc); - assert(func_sym); + func_assert(func_sym); SymValFunc* func_sym_val = dynamic_cast(func_sym->value); if (func_sym->value) { if (func_sym->value->type != SymVal::_Func || !func_sym_val) { @@ -1226,16 +1528,22 @@ void parse_func_def(Lexer& lex) { // code->print(std::cerr); // !!!DEBUG!!! func_sym_code->code = code; } else { + Lexem asm_lexem = lex.cur(); + SymValAsmFunc* asm_func = parse_asm_func_body(lex, func_type, arg_list, ret_type, impure); if (func_sym_val) { if (dynamic_cast(func_sym_val)) { - lex.cur().error("function `"s + func_name.str + "` was already declared as an ordinary function"); + asm_lexem.error("function `"s + func_name.str + "` was already declared as an ordinary function"); } - if (dynamic_cast(func_sym_val)) { - lex.cur().error("redefinition of built-in assembler function `"s + func_name.str + "`"); + SymValAsmFunc* asm_func_old = dynamic_cast(func_sym_val); + if (asm_func_old) { + if (asm_func->crc != asm_func_old->crc) { + asm_lexem.error("redefinition of built-in assembler function `"s + func_name.str + "`"); + } + } else { + asm_lexem.error("redefinition of previously (somehow) defined function `"s + func_name.str + "`"); } - lex.cur().error("redefinition of previously (somehow) defined function `"s + func_name.str + "`"); } - func_sym->value = parse_asm_func_body(lex, func_type, arg_list, ret_type, impure); + func_sym->value = asm_func; } if (method_id.not_null()) { auto val = dynamic_cast(func_sym->value); @@ -1244,8 +1552,9 @@ void parse_func_def(Lexer& lex) { } if (val->method_id.is_null()) { val->method_id = std::move(method_id); - } else if (val->method_id != method_id) { - lex.cur().error("integer method identifier for `"s + func_name.str + "` changed to a different value"); + } else if (td::cmp(val->method_id, method_id) != 0) { + lex.cur().error("integer method identifier for `"s + func_name.str + "` changed from " + + val->method_id->to_dec_string() + " to a different value " + method_id->to_dec_string()); } } if (f) { @@ -1265,12 +1574,179 @@ void parse_func_def(Lexer& lex) { sym::close_scope(lex); } -bool parse_source(std::istream* is, const src::FileDescr* fdescr) { +std::string func_ver_test = func_version; + +void parse_pragma(Lexer& lex) { + auto pragma = lex.cur(); + lex.next(); + if (lex.tp() != _Ident) { + lex.expect(_Ident, "pragma name expected"); + } + auto pragma_name = lex.cur().str; + lex.next(); + if (!pragma_name.compare("version") || !pragma_name.compare("not-version")) { + bool negate = !pragma_name.compare("not-version"); + char op = '='; bool eq = false; + int sem_ver[3] = {0, 0, 0}; + char segs = 1; + auto stoi = [&](const std::string& s) { + auto R = td::to_integer_safe(s); + if (R.is_error()) { + lex.cur().error("invalid semver format"); + } + return R.move_as_ok(); + }; + if (lex.tp() == _Number) { + sem_ver[0] = stoi(lex.cur().str); + } else if (lex.tp() == _Ident) { + auto id1 = lex.cur().str; + char ch1 = id1[0]; + if ((ch1 == '>') || (ch1 == '<') || (ch1 == '=') || (ch1 == '^')) { + op = ch1; + } else { + lex.cur().error("unexpected comparator operation"); + } + if (id1.length() < 2) { + lex.cur().error("expected number after comparator"); + } + if (id1[1] == '=') { + eq = true; + if (id1.length() < 3) { + lex.cur().error("expected number after comparator"); + } + sem_ver[0] = stoi(id1.substr(2)); + } else { + sem_ver[0] = stoi(id1.substr(1)); + } + } else { + lex.cur().error("expected semver with optional comparator"); + } + lex.next(); + if (lex.tp() != ';') { + if (lex.tp() != _Ident || lex.cur().str[0] != '.') { + lex.cur().error("invalid semver format"); + } + sem_ver[1] = stoi(lex.cur().str.substr(1)); + segs = 2; + lex.next(); + } + if (lex.tp() != ';') { + if (lex.tp() != _Ident || lex.cur().str[0] != '.') { + lex.cur().error("invalid semver format"); + } + sem_ver[2] = stoi(lex.cur().str.substr(1)); + segs = 3; + lex.next(); + } + // End reading semver from source code + int func_ver[3] = {0, 0, 0}; + std::istringstream iss(func_ver_test); + std::string s; + for (int idx = 0; idx < 3; idx++) { + std::getline(iss, s, '.'); + func_ver[idx] = stoi(s); + } + // End parsing embedded semver + std::string semver_expr; + if (negate) { + semver_expr += '!'; + } + semver_expr += op; + if (eq) { + semver_expr += '='; + } + for (int idx = 0; idx < 3; idx++) { + semver_expr += std::to_string(sem_ver[idx]); + if (idx < 2) + semver_expr += '.'; + } + bool match = true; + switch (op) { + case '=': + if ((func_ver[0] != sem_ver[0]) || + (func_ver[1] != sem_ver[1]) || + (func_ver[2] != sem_ver[2])) { + match = false; + } + break; + case '>': + if ( ((func_ver[0] == sem_ver[0]) && (func_ver[1] == sem_ver[1]) && (func_ver[2] == sem_ver[2]) && !eq) || + ((func_ver[0] == sem_ver[0]) && (func_ver[1] == sem_ver[1]) && (func_ver[2] < sem_ver[2])) || + ((func_ver[0] == sem_ver[0]) && (func_ver[1] < sem_ver[1])) || + ((func_ver[0] < sem_ver[0])) ) { + match = false; + } + break; + case '<': + if ( ((func_ver[0] == sem_ver[0]) && (func_ver[1] == sem_ver[1]) && (func_ver[2] == sem_ver[2]) && !eq) || + ((func_ver[0] == sem_ver[0]) && (func_ver[1] == sem_ver[1]) && (func_ver[2] > sem_ver[2])) || + ((func_ver[0] == sem_ver[0]) && (func_ver[1] > sem_ver[1])) || + ((func_ver[0] > sem_ver[0])) ) { + match = false; + } + break; + case '^': + if ( ((segs == 3) && ((func_ver[0] != sem_ver[0]) || (func_ver[1] != sem_ver[1]) || (func_ver[2] < sem_ver[2]))) + || ((segs == 2) && ((func_ver[0] != sem_ver[0]) || (func_ver[1] < sem_ver[1]))) + || ((segs == 1) && ((func_ver[0] < sem_ver[0]))) ) { + match = false; + } + break; + } + if ((match && negate) || (!match && !negate)) { + pragma.error(std::string("FunC version ") + func_ver_test + " does not satisfy condition " + semver_expr); + } + } else if (!pragma_name.compare("test-version-set")) { + if (lex.tp() != _String) { + lex.cur().error("version string expected"); + } + func_ver_test = lex.cur().str; + lex.next(); + } else if (pragma_name == pragma_allow_post_modification.name()) { + pragma_allow_post_modification.enable(lex.cur().loc); + } else if (pragma_name == pragma_compute_asm_ltr.name()) { + pragma_compute_asm_ltr.enable(lex.cur().loc); + } else { + lex.cur().error(std::string{"unknown pragma `"} + pragma_name + "`"); + } + lex.expect(';'); +} + +std::vector source_fdescr; + +std::map source_files; +std::stack inclusion_locations; + +void parse_include(Lexer& lex, const src::FileDescr* fdescr) { + auto include = lex.cur(); + lex.expect(_IncludeHashtag); + if (lex.tp() != _String) { + lex.expect(_String, "source file name"); + } + std::string val = lex.cur().str; + std::string parent_dir = fdescr->filename; + if (parent_dir.rfind('/') != std::string::npos) { + val = parent_dir.substr(0, parent_dir.rfind('/') + 1) + val; + } + lex.next(); + lex.expect(';'); + if (!parse_source_file(val.c_str(), include, false)) { + include.error(std::string{"failed parsing included file `"} + val + "`"); + } +} + +bool parse_source(std::istream* is, src::FileDescr* fdescr) { src::SourceReader reader{is, fdescr}; - Lexer lex{reader, true}; + Lexer lex{reader, true, ";,()[] ~."}; while (lex.tp() != _Eof) { - if (lex.tp() == _Global) { + if (lex.tp() == _PragmaHashtag) { + parse_pragma(lex); + } else if (lex.tp() == _IncludeHashtag) { + parse_include(lex, fdescr); + } else if (lex.tp() == _Global) { parse_global_var_decls(lex); + } else if (lex.tp() == _Const) { + parse_const_decls(lex); } else { parse_func_def(lex); } @@ -1278,20 +1754,65 @@ bool parse_source(std::istream* is, const src::FileDescr* fdescr) { return true; } -bool parse_source_file(const char* filename) { +bool parse_source_file(const char* filename, src::Lexem lex, bool is_main) { if (!filename || !*filename) { - throw src::Fatal{"source file name is an empty string"}; + auto msg = "source file name is an empty string"; + if (lex.tp) { + lex.error(msg); + } else { + throw src::Fatal{msg}; + } } + + auto path_res = read_callback(ReadCallback::Kind::Realpath, filename); + if (path_res.is_error()) { + auto error = path_res.move_as_error(); + lex.error(error.message().c_str()); + return false; + } + std::string real_filename = path_res.move_as_ok(); + auto it = source_files.find(real_filename); + if (it != source_files.end()) { + it->second->is_main |= is_main; + if (verbosity >= 2) { + if (lex.tp) { + lex.loc.show_warning(std::string{"skipping file "} + real_filename + " because it was already included"); + } else { + std::cerr << "warning: skipping file " << real_filename << " because it was already included" << std::endl; + } + } + return true; + } + if (lex.tp) { // included + funC::generated_from += std::string{"incl:"}; + } + funC::generated_from += std::string{"`"} + filename + "` "; src::FileDescr* cur_source = new src::FileDescr{filename}; - std::ifstream ifs{filename}; - if (ifs.fail()) { - throw src::Fatal{std::string{"cannot open source file `"} + filename + "`"}; + source_files[real_filename] = cur_source; + cur_source->is_main = is_main; + source_fdescr.push_back(cur_source); + auto file_res = read_callback(ReadCallback::Kind::ReadFile, filename); + if (file_res.is_error()) { + auto msg = file_res.move_as_error().message().str(); + if (lex.tp) { + lex.error(msg); + } else { + throw src::Fatal{msg}; + } } - return parse_source(&ifs, cur_source); + auto file_str = file_res.move_as_ok(); + std::stringstream ss{file_str}; + inclusion_locations.push(lex.loc); + bool res = parse_source(&ss, cur_source); + inclusion_locations.pop(); + return res; } bool parse_source_stdin() { - return parse_source(&std::cin, new src::FileDescr{"stdin", true}); + src::FileDescr* cur_source = new src::FileDescr{"stdin", true}; + cur_source->is_main = true; + source_fdescr.push_back(cur_source); + return parse_source(&std::cin, cur_source); } } // namespace funC diff --git a/crypto/func/stack-transform.cpp b/crypto/func/stack-transform.cpp index 069324dc..b5290608 100644 --- a/crypto/func/stack-transform.cpp +++ b/crypto/func/stack-transform.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "func.h" @@ -183,7 +183,7 @@ bool StackTransform::is_permutation() const { if (!is_valid() || d) { return false; } - assert(n <= max_n); + func_assert(n <= max_n); std::array X, Y; for (int i = 0; i < n; i++) { X[i] = A[i].first; @@ -401,6 +401,57 @@ bool StackTransform::is_xchg(int *i, int *j) const { return true; } +bool StackTransform::is_xchg_xchg(int i, int j, int k, int l) const { + if (is_valid() && !d && n <= 4 && (i | j | k | l) >= 0) { + StackTransform t; + return t.apply_xchg(i, j) && t.apply_xchg(k, l) && t <= *this; + } else { + return false; + } +} + +bool StackTransform::is_xchg_xchg(int *i, int *j, int *k, int *l) const { + if (!is_valid() || d || n > 4 || !dp || !is_permutation()) { + return false; + } + if (!n) { + *i = *j = *k = *l = 0; + return true; + } + if (n <= 2) { + *k = *l = 0; + return is_xchg(i, j); + } + if (n == 3) { + // rotation: a -> b -> c -> a + int a = A[0].first; + int b = A[0].second; + int s = (b == A[2].first ? 2 : 1); + int c = A[s].second; + if (b != A[s].first || c != A[3 - s].first || a != A[3 - s].second) { + return false; + } + // implement as XCHG s(a),s(c) ; XCHG s(a),s(b) + *i = *k = a; + *j = c; + *l = b; + return is_xchg_xchg(*i, *j, *k, *l); + } + *i = A[0].first; + *j = A[0].second; + if (get(*j) != *i) { + return false; + } + for (int s = 1; s < 4; s++) { + if (A[s].first != *j) { + *k = A[s].first; + *l = A[s].second; + return get(*l) == *k && is_xchg_xchg(*i, *j, *k, *l); + } + } + return false; +} + bool StackTransform::is_push(int i) const { return is_valid() && d == -1 && n == 1 && A[0].first == -1 && A[0].second == i; } @@ -418,6 +469,7 @@ bool StackTransform::is_push(int *i) const { // 0 2 3 4 .. = pop1 // 1 0 3 4 .. = pop2 // 1 2 0 4 .. = pop3 +// POP s(i) : 1 2 ... i-1 0 i+1 ... ; d=1, n=1, {(i,0)} bool StackTransform::is_pop(int i) const { if (!is_valid() || d != 1 || n > 1 || i < 0) { return false; @@ -443,6 +495,38 @@ bool StackTransform::is_pop(int *i) const { return false; } +// POP s(i) ; POP s(j) : 2 ... i-1 0 i+1 ... j 1 j+2 ... ; d=2, n=2, {(i,0),(j+1,1)} if i <> j+1 +bool StackTransform::is_pop_pop(int i, int j) const { + if (is_valid() && d == 2 && n <= 2 && i >= 0 && j >= 0) { + StackTransform t; + return t.apply_pop(i) && t.apply_pop(j) && t <= *this; + } else { + return false; + } +} + +bool StackTransform::is_pop_pop(int *i, int *j) const { + if (!is_valid() || d != 2 || n > 2) { + return false; + } + if (!n) { + *i = *j = 0; // 2DROP + } else if (n == 2) { + *i = A[0].first - A[0].second; + *j = A[1].first - A[1].second; + if (A[0].second > A[1].second) { + std::swap(*i, *j); + } + } else if (!A[0].second) { + *i = A[0].first; + *j = 0; + } else { + *i = 0; + *j = A[0].first - 1; + } + return is_pop_pop(*i, *j); +} + const StackTransform StackTransform::rot{2, 0, 1, 3}; const StackTransform StackTransform::rot_rev{1, 2, 0, 3}; @@ -454,6 +538,53 @@ bool StackTransform::is_rotrev() const { return equal(rot_rev, true); } +// PUSH i ; ROT == 1 i 0 2 3 +bool StackTransform::is_push_rot(int i) const { + return is_valid() && d == -1 && i >= 0 && is_trivial_after(3) && get(0) == 1 && get(1) == i && get(2) == 0; +} + +bool StackTransform::is_push_rot(int *i) const { + return is_valid() && (*i = get(1)) >= 0 && is_push_rot(*i); +} + +// PUSH i ; -ROT == 0 1 i 2 3 +bool StackTransform::is_push_rotrev(int i) const { + return is_valid() && d == -1 && i >= 0 && is_trivial_after(3) && get(0) == 0 && get(1) == 1 && get(2) == i; +} + +bool StackTransform::is_push_rotrev(int *i) const { + return is_valid() && (*i = get(2)) >= 0 && is_push_rotrev(*i); +} + +// PUSH s(i) ; XCHG s(j),s(k) --> i 0 1 .. i .. +// PUSH s(i) ; XCHG s(0),s(k) --> k-1 0 1 .. k-2 i k .. +bool StackTransform::is_push_xchg(int i, int j, int k) const { + StackTransform t; + return is_valid() && d == -1 && n <= 3 && t.apply_push(i) && t.apply_xchg(j, k) && t <= *this; +} + +bool StackTransform::is_push_xchg(int *i, int *j, int *k) const { + if (!(is_valid() && d == -1 && n <= 3 && n > 0)) { + return false; + } + int s = get(0); + if (s < 0) { + return false; + } + *i = s; + *j = 0; + if (n == 1) { + *k = 0; + } else if (n == 2) { + *k = s + 1; + *i = get(s + 1); + } else { + *j = A[1].first + 1; + *k = A[2].first + 1; + } + return is_push_xchg(*i, *j, *k); +} + // XCHG s1,s(i) ; XCHG s0,s(j) bool StackTransform::is_xchg2(int i, int j) const { StackTransform t; @@ -472,10 +603,9 @@ bool StackTransform::is_xchg2(int *i, int *j) const { if (*i < 0 || *j < 0) { return false; } - if (n != 3) { - return is_xchg2(*i, *j); - } - if (*i) { + if (n == 2 && !*i) { + *j = *i; // XCHG s0,s1 = XCHG2 s0,s0 + } else if (n == 3 && *i) { // XCHG2 s(i),s(i) = XCHG s1,s(i) ; XCHG s0,s(i) : 0->1, 1->i *j = *i; } // XCHG2 s0,s(i) = XCHG s0,s1 ; XCHG s0,s(i) : 0->i, 1->0 @@ -742,6 +872,28 @@ bool StackTransform::is_blkdrop(int *i) const { return false; } +// 0 1 .. j-1 j+i j+i+1 ... +bool StackTransform::is_blkdrop2(int i, int j) const { + if (!is_valid() || d != i || i <= 0 || j < 0 || dp < i + j || n != j || !is_trivial_after(j)) { + return false; + } + for (int s = 0; s < j; s++) { + if (get(s) != s) { + return false; + } + } + return true; +} + +bool StackTransform::is_blkdrop2(int *i, int *j) const { + if (is_valid() && is_blkdrop2(d, n)) { + *i = d; + *j = n; + return true; + } + return false; +} + // equivalent to i times PUSH s(j) bool StackTransform::is_blkpush(int *i, int *j) const { if (!is_valid() || d >= 0) { @@ -846,8 +998,36 @@ bool StackTransform::is_2pop_blkdrop(int *i, int *j, int *k) const { } // PUSHCONST c ; ROT == 1 -1000 0 2 3 -bool StackTransform::is_const_rot() const { - return is_valid() && d == -1 && is_trivial_after(3) && get(0) == 1 && get(1) <= c_start && get(2) == 0; +bool StackTransform::is_const_rot(int c) const { + return is_valid() && d == -1 && is_trivial_after(3) && get(0) == 1 && c <= c_start && get(1) == c && get(2) == 0; +} + +bool StackTransform::is_const_rot(int *c) const { + return is_valid() && (*c = get(1)) <= c_start && is_const_rot(*c); +} + +// PUSHCONST c ; POP s(i) == 0 1 .. i-1 -1000 i+1 ... +bool StackTransform::is_const_pop(int c, int i) const { + return is_valid() && !d && n == 1 && i > 0 && c <= c_start && get(i - 1) == c; +} + +bool StackTransform::is_const_pop(int *c, int *i) const { + if (is_valid() && !d && n == 1 && A[0].second <= c_start) { + *i = A[0].first + 1; + *c = A[0].second; + return is_const_pop(*c, *i); + } else { + return false; + } +} + +// PUSH i ; PUSHCONST c == c i 0 1 2 ... +bool StackTransform::is_push_const(int i, int c) const { + return is_valid() && d == -2 && c <= c_start && i >= 0 && is_trivial_after(2) && get(0) == c && get(1) == i; +} + +bool StackTransform::is_push_const(int *i, int *c) const { + return is_valid() && d == -2 && n == 2 && is_push_const(*i = get(1), *c = get(0)); } void StackTransform::show(std::ostream &os, int mode) const { diff --git a/crypto/func/test/a12.fc b/crypto/func/test/a12.fc new file mode 100644 index 00000000..4f668ed3 --- /dev/null +++ b/crypto/func/test/a12.fc @@ -0,0 +1,4 @@ +_ f(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n, int o) { + return (a, i, o, j, e, f, g, h, k, l, m, d, b, c, n); + ;; optimal code is 6-byte: s11 s12 XCHG2 2 5 BLKSWAP s13 s13 s11 XCHG3 +} diff --git a/crypto/func/test/a12_1.fc b/crypto/func/test/a12_1.fc new file mode 100644 index 00000000..2f59e07e --- /dev/null +++ b/crypto/func/test/a12_1.fc @@ -0,0 +1,4 @@ +_ f(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) { + return (f, j, a, b, i, g, d, e, h, c); ;; optimal code is 3 ops, 6 bytes + ;; s6 s0 s4 XCHG3 s4 s8 s9 XCHG3 8 0 REVERSE +} diff --git a/crypto/func/test/a12_2.fc b/crypto/func/test/a12_2.fc new file mode 100644 index 00000000..7c60c6fd --- /dev/null +++ b/crypto/func/test/a12_2.fc @@ -0,0 +1,4 @@ +_ f(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) { + return (f, d, j, a, i, g, b, e, h, c); ;; optimal code is 4 ops, 6 bytes + ;; s1 s8 XCHG s1 s6 XCHG 7 3 BLKSWAP s9 s4 XCHG2 +} diff --git a/crypto/func/test/a12_3.fc b/crypto/func/test/a12_3.fc new file mode 100644 index 00000000..86b3409a --- /dev/null +++ b/crypto/func/test/a12_3.fc @@ -0,0 +1,4 @@ +_ f(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k) { + return (e, h, d, a, c, g, f, b, i, j, k); ;; optimal code is 4 ops, 8 bytes + ;; s9 s9 XCHG2 s6 s6 s8 XCHG3 s10 s7 s3 XCHG3 10 0 REVERSE +} diff --git a/crypto/func/test/a12_4.fc b/crypto/func/test/a12_4.fc new file mode 100644 index 00000000..a4132917 --- /dev/null +++ b/crypto/func/test/a12_4.fc @@ -0,0 +1,4 @@ +_ f(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k) { + return (b, a, c, e, g, d, f, k, i, j, h); ;; optimal code is 4 ops, 8 bytes + ;; s5 s0 s8 XCHG3 2 9 BLKSWAP s8 s9 s10 XCHG3 s7 s0 s5 XCHG3 +} diff --git a/crypto/func/test/a12_5.fc b/crypto/func/test/a12_5.fc new file mode 100644 index 00000000..277308a1 --- /dev/null +++ b/crypto/func/test/a12_5.fc @@ -0,0 +1,4 @@ +_ f(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k) { + return (c, g, d, k, a, f, e, h, i, j, b); ;; optimal code is 6 ops, 6 bytes + ;; s0 s7 XCHG s0 s8 XCHG s0 s10 XCHG s0 s6 XCHG s0 s4 XCHG s0 s9 XCHG +} diff --git a/crypto/func/test/a12_6.fc b/crypto/func/test/a12_6.fc new file mode 100644 index 00000000..01f7a586 --- /dev/null +++ b/crypto/func/test/a12_6.fc @@ -0,0 +1,4 @@ +_ f(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k) { + return (h, e, d, j, k, f, i, a, b, c, g); ;; optimal code is 3 ops, 6 bytes + ;; s3 s10 XCHG s6 s7 s6 XCHG3 s9 s8 s4 XCHG3 +} diff --git a/crypto/func/test/a12_7.fc b/crypto/func/test/a12_7.fc new file mode 100644 index 00000000..99c42b0c --- /dev/null +++ b/crypto/func/test/a12_7.fc @@ -0,0 +1,4 @@ +_ f(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n, int o, int p) { + return (m, f, l, j, b, e, a, d, p, k, c, n, g, i, h, o); ;; optimal code is 6 ops, 11 bytes + ;; s12 s0 s14 XCHG3 s0 s5 XCHG 6 8 BLKSWAP s3 s3 s9 XCHG3 s10 s14 s15 XCHG3 s13 s9 s9 XCHG3 +} diff --git a/crypto/func/test/a12_8.fc b/crypto/func/test/a12_8.fc new file mode 100644 index 00000000..054ae81d --- /dev/null +++ b/crypto/func/test/a12_8.fc @@ -0,0 +1,4 @@ +_ f(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n, int o, int p) { + return (l, i, o, h, a, m, j, d, p, g, n, b, k, e, c, f); ;; optimal code is 5 ops, 10 bytes + ;; 8 0 REVERSE s3 s9 s14 XCHG3 s15 s6 s4 XCHG3 s8 s12 XCHG s11 s13 s10 XCHG3 +} diff --git a/crypto/func/test/a12_9.fc b/crypto/func/test/a12_9.fc new file mode 100644 index 00000000..cd5789e2 --- /dev/null +++ b/crypto/func/test/a12_9.fc @@ -0,0 +1,4 @@ +_ f(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n, int o, int p) { + return (g, j, n, f, o, i, e, l, d, h, b, k, m, c, p, a); ;; optimal code is 5 ops, 10 bytes + ;; s7 s5 s14 XCHG3 s10 s4 s5 XCHG3 s12 s8 s11 XCHG3 s7 s6 s9 XCHG3 s13 s14 s15 XCHG3 +} diff --git a/crypto/func/test/co1.fc b/crypto/func/test/co1.fc new file mode 100644 index 00000000..82071e45 --- /dev/null +++ b/crypto/func/test/co1.fc @@ -0,0 +1,55 @@ +const int1 = 1, int2 = 2; + +const int int101 = 101; +const int int111 = 111; + +const int1r = int1; + +const str1 = "const1", str2 = "aabbcc"s; + +const slice str2r = str2; + +const str1int = 0x636f6e737431; +const str2int = 0xAABBCC; + +const int nibbles = 4; + +int iget1() { return int1; } +int iget2() { return int2; } +int iget3() { return int1 + int2; } + +int iget1r() { return int1r; } + +slice sget1() { return str1; } +slice sget2() { return str2; } +slice sget2r() { return str2r; } + +const int int240 = ((int1 + int2) * 10) << 3; + +int iget240() { return int240; } + +builder newc() asm "NEWC"; +slice endcs(builder b) asm "ENDC" "CTOS"; +int sdeq (slice s1, slice s2) asm "SDEQ"; +builder stslicer(builder b, slice s) asm "STSLICER"; + +_ main() { + int i1 = iget1(); + int i2 = iget2(); + int i3 = iget3(); + + throw_unless(int101, i1 == 1); + throw_unless(102, i2 == 2); + throw_unless(103, i3 == 3); + + slice s1 = sget1(); + slice s2 = sget2(); + slice s3 = newc().stslicer(str1).stslicer(str2r).endcs(); + + throw_unless(int111, sdeq(s1, newc().store_uint(str1int, 12 * nibbles).endcs())); + throw_unless(112, sdeq(s2, newc().store_uint(str2int, 6 * nibbles).endcs())); + throw_unless(113, sdeq(s3, newc().store_uint(0x636f6e737431ABCDEF, 18 * nibbles).endcs())); + + int i4 = iget240(); + throw_unless(104, i4 == 240); +} diff --git a/crypto/func/test/co2.fc b/crypto/func/test/co2.fc new file mode 100644 index 00000000..f5fcb748 --- /dev/null +++ b/crypto/func/test/co2.fc @@ -0,0 +1,15 @@ +const int x = 5; +const slice s = "abacaba"; +const int y = 3; +const slice s = "abacaba"; +const int x = 5; +const int z = 4, z = 4; + +int sdeq (slice s1, slice s2) asm "SDEQ"; + +() main() { + throw_unless(101, x == 5); + throw_unless(102, y == 3); + throw_unless(103, z == 4); + throw_unless(104, sdeq(s, "abacaba")); +} diff --git a/crypto/func/test/co3.fc b/crypto/func/test/co3.fc new file mode 100644 index 00000000..398592d3 --- /dev/null +++ b/crypto/func/test/co3.fc @@ -0,0 +1,24 @@ +const val1 = 123456789; +const val2 = 987654321; +const val3 = 135792468; +const val4 = 246813579; + +const prec_and = val1 & val2; +const prec_or = val1 | val2; +const prec_xor = val1 ^ val2; +const prec_logic = ((val1 & val2) | val3) ^ val4; +const prec_nand = val1 & (~ val2); + +int get_and() { return prec_and; } +int get_or() { return prec_or; } +int get_xor() { return prec_xor; } +int get_logic() { return prec_logic; } +int get_nand() { return prec_nand; } + +_ main() { + throw_unless(101, get_and() == 39471121); + throw_unless(102, get_or() == 1071639989); + throw_unless(103, get_xor() == 1032168868); + throw_unless(104, get_logic() == 82599134); + throw_unless(105, get_nand() == 83985668); +} diff --git a/crypto/func/test/i1.fc b/crypto/func/test/i1.fc new file mode 100644 index 00000000..04f7889e --- /dev/null +++ b/crypto/func/test/i1.fc @@ -0,0 +1,16 @@ +global int i; + +#include "i1sub1.fc"; + +() sub0() impure { i = 0; } + +#include "i1sub2.fc"; + +() main() impure { + sub0(); + sub1(); + sub2(); + i = 9; +} + +#include "../test/i1sub2.fc"; \ No newline at end of file diff --git a/crypto/func/test/i1sub1.fc b/crypto/func/test/i1sub1.fc new file mode 100644 index 00000000..62bdd177 --- /dev/null +++ b/crypto/func/test/i1sub1.fc @@ -0,0 +1,8 @@ +;; DO NOT COMPILE DIRECTLY! +;; Compile i1.fc + +#include "i1sub1.fc"; + +() sub1() impure { + i = 1; +} diff --git a/crypto/func/test/i1sub2.fc b/crypto/func/test/i1sub2.fc new file mode 100644 index 00000000..0c48e192 --- /dev/null +++ b/crypto/func/test/i1sub2.fc @@ -0,0 +1,10 @@ +;; DO NOT COMPILE DIRECTLY! +;; Compile i1.fc + +#include "./i1sub1.fc"; + +() sub2() impure { + sub1(); + sub0(); + i = 2; +} diff --git a/crypto/func/test/pv.fc b/crypto/func/test/pv.fc new file mode 100644 index 00000000..d9bcd570 --- /dev/null +++ b/crypto/func/test/pv.fc @@ -0,0 +1,53 @@ +#pragma test-version-set "1.2.3"; + +;; Positive tests +#pragma version ^1.2.0; +#pragma version ^1.2.3; +#pragma version >1.2.0; +#pragma version >0.9.9; +#pragma version <1.3.0; +#pragma version <2.0.0; +#pragma version >=1.2.0; +#pragma version <=1.3.0; +#pragma version >=1.2.3; +#pragma version <=1.2.3; +#pragma version ^1.2.3; +#pragma version 1.2.3; +#pragma version =1.2.3; + +;; Negative tests +#pragma not-version ^1.1.0; +#pragma not-version ^1.0.0; +#pragma not-version ^0.2.3; +#pragma not-version ^2.2.3; +#pragma not-version ^1.3.3; +#pragma not-version >1.2.3; +#pragma not-version <1.2.3; +#pragma not-version ^1.2.4; +#pragma not-version >=1.2.4; +#pragma not-version <=1.2.2; +#pragma not-version 3.2.1; +#pragma not-version =3.2.1; + +;; Test incomplete (partial) version +#pragma version ^1.2; +#pragma version >1.2; +#pragma version <1.3; +#pragma version <2; +#pragma version >=1.2; +#pragma version <=1.3; + +;; Advanced ^ behaviour (partials) +#pragma version ^1.2; +#pragma version ^1.0; +#pragma version ^1; +#pragma version ^0; +#pragma not-version ^1.0.0; +#pragma not-version ^0.0.0; +#pragma not-version ^0.0; +#pragma not-version ^1.3; +#pragma not-version ^2; + +(int) main(int a) { + return a; +} diff --git a/crypto/func/test/s1.fc b/crypto/func/test/s1.fc new file mode 100644 index 00000000..630d0180 --- /dev/null +++ b/crypto/func/test/s1.fc @@ -0,0 +1,49 @@ +slice ascii_slice() method_id { + return "string"; +} + +slice raw_slice() method_id { + return "abcdef"s; +} + +slice addr_slice() method_id { + return "Ef8zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM0vF"a; +} + +int string_hex() method_id { + return "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"u; +} + +int string_minihash() method_id { + return "transfer(slice, int)"h; +} + +int string_maxihash() method_id { + return "transfer(slice, int)"H; +} + +int string_crc32() method_id { + return "transfer(slice, int)"c; +} + +builder newc() asm "NEWC"; +slice endcs(builder b) asm "ENDC" "CTOS"; +int sdeq (slice s1, slice s2) asm "SDEQ"; + +_ main() { + slice s_ascii = ascii_slice(); + slice s_raw = raw_slice(); + slice s_addr = addr_slice(); + int i_hex = string_hex(); + int i_mini = string_minihash(); + int i_maxi = string_maxihash(); + int i_crc = string_crc32(); + throw_unless(101, sdeq(s_ascii, newc().store_uint(0x737472696E67, 12 * 4).endcs())); + throw_unless(102, sdeq(s_raw, newc().store_uint(0xABCDEF, 6 * 4).endcs())); + throw_unless(103, sdeq(s_addr, newc().store_uint(4, 3).store_int(-1, 8) + .store_uint(0x3333333333333333333333333333333333333333333333333333333333333333, 256).endcs())); + throw_unless(104, i_hex == 0x4142434445464748494A4B4C4D4E4F505152535455565758595A303132333435); + throw_unless(105, i_mini == 0x7a62e8a8); + throw_unless(106, i_maxi == 0x7a62e8a8ebac41bd6de16c65e7be363bc2d2cbc6a0873778dead4795c13db979); + throw_unless(107, i_crc == 2235694568); +} diff --git a/crypto/func/test/s2.fc b/crypto/func/test/s2.fc new file mode 100644 index 00000000..c6df49d5 --- /dev/null +++ b/crypto/func/test/s2.fc @@ -0,0 +1,26 @@ +slice test1() asm """ + "Test" $>s + PUSHSLICE +"""; + +slice test2() asm """ + "Hello" + " " + "World" + $+ $+ $>s + PUSHSLICE +"""; + +int sdeq (slice s1, slice s2) asm """SDEQ"""; +int sdeq (slice s1, slice s2) asm "SDEQ" ""; +int sdeq (slice s1, slice s2) asm "" """ +SDEQ +"""; + +() main() { + slice s = test1(); + throw_unless(101, sdeq(s, "Test")); + + slice s = test2(); + throw_unless(102, sdeq(s, "Hello World")); +} diff --git a/crypto/func/test/tc1.fc b/crypto/func/test/tc1.fc new file mode 100644 index 00000000..245fc521 --- /dev/null +++ b/crypto/func/test/tc1.fc @@ -0,0 +1,113 @@ +() test1() impure { + int i = 3; + repeat (3) { + try { + int j = i; + i *= 2; + throw_unless(500, j <= 10); + } catch (x, e) { + i -= 2; + } + i += i + 1; + } + throw_unless(501, i == 43); +} + +int divide_by_ten(int num) { + try { + throw_unless(500, num < 10); + } catch (x, e) { + return divide_by_ten(num - 10) + 1; + } + return 0; +} + +() test2() impure { + int n = divide_by_ten(37); + throw_unless(502, n == 3); +} + +(int, int) swap_int(int a, int b) { + try { + a = a * b; + b = a / b; + a = a / b; + return (a, b); + } catch (x, e) { + throw_unless(500, b == 0); + } + return (0, a); +} + +() test3() impure { + int a = 0; + int b = 57; + try { + (a, b) = swap_int(a, b); + } catch (x, e) { + throw_unless(500, a == 0); + a = b; + b = 0; + } + throw_unless(503, (a == 57) & (b == 0)); +} + +int get_x(int x, int y) { + try { + } catch (x, e) { + return -1; + } + return x; +} + +int get_y(int x, int y) { + try { + return -1; + } catch (x, e) { + } + return y; +} + +() test4() impure { + throw_unless(504, get_x(3, 4) == 3); + throw_unless(504, get_y(3, 4) == -1); +} + +(int, int, int, int, int) foo(int a, int b, int c, int d, int e) { + try { + throw(11); + } catch (x, y) { + a += 1; + b += 2; + c += 3; + d += 4; + e += 5; + } + return (a, b, c, d, e); +} + +() test5() impure { + var (a, b, c, d, e) = foo(10, 20, 30, 40, 50); + throw_unless(505, (a == 11) & (b == 22) & (c == 33) & (d == 44) & (e == 55)); +} + +() test6() impure { + int a = 0; + int b = 0; + int c = 0; + try { + b = 3; + } catch (x, y) { + b = 12; + } + throw_unless(506, (a == 0) & (b == 3) & (c == 0)); +} + +() main() { + test1(); + test2(); + test3(); + test4(); + test5(); + test6(); +} diff --git a/crypto/func/test/tc2.fc b/crypto/func/test/tc2.fc new file mode 100644 index 00000000..2bde6813 --- /dev/null +++ b/crypto/func/test/tc2.fc @@ -0,0 +1,84 @@ +forall X -> int cast_to_int(X x) asm "NOP"; +forall X -> builder cast_to_builder(X x) asm "NOP"; + +_ test1_body() { + int a = 3; + builder b = begin_cell(); + int c = 1; + try { + c = 3; + throw_arg(b, 100); + } catch (x, y) { + return (a + c + y, cast_to_builder(x)); + } + return (0, null()); +} + +() test1() impure { + var (x, y) = test1_body(); + throw_unless(101, x == 104); + throw_unless(102, y.builder_refs() == y.builder_bits()); +} + +_ test2_body(int a, int b, int c) { + try { + try { + try { + try { + throw_arg_if(1, 201, a + b + c == 3); + throw_arg_if(2, 201, a == 3); + throw_arg_unless(1, 202, b == 4); + return 1; + } catch (y, x) { + int y = y.cast_to_int(); + throw_arg_unless(y, x, x == 202); + throw_arg(y + 1, 200); + } + } catch (y, x) { + int y = y.cast_to_int(); + throw_arg_if(y, x, x == 200); + throw_arg_if(y + 2, x, y < 2); + throw_arg_if(y + 3, 203, a + b + c == 4); + throw_arg_unless(y + 4, 204, b == 4); + return 3; + } + } catch (y, x) { + int y = y.cast_to_int(); + try { + throw_arg_if(y, x, x == 200); + throw_arg_if(y + 1, 200, x == 201); + throw_arg_if(x - 203, 200, x == 202); + throw_arg_if(y, 200, x == 203); + throw_arg_if(a + 4, 205, a + b + c == 5); + throw_arg(7, 200); + } catch (v, u) { + int v = v.cast_to_int(); + throw_arg_unless(v, u, u == 205); + if (c == 0) { + return b + 4; + } + throw_arg(v + 1, 200); + } + } + } catch (y, x) { + throw_unless(x, x == 200); + return y.cast_to_int(); + } + return null(); +} + +() test2() impure { + throw_unless(201, test2_body(0, 4, 0) == 1); + throw_unless(202, test2_body(0, 5, 0) == 2); + throw_unless(203, test2_body(3, 4, 0) == 3); + throw_unless(204, test2_body(3, 0, 0) == 4); + throw_unless(205, test2_body(3, 1, 0) == 5); + throw_unless(206, test2_body(3, 2, 0) == 6); + throw_unless(207, test2_body(3, 1, 2) == 7); + throw_unless(208, test2_body(3, 1, 1) == 8); +} + +() main() { + test1(); + test2(); +} diff --git a/crypto/func/unify-types.cpp b/crypto/func/unify-types.cpp index 698d21fe..f9b639c0 100644 --- a/crypto/func/unify-types.cpp +++ b/crypto/func/unify-types.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "func.h" @@ -47,6 +47,12 @@ void TypeExpr::compute_width() { maxw = w_inf; } break; + case te_Tuple: + minw = maxw = 1; + for (TypeExpr* arg : args) { + arg->compute_width(); + } + break; case te_Indirect: minw = args[0]->minw; maxw = args[0]->maxw; @@ -84,6 +90,14 @@ bool TypeExpr::recompute_width() { } return true; } + case te_Tuple: { + for (TypeExpr* arg : args) { + if (arg->minw > 1 || arg->maxw < 1 || arg->minw > arg->maxw) { + return false; + } + } + return true; + } default: return false; } @@ -118,7 +132,7 @@ void TypeExpr::replace_with(TypeExpr* te2) { } bool TypeExpr::remove_indirect(TypeExpr*& te, TypeExpr* forbidden) { - assert(te); + func_assert(te); while (te->constr == te_Indirect) { te = te->args[0]; } @@ -132,12 +146,9 @@ bool TypeExpr::remove_indirect(TypeExpr*& te, TypeExpr* forbidden) { return res; } -bool TypeExpr::remove_forall(TypeExpr*& te) { - assert(te); - if (te->constr != te_ForAll) { - return false; - } - assert(te->args.size() >= 1); +std::vector TypeExpr::remove_forall(TypeExpr*& te) { + func_assert(te && te->constr == te_ForAll); + func_assert(te->args.size() >= 1); std::vector new_vars; for (std::size_t i = 1; i < te->args.size(); i++) { new_vars.push_back(new_hole(1)); @@ -147,12 +158,12 @@ bool TypeExpr::remove_forall(TypeExpr*& te) { te = te->args[0]; remove_forall_in(te, te2, new_vars); // std::cerr << "-> " << te << std::endl; - return true; + return new_vars; } bool TypeExpr::remove_forall_in(TypeExpr*& te, TypeExpr* te2, const std::vector& new_vars) { - assert(te); - assert(te2 && te2->constr == te_ForAll); + func_assert(te); + func_assert(te2 && te2->constr == te_ForAll); if (te->constr == te_Var) { for (std::size_t i = 0; i < new_vars.size(); i++) { if (te == te2->args[i + 1]) { @@ -233,7 +244,9 @@ std::ostream& TypeExpr::print(std::ostream& os, int lex_level) { } } case te_Tensor: { - os << "("; + if (lex_level > -127) { + os << "("; + } auto c = args.size(); if (c) { for (const auto& x : args) { @@ -243,10 +256,28 @@ std::ostream& TypeExpr::print(std::ostream& os, int lex_level) { } } } - return os << ")"; + if (lex_level > -127) { + os << ")"; + } + return os; + } + case te_Tuple: { + os << "["; + auto c = args.size(); + if (c == 1 && args[0]->constr == te_Tensor) { + args[0]->print(os, -127); + } else if (c) { + for (const auto& x : args) { + x->print(os); + if (--c) { + os << ", "; + } + } + } + return os << "]"; } case te_Map: { - assert(args.size() == 2); + func_assert(args.size() == 2); if (lex_level > 0) { os << "("; } @@ -259,7 +290,7 @@ std::ostream& TypeExpr::print(std::ostream& os, int lex_level) { return os; } case te_ForAll: { - assert(args.size() >= 1); + func_assert(args.size() >= 1); if (lex_level > 0) { os << '('; } @@ -300,7 +331,7 @@ std::string UnifyError::message() const { void check_width_compat(TypeExpr* te1, TypeExpr* te2) { if (te1->minw > te2->maxw || te2->minw > te1->maxw) { - std::ostringstream os{"cannot unify types of widths "}; + std::ostringstream os{"cannot unify types of widths ", std::ios_base::ate}; te1->show_width(os); os << " and "; te2->show_width(os); @@ -312,11 +343,11 @@ void check_update_widths(TypeExpr* te1, TypeExpr* te2) { check_width_compat(te1, te2); te1->minw = te2->minw = std::max(te1->minw, te2->minw); te1->maxw = te2->maxw = std::min(te1->maxw, te2->maxw); - assert(te1->minw <= te2->minw); + func_assert(te1->minw <= te1->maxw); } void unify(TypeExpr*& te1, TypeExpr*& te2) { - assert(te1 && te2); + func_assert(te1 && te2); // std::cerr << "unify( " << te1 << " , " << te2 << " )\n"; while (te1->constr == TypeExpr::te_Indirect) { te1 = te1->args[0]; @@ -329,23 +360,37 @@ void unify(TypeExpr*& te1, TypeExpr*& te2) { } if (te1->constr == TypeExpr::te_ForAll) { TypeExpr* te = te1; - if (!TypeExpr::remove_forall(te)) { - throw UnifyError{te1, te2, "cannot remove universal type quantifier while performing type unification"}; + std::vector new_vars = TypeExpr::remove_forall(te); + for (TypeExpr* t : new_vars) { + t->was_forall_var = true; } unify(te, te2); + for (TypeExpr* t : new_vars) { + t->was_forall_var = false; + } return; } if (te2->constr == TypeExpr::te_ForAll) { TypeExpr* te = te2; - if (!TypeExpr::remove_forall(te)) { - throw UnifyError{te2, te1, "cannot remove universal type quantifier while performing type unification"}; + std::vector new_vars = TypeExpr::remove_forall(te); + for (TypeExpr* t : new_vars) { + t->was_forall_var = true; } unify(te1, te); + for (TypeExpr* t : new_vars) { + t->was_forall_var = false; + } return; } + if (te1->was_forall_var && te2->constr == TypeExpr::te_Tensor) { + throw UnifyError{te1, te2, "cannot unify generic type and tensor"}; + } + if (te2->was_forall_var && te1->constr == TypeExpr::te_Tensor) { + throw UnifyError{te2, te1, "cannot unify generic type and tensor"}; + } if (te1->constr == TypeExpr::te_Unknown) { if (te2->constr == TypeExpr::te_Unknown) { - assert(te1->value != te2->value); + func_assert(te1->value != te2->value); } if (!TypeExpr::remove_indirect(te2, te1)) { throw UnifyError{te1, te2, "type unification results in an infinite cyclic type"}; diff --git a/crypto/funcfiftlib/funcfiftlib-prejs.js b/crypto/funcfiftlib/funcfiftlib-prejs.js new file mode 100644 index 00000000..38326c38 --- /dev/null +++ b/crypto/funcfiftlib/funcfiftlib-prejs.js @@ -0,0 +1 @@ +var crypto = { getRandomValues: function(array) { for (var i = 0; i < array.length; i++) array[i] = (Math.random()*256)|0 } }; \ No newline at end of file diff --git a/crypto/funcfiftlib/funcfiftlib.cpp b/crypto/funcfiftlib/funcfiftlib.cpp new file mode 100644 index 00000000..a041c25d --- /dev/null +++ b/crypto/funcfiftlib/funcfiftlib.cpp @@ -0,0 +1,150 @@ +/* + This file is part of TON Blockchain source code. + + TON Blockchain is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + TON Blockchain 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TON Blockchain. If not, see . + + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement + from all source files in the program, then also delete it here. + + Copyright 2017-2020 Telegram Systems LLP +*/ +#include "func/func.h" +#include "git.h" +#include "td/utils/JsonBuilder.h" +#include "fift/utils.h" +#include "td/utils/base64.h" +#include "td/utils/Status.h" +#include +#include + +td::Result compile_internal(char *config_json) { + TRY_RESULT(input_json, td::json_decode(td::MutableSlice(config_json))) + auto &obj = input_json.get_object(); + + TRY_RESULT(opt_level, td::get_json_object_int_field(obj, "optLevel", false)); + TRY_RESULT(sources_obj, td::get_json_object_field(obj, "sources", td::JsonValue::Type::Array, false)); + + auto &sources_arr = sources_obj.get_array(); + + std::vector sources; + + for (auto &item : sources_arr) { + sources.push_back(item.get_string().str()); + } + + funC::opt_level = std::max(0, opt_level); + funC::program_envelope = true; + funC::verbosity = 0; + funC::indent = 1; + + std::ostringstream outs, errs; + auto compile_res = funC::func_proceed(sources, outs, errs); + + if (compile_res != 0) { + return td::Status::Error(std::string("Func compilation error: ") + errs.str()); + } + + TRY_RESULT(code_cell, fift::compile_asm(outs.str(), "/fiftlib/", false)); + TRY_RESULT(boc, vm::std_boc_serialize(code_cell)); + + td::JsonBuilder result_json; + auto result_obj = result_json.enter_object(); + result_obj("status", "ok"); + result_obj("codeBoc", td::base64_encode(boc)); + result_obj("fiftCode", outs.str()); + result_obj("codeHashHex", code_cell->get_hash().to_hex()); + result_obj.leave(); + + outs.clear(); + errs.clear(); + + return result_json.string_builder().as_cslice().str(); +} + +/// Callback used to retrieve additional source files or data. +/// +/// @param _kind The kind of callback (a string). +/// @param _data The data for the callback (a string). +/// @param o_contents A pointer to the contents of the file, if found. Allocated via malloc(). +/// @param o_error A pointer to an error message, if there is one. Allocated via malloc(). +/// +/// The callback implementor must use malloc() to allocate storage for +/// contents or error. The callback implementor must use free() to free +/// said storage after func_compile returns. +/// +/// If the callback is not supported, *o_contents and *o_error must be set to NULL. +typedef void (*CStyleReadFileCallback)(char const* _kind, char const* _data, char** o_contents, char** o_error); + +funC::ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback) +{ + funC::ReadCallback::Callback readCallback; + if (_readCallback) { + readCallback = [=](funC::ReadCallback::Kind _kind, char const* _data) -> td::Result { + char* contents_c = nullptr; + char* error_c = nullptr; + _readCallback(funC::ReadCallback::kindString(_kind).data(), _data, &contents_c, &error_c); + if (!contents_c && !error_c) { + return td::Status::Error("Callback not supported"); + } + if (contents_c) { + return contents_c; + } + return td::Status::Error(std::string(error_c)); + }; + } + return readCallback; +} + +extern "C" { + +const char* version() { + auto version_json = td::JsonBuilder(); + auto obj = version_json.enter_object(); + obj("funcVersion", funC::func_version); + obj("funcFiftLibCommitHash", GitMetadata::CommitSHA1()); + obj("funcFiftLibCommitDate", GitMetadata::CommitDate()); + obj.leave(); + return strdup(version_json.string_builder().as_cslice().c_str()); +} + +const char *func_compile(char *config_json, CStyleReadFileCallback callback) { + if (callback) { + funC::read_callback = wrapReadCallback(callback); + } else { + funC::read_callback = funC::fs_read_callback; + } + + auto res = compile_internal(config_json); + + if (res.is_error()) { + auto result = res.move_as_error(); + auto error_res = td::JsonBuilder(); + auto error_o = error_res.enter_object(); + error_o("status", "error"); + error_o("message", result.message().str()); + error_o.leave(); + return strdup(error_res.string_builder().as_cslice().c_str()); + } + + auto res_string = res.move_as_ok(); + + return strdup(res_string.c_str()); +} +} diff --git a/crypto/keccak/keccak.cpp b/crypto/keccak/keccak.cpp new file mode 100644 index 00000000..aac48ddc --- /dev/null +++ b/crypto/keccak/keccak.cpp @@ -0,0 +1,473 @@ +/* + * An implementation of the SHA3 (Keccak) hash function family. + * + * Algorithm specifications: http://keccak.noekeon.org/ + * NIST Announcement: + * http://csrc.nist.gov/groups/ST/hash/sha-3/winner_sha-3.html + * + * Written in 2013 by Fabrizio Tarizzo + * + * =================================================================== + * The contents of this file are dedicated to the public domain. To + * the extent that dedication to the public domain is not available, + * everyone is granted a worldwide, perpetual, royalty-free, + * non-exclusive license to exercise all rights associated with the + * contents of this file for any purpose whatsoever. + * No rights are reserved. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * =================================================================== +*/ + +#include +#include +#include +#include + +#define KECCAK_F1600_STATE 200 +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#define MAX(x, y) ((x) < (y) ? (x) : (y)) + +/** Standard errors common to all ciphers **/ +#define ERR_NULL 1 +#define ERR_MEMORY 2 +#define ERR_NOT_ENOUGH_DATA 3 +#define ERR_ENCRYPT 4 +#define ERR_DECRYPT 5 +#define ERR_KEY_SIZE 6 +#define ERR_NONCE_SIZE 7 +#define ERR_NR_ROUNDS 8 +#define ERR_DIGEST_SIZE 9 +#define ERR_MAX_DATA 10 +#define ERR_MAX_OFFSET 11 +#define ERR_BLOCK_SIZE 12 +#define ERR_TAG_SIZE 13 +#define ERR_VALUE 14 +#define ERR_EC_POINT 15 +#define ERR_EC_CURVE 16 +#define ERR_MODULUS 17 +#define ERR_UNKNOWN 32 + +typedef struct +{ + uint64_t state[25]; + + /* The buffer is as long as the state, + * but only 'rate' bytes will be used. + */ + uint8_t buf[KECCAK_F1600_STATE]; + + /* When absorbing, this is the number of bytes in buf that + * are coming from the message and outstanding. + * When squeezing, this is the remaining number of bytes + * that can be used as digest. + */ + unsigned valid_bytes; + + /* All values in bytes */ + unsigned capacity; + unsigned rate; + + uint8_t squeezing; + uint8_t rounds; +} keccak_state; + +#undef ROL64 +#define ROL64(x,y) ((((x) << (y)) | (x) >> (64-(y))) & 0xFFFFFFFFFFFFFFFFULL) + +static void keccak_function (uint64_t *state, unsigned rounds); + +int keccak_reset(keccak_state *state) +{ + if (NULL == state) + return ERR_NULL; + + memset(state->state, 0, sizeof(state->state)); + memset(state->buf, 0, sizeof(state->buf)); + state->valid_bytes = 0; + state->squeezing = 0; + + return 0; +} + +static void keccak_absorb_internal (keccak_state *self) +{ + unsigned i,j; + uint64_t d; + + for (i=j=0; j < self->rate; ++i, j += 8) { + d = *(const uint64_t*)(self->buf + j); + self->state[i] ^= d; + } +} + +static void +keccak_squeeze_internal (keccak_state *self) +{ + unsigned i, j; + + for (i=j=0; j < self->rate; ++i, j += 8) { + *(uint64_t*)(self->buf+j) = self->state[i]; + } +} + +int keccak_init (keccak_state **state, + size_t capacity_bytes, + uint8_t rounds) +{ + keccak_state *ks; + + if (NULL == state) { + return ERR_NULL; + } + + *state = ks = (keccak_state*) calloc(1, sizeof(keccak_state)); + if (NULL == ks) + return ERR_MEMORY; + + if (capacity_bytes >= KECCAK_F1600_STATE) + return ERR_DIGEST_SIZE; + + if ((rounds != 12) && (rounds != 24)) + return ERR_NR_ROUNDS; + + ks->capacity = (unsigned)capacity_bytes; + + ks->rate = KECCAK_F1600_STATE - ks->capacity; + + ks->squeezing = 0; + ks->rounds = rounds; + + return 0; +} + +int keccak_destroy(keccak_state *state) +{ + free(state); + return 0; +} + +int keccak_absorb (keccak_state *self, + const uint8_t *in, + size_t length) +{ + if (NULL==self || NULL==in) + return ERR_NULL; + + if (self->squeezing != 0) + return ERR_UNKNOWN; + + while (length > 0) { + unsigned tc; + unsigned left; + + left = self->rate - self->valid_bytes; + tc = (unsigned) MIN(length, left); + memcpy(self->buf + self->valid_bytes, in, tc); + + self->valid_bytes += tc; + in += tc; + length -= tc; + + if (self->valid_bytes == self->rate) { + keccak_absorb_internal (self); + keccak_function(self->state, self->rounds); + self->valid_bytes = 0; + } + } + + return 0; +} + +static void keccak_finish (keccak_state *self, uint8_t padding) +{ + assert(self->squeezing == 0); + assert(self->valid_bytes < self->rate); + + /* Padding */ + memset(self->buf + self->valid_bytes, 0, self->rate - self->valid_bytes); + self->buf[self->valid_bytes] = padding; + self->buf[self->rate-1] |= 0x80; + + /* Final absorb */ + keccak_absorb_internal (self); + keccak_function (self->state, self->rounds); + + /* First squeeze */ + self->squeezing = 1; + keccak_squeeze_internal (self); + self->valid_bytes = self->rate; +} + +int keccak_squeeze (keccak_state *self, uint8_t *out, size_t length, uint8_t padding) +{ + if ((NULL == self) || (NULL == out)) + return ERR_NULL; + + if (self->squeezing == 0) { + keccak_finish (self, padding); + } + + assert(self->squeezing == 1); + assert(self->valid_bytes > 0); + assert(self->valid_bytes <= self->rate); + + while (length > 0) { + unsigned tc; + + tc = (unsigned)MIN(self->valid_bytes, length); + memcpy(out, self->buf + (self->rate - self->valid_bytes), tc); + + self->valid_bytes -= tc; + out += tc; + length -= tc; + + if (self->valid_bytes == 0) { + keccak_function (self->state, self->rounds); + keccak_squeeze_internal (self); + self->valid_bytes = self->rate; + } + } + + return 0; +} + +int keccak_digest(keccak_state *state, uint8_t *digest, size_t len, uint8_t padding) +{ + keccak_state tmp; + + if ((NULL==state) || (NULL==digest)) + return ERR_NULL; + + if (2*len != state->capacity) + return ERR_UNKNOWN; + + tmp = *state; + return keccak_squeeze(&tmp, digest, len, padding); +} + +int keccak_copy(const keccak_state *src, keccak_state *dst) +{ + if (NULL == src || NULL == dst) { + return ERR_NULL; + } + + *dst = *src; + return 0; +} + +/* Keccak core function */ + +#define KECCAK_ROUNDS 24 + +#define ROT_01 36 +#define ROT_02 3 +#define ROT_03 41 +#define ROT_04 18 +#define ROT_05 1 +#define ROT_06 44 +#define ROT_07 10 +#define ROT_08 45 +#define ROT_09 2 +#define ROT_10 62 +#define ROT_11 6 +#define ROT_12 43 +#define ROT_13 15 +#define ROT_14 61 +#define ROT_15 28 +#define ROT_16 55 +#define ROT_17 25 +#define ROT_18 21 +#define ROT_19 56 +#define ROT_20 27 +#define ROT_21 20 +#define ROT_22 39 +#define ROT_23 8 +#define ROT_24 14 + +static const uint64_t roundconstants[KECCAK_ROUNDS] = { + 0x0000000000000001ULL, + 0x0000000000008082ULL, + 0x800000000000808aULL, + 0x8000000080008000ULL, + 0x000000000000808bULL, + 0x0000000080000001ULL, + 0x8000000080008081ULL, + 0x8000000000008009ULL, + 0x000000000000008aULL, + 0x0000000000000088ULL, + 0x0000000080008009ULL, + 0x000000008000000aULL, + 0x000000008000808bULL, + 0x800000000000008bULL, + 0x8000000000008089ULL, + 0x8000000000008003ULL, + 0x8000000000008002ULL, + 0x8000000000000080ULL, + 0x000000000000800aULL, + 0x800000008000000aULL, + 0x8000000080008081ULL, + 0x8000000000008080ULL, + 0x0000000080000001ULL, + 0x8000000080008008ULL +}; + +static void keccak_function (uint64_t *state, unsigned rounds) +{ + unsigned i; + unsigned start_round; + + /* Temporary variables to avoid indexing overhead */ + uint64_t a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12; + uint64_t a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24; + + uint64_t b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12; + uint64_t b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24; + + uint64_t c0, c1, c2, c3, c4, d; + + a0 = state[0]; + a1 = state[1]; + a2 = state[2]; + a3 = state[3]; + a4 = state[4]; + a5 = state[5]; + a6 = state[6]; + a7 = state[7]; + a8 = state[8]; + a9 = state[9]; + a10 = state[10]; + a11 = state[11]; + a12 = state[12]; + a13 = state[13]; + a14 = state[14]; + a15 = state[15]; + a16 = state[16]; + a17 = state[17]; + a18 = state[18]; + a19 = state[19]; + a20 = state[20]; + a21 = state[21]; + a22 = state[22]; + a23 = state[23]; + a24 = state[24]; + + if (rounds == 24) + start_round = 0; + else /* rounds == 12 */ + start_round = 12; + + for (i = start_round; i < KECCAK_ROUNDS; ++i) { + /* + Uses temporary variables and loop unrolling to + avoid array indexing and inner loops overhead + */ + + /* Prepare column parity for Theta step */ + c0 = a0 ^ a5 ^ a10 ^ a15 ^ a20; + c1 = a1 ^ a6 ^ a11 ^ a16 ^ a21; + c2 = a2 ^ a7 ^ a12 ^ a17 ^ a22; + c3 = a3 ^ a8 ^ a13 ^ a18 ^ a23; + c4 = a4 ^ a9 ^ a14 ^ a19 ^ a24; + + /* Theta + Rho + Pi steps */ + d = c4 ^ ROL64(c1, 1); + b0 = d ^ a0; + b16 = ROL64(d ^ a5, ROT_01); + b7 = ROL64(d ^ a10, ROT_02); + b23 = ROL64(d ^ a15, ROT_03); + b14 = ROL64(d ^ a20, ROT_04); + + d = c0 ^ ROL64(c2, 1); + b10 = ROL64(d ^ a1, ROT_05); + b1 = ROL64(d ^ a6, ROT_06); + b17 = ROL64(d ^ a11, ROT_07); + b8 = ROL64(d ^ a16, ROT_08); + b24 = ROL64(d ^ a21, ROT_09); + + d = c1 ^ ROL64(c3, 1); + b20 = ROL64(d ^ a2, ROT_10); + b11 = ROL64(d ^ a7, ROT_11); + b2 = ROL64(d ^ a12, ROT_12); + b18 = ROL64(d ^ a17, ROT_13); + b9 = ROL64(d ^ a22, ROT_14); + + d = c2 ^ ROL64(c4, 1); + b5 = ROL64(d ^ a3, ROT_15); + b21 = ROL64(d ^ a8, ROT_16); + b12 = ROL64(d ^ a13, ROT_17); + b3 = ROL64(d ^ a18, ROT_18); + b19 = ROL64(d ^ a23, ROT_19); + + d = c3 ^ ROL64(c0, 1); + b15 = ROL64(d ^ a4, ROT_20); + b6 = ROL64(d ^ a9, ROT_21); + b22 = ROL64(d ^ a14, ROT_22); + b13 = ROL64(d ^ a19, ROT_23); + b4 = ROL64(d ^ a24, ROT_24); + + /* Chi + Iota steps */ + a0 = b0 ^ (~b1 & b2) ^ roundconstants[i]; + a1 = b1 ^ (~b2 & b3); + a2 = b2 ^ (~b3 & b4); + a3 = b3 ^ (~b4 & b0); + a4 = b4 ^ (~b0 & b1); + + a5 = b5 ^ (~b6 & b7); + a6 = b6 ^ (~b7 & b8); + a7 = b7 ^ (~b8 & b9); + a8 = b8 ^ (~b9 & b5); + a9 = b9 ^ (~b5 & b6); + + a10 = b10 ^ (~b11 & b12); + a11 = b11 ^ (~b12 & b13); + a12 = b12 ^ (~b13 & b14); + a13 = b13 ^ (~b14 & b10); + a14 = b14 ^ (~b10 & b11); + + a15 = b15 ^ (~b16 & b17); + a16 = b16 ^ (~b17 & b18); + a17 = b17 ^ (~b18 & b19); + a18 = b18 ^ (~b19 & b15); + a19 = b19 ^ (~b15 & b16); + + a20 = b20 ^ (~b21 & b22); + a21 = b21 ^ (~b22 & b23); + a22 = b22 ^ (~b23 & b24); + a23 = b23 ^ (~b24 & b20); + a24 = b24 ^ (~b20 & b21); + } + + state[0] = a0; + state[1] = a1; + state[2] = a2; + state[3] = a3; + state[4] = a4; + state[5] = a5; + state[6] = a6; + state[7] = a7; + state[8] = a8; + state[9] = a9; + state[10] = a10; + state[11] = a11; + state[12] = a12; + state[13] = a13; + state[14] = a14; + state[15] = a15; + state[16] = a16; + state[17] = a17; + state[18] = a18; + state[19] = a19; + state[20] = a20; + state[21] = a21; + state[22] = a22; + state[23] = a23; + state[24] = a24; +} diff --git a/crypto/keccak/keccak.h b/crypto/keccak/keccak.h new file mode 100644 index 00000000..28fa66fb --- /dev/null +++ b/crypto/keccak/keccak.h @@ -0,0 +1,41 @@ +/* + * An implementation of the SHA3 (Keccak) hash function family. + * + * Algorithm specifications: http://keccak.noekeon.org/ + * NIST Announcement: + * http://csrc.nist.gov/groups/ST/hash/sha-3/winner_sha-3.html + * + * Written in 2013 by Fabrizio Tarizzo + * + * =================================================================== + * The contents of this file are dedicated to the public domain. To + * the extent that dedication to the public domain is not available, + * everyone is granted a worldwide, perpetual, royalty-free, + * non-exclusive license to exercise all rights associated with the + * contents of this file for any purpose whatsoever. + * No rights are reserved. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * =================================================================== +*/ + +#pragma once +#include +#include + +struct keccak_state; + +int keccak_reset(keccak_state *state); +int keccak_init(keccak_state **state, size_t capacity_bytes, uint8_t rounds); +int keccak_destroy(keccak_state *state); +int keccak_absorb(keccak_state *self, const uint8_t *in, size_t length); +int keccak_squeeze(keccak_state *self, uint8_t *out, size_t length, uint8_t padding); +int keccak_digest(keccak_state *state, uint8_t *digest, size_t len, uint8_t padding); +int keccak_copy(const keccak_state *src, keccak_state *dst); diff --git a/crypto/openssl/bignum.cpp b/crypto/openssl/bignum.cpp index 275b01bb..9b6bf637 100644 --- a/crypto/openssl/bignum.cpp +++ b/crypto/openssl/bignum.cpp @@ -14,10 +14,14 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "openssl/bignum.h" +#ifdef OPENSSL_IS_BORINGSSL +#include +#endif + // impl only #include @@ -228,6 +232,7 @@ Bignum& Bignum::import_lsb(const unsigned char* buffer, std::size_t size) { std::string Bignum::to_str() const { char* ptr = BN_bn2dec(val); + CHECK(ptr); std::string z(ptr); OPENSSL_free(ptr); return z; @@ -235,6 +240,7 @@ std::string Bignum::to_str() const { std::string Bignum::to_hex() const { char* ptr = BN_bn2hex(val); + CHECK(ptr); std::string z(ptr); OPENSSL_free(ptr); return z; @@ -251,7 +257,13 @@ std::istream& operator>>(std::istream& is, Bignum& x) { return is; } -bool is_prime(const Bignum& p, int nchecks, bool trial_div) { - return BN_is_prime_fasttest_ex(p.bn_ptr(), BN_prime_checks, get_ctx(), trial_div, 0); +bool is_prime(const Bignum& p) { +#if OPENSSL_VERSION_MAJOR >= 3 + int result = BN_check_prime(p.bn_ptr(), get_ctx(), nullptr); + LOG_IF(FATAL, result == -1); + return result; +#else + return BN_is_prime_fasttest_ex(p.bn_ptr(), BN_prime_checks, get_ctx(), true, 0); +#endif } } // namespace arith diff --git a/crypto/openssl/bignum.h b/crypto/openssl/bignum.h index 40d7d582..032dbb02 100644 --- a/crypto/openssl/bignum.h +++ b/crypto/openssl/bignum.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -115,7 +115,7 @@ class Bignum { return set_hex_str(hs.str); } Bignum& swap(Bignum& x) { - BN_swap(val, x.val); + std::swap(val, x.val); return *this; } BIGNUM* bn_ptr() { @@ -335,7 +335,7 @@ const Bignum sqr(const Bignum& x); std::ostream& operator<<(std::ostream& os, const Bignum& x); std::istream& operator>>(std::istream& is, Bignum& x); -bool is_prime(const Bignum& p, int nchecks = 64, bool trial_div = true); +bool is_prime(const Bignum& p); inline int cmp(const Bignum& x, const Bignum& y) { return BN_cmp(x.bn_ptr(), y.bn_ptr()); diff --git a/crypto/openssl/digest.hpp b/crypto/openssl/digest.hpp new file mode 100644 index 00000000..5c232df9 --- /dev/null +++ b/crypto/openssl/digest.hpp @@ -0,0 +1,151 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ +#pragma once +#include + +#include +#include + +#include "td/utils/Slice.h" + +namespace digest { +struct OpensslEVP_SHA1 { + enum { digest_bytes = 20 }; + static const EVP_MD *get_evp() { + return EVP_sha1(); + } +}; + +struct OpensslEVP_SHA256 { + enum { digest_bytes = 32 }; + static const EVP_MD *get_evp() { + return EVP_sha256(); + } +}; + +struct OpensslEVP_SHA512 { + enum { digest_bytes = 64 }; + static const EVP_MD *get_evp() { + return EVP_sha512(); + } +}; + +template +class HashCtx { + EVP_MD_CTX *ctx{nullptr}; + void init(); + void clear(); + + public: + enum { digest_bytes = H::digest_bytes }; + HashCtx() { + init(); + } + HashCtx(const void *data, std::size_t len) { + init(); + feed(data, len); + } + ~HashCtx() { + clear(); + } + void reset(); + void feed(const void *data, std::size_t len); + void feed(td::Slice slice) { + feed(slice.data(), slice.size()); + } + std::size_t extract(unsigned char buffer[digest_bytes]); + std::size_t extract(td::MutableSlice slice); + std::string extract(); +}; + +template +void HashCtx::init() { + ctx = EVP_MD_CTX_create(); + reset(); +} + +template +void HashCtx::reset() { + EVP_DigestInit_ex(ctx, H::get_evp(), 0); +} + +template +void HashCtx::clear() { + EVP_MD_CTX_destroy(ctx); + ctx = nullptr; +} + +template +void HashCtx::feed(const void *data, std::size_t len) { + EVP_DigestUpdate(ctx, data, len); +} + +template +std::size_t HashCtx::extract(unsigned char buffer[digest_bytes]) { + unsigned olen = 0; + EVP_DigestFinal_ex(ctx, buffer, &olen); + assert(olen == digest_bytes); + return olen; +} + +template +std::size_t HashCtx::extract(td::MutableSlice slice) { + return extract(slice.ubegin()); +} + +template +std::string HashCtx::extract() { + unsigned char buffer[digest_bytes]; + unsigned olen = 0; + EVP_DigestFinal_ex(ctx, buffer, &olen); + assert(olen == digest_bytes); + return std::string((char *)buffer, olen); +} + +typedef HashCtx SHA1; +typedef HashCtx SHA256; +typedef HashCtx SHA512; + +template +std::size_t hash_str(unsigned char buffer[T::digest_bytes], const void *data, std::size_t size) { + T hasher(data, size); + return hasher.extract(buffer); +} + +template +std::size_t hash_two_str(unsigned char buffer[T::digest_bytes], const void *data1, std::size_t size1, const void *data2, + std::size_t size2) { + T hasher(data1, size1); + hasher.feed(data2, size2); + return hasher.extract(buffer); +} + +template +std::string hash_str(const void *data, std::size_t size) { + T hasher(data, size); + return hasher.extract(); +} + +template +std::string hash_two_str(const void *data1, std::size_t size1, const void *data2, std::size_t size2) { + T hasher(data1, size1); + hasher.feed(data2, size2); + return hasher.extract(); +} +} // namespace digest diff --git a/crypto/openssl/rand.cpp b/crypto/openssl/rand.cpp index 9e9c546d..57f5c05f 100644 --- a/crypto/openssl/rand.cpp +++ b/crypto/openssl/rand.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "openssl/rand.hpp" diff --git a/crypto/openssl/rand.hpp b/crypto/openssl/rand.hpp index 91064e07..6ead2be7 100644 --- a/crypto/openssl/rand.hpp +++ b/crypto/openssl/rand.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include diff --git a/crypto/openssl/residue.cpp b/crypto/openssl/residue.cpp index 809ccd5a..20c81e24 100644 --- a/crypto/openssl/residue.cpp +++ b/crypto/openssl/residue.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "residue.h" diff --git a/crypto/openssl/residue.h b/crypto/openssl/residue.h index 00670838..adea1de7 100644 --- a/crypto/openssl/residue.h +++ b/crypto/openssl/residue.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "bignum.h" diff --git a/crypto/parser/lexer.cpp b/crypto/parser/lexer.cpp index d8029340..117f1df5 100644 --- a/crypto/parser/lexer.cpp +++ b/crypto/parser/lexer.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "lexer.h" #include "symtable.h" @@ -125,8 +125,9 @@ int Lexem::set(std::string _str, const SrcLocation& _loc, int _tp, int _val) { } Lexer::Lexer(SourceReader& _src, bool init, std::string active_chars, std::string eol_cmts, std::string open_cmts, - std::string close_cmts, std::string quote_chars) - : src(_src), eof(false), lexem("", src.here(), Lexem::Undefined), peek_lexem("", {}, Lexem::Undefined) { + std::string close_cmts, std::string quote_chars, std::string multiline_quote) + : src(_src), eof(false), lexem("", src.here(), Lexem::Undefined), peek_lexem("", {}, Lexem::Undefined), + multiline_quote(std::move(multiline_quote)) { std::memset(char_class, 0, sizeof(char_class)); unsigned char activity = cc::active; for (char c : active_chars) { @@ -171,6 +172,19 @@ void Lexer::set_spec(std::array& arr, std::string setup) { } } +bool Lexer::is_multiline_quote(const char* begin, const char* end) { + if (multiline_quote.empty()) { + return false; + } + for (const char& c : multiline_quote) { + if (begin == end || *begin != c) { + return false; + } + ++begin; + } + return true; +} + void Lexer::expect(int exp_tp, const char* msg) { if (tp() != exp_tp) { throw ParseError{lexem.loc, (msg ? std::string{msg} : Lexem::lexem_name_str(exp_tp)) + " expected instead of " + @@ -234,6 +248,36 @@ const Lexem& Lexer::next() { } return lexem.clear(src.here(), Lexem::Eof); } + if (is_multiline_quote(src.get_ptr(), src.get_end_ptr())) { + src.advance(multiline_quote.size()); + const char* end = nullptr; + SrcLocation here = src.here(); + std::string body; + while (!src.is_eof()) { + if (src.is_eoln()) { + body.push_back('\n'); + src.load_line(); + continue; + } + if (is_multiline_quote(src.get_ptr(), src.get_end_ptr())) { + end = src.get_ptr(); + src.advance(multiline_quote.size()); + break; + } + body.push_back(src.cur_char()); + src.advance(1); + } + if (!end) { + src.error("string extends past end of file"); + } + lexem.set(body, here, Lexem::String); + int c = src.cur_char(); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + lexem.val = c; + src.advance(1); + } + return lexem; + } int c = src.cur_char(); const char* end = src.get_ptr(); if (is_quote_char(c) || c == '`') { @@ -247,6 +291,11 @@ const Lexem& Lexer::next() { } lexem.set(std::string{src.get_ptr() + 1, end}, src.here(), qc == '`' ? Lexem::Unknown : Lexem::String); src.set_ptr(end + 1); + c = src.cur_char(); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + lexem.val = c; + src.set_ptr(end + 2); + } // std::cerr << lexem.name_str() << ' ' << lexem.str << std::endl; return lexem; } diff --git a/crypto/parser/lexer.h b/crypto/parser/lexer.h index 803560e1..686d8eac 100644 --- a/crypto/parser/lexer.h +++ b/crypto/parser/lexer.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "srcread.h" @@ -71,6 +71,7 @@ class Lexer { Lexem lexem, peek_lexem; unsigned char char_class[128]; std::array eol_cmt, cmt_op, cmt_cl; + std::string multiline_quote; enum cc { left_active = 2, right_active = 1, active = 3, allow_repeat = 4, quote_char = 8 }; public: @@ -78,7 +79,8 @@ class Lexer { return eof; } Lexer(SourceReader& _src, bool init = false, std::string active_chars = ";,() ~.", std::string eol_cmts = ";;", - std::string open_cmts = "{-", std::string close_cmts = "-}", std::string quote_chars = "\""); + std::string open_cmts = "{-", std::string close_cmts = "-}", std::string quote_chars = "\"", + std::string multiline_quote = "\"\"\""); const Lexem& next(); const Lexem& cur() const { return lexem; @@ -109,6 +111,7 @@ class Lexer { private: void set_spec(std::array& arr, std::string setup); + bool is_multiline_quote(const char* begin, const char* end); }; } // namespace src diff --git a/crypto/parser/srcread.cpp b/crypto/parser/srcread.cpp index 3b363242..332f1539 100644 --- a/crypto/parser/srcread.cpp +++ b/crypto/parser/srcread.cpp @@ -14,9 +14,10 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "srcread.h" +#include namespace src { @@ -34,9 +35,47 @@ std::ostream& operator<<(std::ostream& os, const Fatal& fatal) { return os << fatal.get_msg(); } +const char* FileDescr::convert_offset(long offset, long* line_no, long* line_pos, long* line_size) const { + long lno = 0, lpos = -1, lsize = 0; + const char* lstart = nullptr; + if (offset >= 0 && offset < (long)text.size()) { + auto it = std::upper_bound(line_offs.begin(), line_offs.end(), offset); + lno = it - line_offs.begin(); + if (lno && it != line_offs.end()) { + lsize = it[0] - it[-1]; + lpos = offset - it[-1]; + lstart = text.data() + it[-1]; + } + } else { + lno = (long)line_offs.size(); + } + if (line_no) { + *line_no = lno; + } + if (line_pos) { + *line_pos = lpos; + } + if (line_size) { + *line_size = lsize; + } + return lstart; +} + +const char* FileDescr::push_line(std::string new_line) { + if (line_offs.empty()) { + line_offs.push_back(0); + } + std::size_t cur_size = text.size(); + text += new_line; + text += '\0'; + line_offs.push_back((long)text.size()); + return text.data() + cur_size; +} + void SrcLocation::show(std::ostream& os) const { os << fdescr; - if (line_no > 0) { + long line_no, line_pos; + if (fdescr && convert_pos(&line_no, &line_pos)) { os << ':' << line_no; if (line_pos >= 0) { os << ':' << (line_pos + 1); @@ -45,13 +84,15 @@ void SrcLocation::show(std::ostream& os) const { } bool SrcLocation::show_context(std::ostream& os) const { - if (text.empty() || line_pos < 0 || (unsigned)line_pos > text.size()) { + long line_no, line_pos, line_size; + if (!fdescr || !convert_pos(&line_no, &line_pos, &line_size)) { return false; } - bool skip_left = (line_pos > 200), skip_right = (line_pos + 200u < text.size()); - const char* start = skip_left ? text.c_str() + line_pos - 100 : text.c_str(); - const char* end = skip_right ? text.c_str() + line_pos + 100 : text.c_str() + text.size(); - const char* here = text.c_str() + line_pos; + bool skip_left = (line_pos > 200), skip_right = (line_pos + 200u < line_size); + const char* here = fdescr->text.data() + char_offs; + const char* base = here - line_pos; + const char* start = skip_left ? here - 100 : base; + const char* end = skip_right ? here + 100 : base + line_size; os << " "; if (skip_left) { os << "... "; @@ -99,8 +140,8 @@ void ParseError::show(std::ostream& os) const { where.show_context(os); } -SourceReader::SourceReader(std::istream* _is, const FileDescr* _fdescr) - : ifs(_is), loc(_fdescr), eof(false), cur_line_len(0), start(0), cur(0), end(0) { +SourceReader::SourceReader(std::istream* _is, FileDescr* _fdescr) + : ifs(_is), fdescr(_fdescr), loc(_fdescr), eof(false), cur_line_len(0), start(0), cur(0), end(0) { load_line(); } @@ -139,7 +180,7 @@ const char* SourceReader::set_ptr(const char* ptr) { if (ptr < cur || ptr > end) { error("parsing position went outside of line"); } - loc.line_pos = (int)(ptr - start); + loc.char_offs += ptr - cur; cur = ptr; } return ptr; @@ -149,12 +190,11 @@ bool SourceReader::load_line() { if (eof) { return false; } + loc.set_eof(); if (ifs->eof()) { set_eof(); return false; } - ++loc.line_no; - loc.line_pos = -1; std::getline(*ifs, cur_line); if (ifs->fail()) { set_eof(); @@ -174,11 +214,16 @@ bool SourceReader::load_line() { cur_line.pop_back(); --len; } - loc.text = cur_line; cur_line_len = (int)len; - loc.line_pos = 0; - cur = start = cur_line.c_str(); - end = start + cur_line_len; + if (fdescr) { + cur = start = fdescr->push_line(std::move(cur_line)); + end = start + len; + loc.char_offs = (std::size_t)(cur - fdescr->text.data()); + cur_line.clear(); + } else { + cur = start = cur_line.c_str(); + end = start + cur_line_len; + } return true; } diff --git a/crypto/parser/srcread.h b/crypto/parser/srcread.h index 05b79212..61128eff 100644 --- a/crypto/parser/srcread.h +++ b/crypto/parser/srcread.h @@ -14,11 +14,12 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include +#include #include namespace src { @@ -31,9 +32,14 @@ namespace src { struct FileDescr { std::string filename; + std::string text; + std::vector line_offs; bool is_stdin; + bool is_main = false; FileDescr(std::string _fname, bool _stdin = false) : filename(std::move(_fname)), is_stdin(_stdin) { } + const char* push_line(std::string new_line); + const char* convert_offset(long offset, long* line_no, long* line_pos, long* line_size = nullptr) const; }; struct Fatal { @@ -49,16 +55,23 @@ std::ostream& operator<<(std::ostream& os, const Fatal& fatal); struct SrcLocation { const FileDescr* fdescr; - int line_no; - int line_pos; - std::string text; - SrcLocation() : fdescr(nullptr), line_no(0), line_pos(-1) { + long char_offs; + SrcLocation() : fdescr(nullptr), char_offs(-1) { } - SrcLocation(const FileDescr* _fdescr, int line = 0, int pos = -1) : fdescr(_fdescr), line_no(line), line_pos(pos) { + SrcLocation(const FileDescr* _fdescr, long offs = -1) : fdescr(_fdescr), char_offs(-1) { } bool defined() const { return fdescr; } + bool eof() const { + return char_offs == -1; + } + void set_eof() { + char_offs = -1; + } + const char* convert_pos(long* line_no, long* line_pos, long* line_size = nullptr) const { + return defined() ? fdescr->convert_offset(char_offs, line_no, line_pos, line_size) : nullptr; + } void show(std::ostream& os) const; bool show_context(std::ostream& os) const; void show_gen_error(std::ostream& os, std::string message, std::string err_type = "") const; @@ -98,6 +111,7 @@ struct ParseError : Error { class SourceReader { std::istream* ifs; + FileDescr* fdescr; SrcLocation loc; bool eof; std::string cur_line; @@ -106,7 +120,7 @@ class SourceReader { const char *start, *cur, *end; public: - SourceReader(std::istream* _is, const FileDescr* _fdescr); + SourceReader(std::istream* _is, FileDescr* _fdescr); bool load_line(); bool is_eof() const { return eof; diff --git a/crypto/parser/symtable.cpp b/crypto/parser/symtable.cpp index 939bb29c..d52d9648 100644 --- a/crypto/parser/symtable.cpp +++ b/crypto/parser/symtable.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "symtable.h" #include @@ -32,8 +32,8 @@ int scope_level; SymTable<100003> symbols; -SymDef* sym_def[symbols.hprime]; -SymDef* global_sym_def[symbols.hprime]; +SymDef* sym_def[symbols.hprime + 1]; +SymDef* global_sym_def[symbols.hprime + 1]; std::vector> symbol_stack; std::vector scope_opened_at; diff --git a/crypto/parser/symtable.h b/crypto/parser/symtable.h index 88f683bd..51d59dfa 100644 --- a/crypto/parser/symtable.h +++ b/crypto/parser/symtable.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "srcread.h" @@ -32,7 +32,7 @@ namespace sym { typedef int var_idx_t; struct SymValBase { - enum { _Param, _Var, _Func, _Typename, _GlobVar }; + enum { _Param, _Var, _Func, _Typename, _GlobVar, _Const }; int type; int idx; SymValBase(int _type, int _idx) : type(_type), idx(_idx) { @@ -161,8 +161,8 @@ struct SymDef { } }; -extern SymDef* sym_def[symbols.hprime]; -extern SymDef* global_sym_def[symbols.hprime]; +extern SymDef* sym_def[symbols.hprime + 1]; +extern SymDef* global_sym_def[symbols.hprime + 1]; extern std::vector> symbol_stack; extern std::vector scope_opened_at; diff --git a/crypto/smartcont/CreateState.fif b/crypto/smartcont/CreateState.fif index 1330a446..de21cd47 100644 --- a/crypto/smartcont/CreateState.fif +++ b/crypto/smartcont/CreateState.fif @@ -15,6 +15,11 @@ constant empty_cell +variable @default-subwallet-id +@default-subwallet-id 0! +{ @default-subwallet-id @ } : default-subwallet-id +{ @default-subwallet-id ! } : default-subwallet-id! + // b x --> b' ( serializes a Gram amount ) { -1 { 1+ 2dup 8 * ufits } until rot over 4 u, -rot 8 * u, } : Gram, @@ -40,6 +45,10 @@ { 8 config! } : config.version! 1 constant capIhr 2 constant capCreateStats +4 constant capBounceMsgBody +8 constant capReportVersion +16 constant capSplitMergeTransactions +32 constant capShortDequeue // max-validators masterchain-validators min-validators -- { swap rot 16 config! } : config.validator_num! @@ -52,43 +61,79 @@ // elected-for elections-begin-before elections-end-before stakes-frozen { 4 0 reverse 15 config! } : config.election_params! -dictnew 0 2constant validator-dict -{ @' validator-dict } : validator-dict@ -{ validator-dict@ nip } : validator# -// val-pubkey weight -- +variable validator-dict +dictnew 0 validator-dict 2! +variable validators-weight +validators-weight 0! + +{ validator-dict @ second } : validator# { dup 0<= abort"validator weight must be non-negative" - dup 64 ufits not abort"validator weight must fit into 64 bits" + 64 ufits not abort"validator weight must fit into 64 bits" +} : check-val-weight +// ( val-pubkey weight -- c ) +{ dup check-val-weight over Blen 32 <> abort"validator public key must be 32 bytes long" - +} : serialize-validator +// ( val-pubkey adnl weight -- c ) +{ dup check-val-weight + over 256 ufits not abort"adnl address must fit into 256 bits" + rot dup Blen 32 <> abort"validator public key must be 32 bytes long" + +} : serialize-adnl-validator +// ( weight val-cell -- ) +{ swap validators-weight +! + + rot 34 config! } : config.validators! -dictnew constant workchain-dict +variable workchain-dict // root-hash file-hash enable-utime actual-min-split min-split max-split workchain-id -- { dup isWorkchainDescr? not abort"invalid WorkchainDescr created" - s s>c 12 config! } : config.workchains! +{ workchain-dict @ dict>s s>c 12 config! } : config.workchains! -dictnew constant special-dict +variable special-dict // special-smc-addr -- -{ x{} swap @' special-dict 256 udict! not abort"cannot add a new special smart contract" - =: special-dict +{ x{} swap special-dict @ 256 udict! not abort"cannot add a new special smart contract" + special-dict ! } : make_special -{ @' special-dict dict>s s>c 31 config! } : config.special! +{ special-dict @ dict>s s>c 31 config! } : config.special! + +// ( l -- D ) Converts a list of parameter indices into a dictionary +{ dictnew { swap uncons -rot idict! not abort"cannot add parameter index" over null? + } until nip +} : param-list-to-dict +{ param-list-to-dict 9 config! } : config.mandatory_params! +{ param-list-to-dict 10 config! } : config.critical_params! + +// min_tot_rounds max_tot_rounds min_wins max_losses min_store_sec max_store_sec bit_price cell_price -- +{ 8 untuple 8 0 reverse } : cfg-prop-setup +// normal-prop-params critical-prop-params -- +{ swap cfg-prop-setup swap cfg-prop-setup } : make-proposals-setup +{ make-proposals-setup 11 config! } : config.param_proposals_setup! + +// deposit bit_pps cell_pps +{ 3 0 reverse } : create-complaint-pricing +{ create-complaint-pricing 13 config! } : config.complaint_prices! // bit-pps cell-pps mc-bit-pps mc-cell-pps -- { +// mc-cc-lifetime sh-cc-lifetime sh-val-lifetime sh-val-num mc-shuffle +{ 4 1 reverse } : make-catchain-params { make-catchain-params 28 config! } : config.catchain_params! -// round-candidates next-cand-delay-ms consensus-timeout-ms fast-attempts attempt-duration cc-max-deps max-block-size max-collated-size -{ 8 0 reverse } : make-vsession-params +// round-candidates next-cand-delay-ms consensus-timeout-ms fast-attempts attempt-duration cc-max-deps max-block-size max-collated-size new-cc-ids +{ 8 1 reverse } : make-vsession-params { make-vsession-params 29 config! } : config.consensus_params! // b [underload soft hard] -- b' @@ -188,9 +233,15 @@ dictnew constant special-dict // restricted wallet creation + +// same as in new-wallet-v3.fif +"wallet-v3-code.fif" include +=: WCode3 + "auto/wallet-code.fif" include =: WCode0 "auto/restricted-wallet-code.fif" include =: RWCode1 "auto/restricted-wallet2-code.fif" include =: RWCode2 +"auto/restricted-wallet3-code.fif" include =: RWCode3 // pubkey amount -- { over ."Key " pubkey>$ type ." -> " @@ -205,22 +256,49 @@ dictnew constant special-dict Masterchain swap 6 .Addr cr } : create-wallet1 +// pubkey amount +{ over ."W0 Key " pubkey>$ type space dup .GR ." -> " + WCode3 // code + // data + empty_cell // libs + 3 roll // balance + 0 0 2 register_smc + Masterchain swap 6 .Addr cr +} : create-wallet0 + // D x t -- D' -{ idict! not abort"cannot add value" +{ idict! not abort"cannot add value" } : rdict-entry +{ 86400 * } : days* +{ 365 * days* } : years* // balance -- dict { dictnew - over -32768 rdict-entry - over 3/4 */ 92 rdict-entry - over 1/2 */ 183 rdict-entry - swap 1/4 */ 366 rdict-entry - 0 548 rdict-entry -} : make-rdict + over 31 -1<< rdict-entry + over 3/4 */ 91 days* rdict-entry + over 1/2 */ 183 days* rdict-entry + swap 1/4 */ 365 days* rdict-entry + 0 548 days* rdict-entry +} : make-rdict1 +{ dictnew + over 31 -1<< rdict-entry + over .9 */ 0 rdict-entry + over .6775 */ 1 years* rdict-entry + over .445 */ 2 years* rdict-entry + swap .2225 */ 3 years* rdict-entry + 0 4 years* 86400 + rdict-entry +} : make-rdict2 + +variable 'make-rdict +{ 'make-rdict @ execute } : make-rdict + +variable rwallet-start-at rwallet-start-at 0! +now 86400 / 1+ 86400 * rwallet-start-at ! // pubkey amount -- { over ."Key " pubkey>$ type ." -> " RWCode2 // code - // data + // data empty_cell // libs 3 roll // balance 0 // split_depth @@ -230,8 +308,42 @@ dictnew constant special-dict Masterchain swap 6 .Addr cr } : create-wallet2 +variable rwallet-init-pubkey + +// pubkey -- addr +{ RWCode3 // code + // data + empty_cell // libs + 0 // balance + 0 0 0 register_smc // compute address only +} : precompute-wallet3-addr + +variable w3-addr + +// pubkey amount 'rdict -- +{ 'make-rdict ! over precompute-wallet3-addr w3-addr ! + over ."RW3 Key " pubkey>$ type space dup .GR ." -> " + RWCode3 // code + // data + empty_cell // libs + 3 roll // balance + 0 // split_depth + 0 // ticktock + w3-addr @ // address + 6 // mode: create+setaddr + register_smc + Masterchain swap 6 .Addr cr +} : create-wallet3-internal + +{ ' make-rdict1 create-wallet3-internal } : create-wallet3 +{ ' make-rdict2 create-wallet3-internal } : create-wallet3b + // pubkey amount -{ over ."Key " pubkey>$ type ." -> " +{ over ."Key " pubkey>$ type space dup .GR ." -> " WCode0 // code // data empty_cell // libs @@ -241,7 +353,7 @@ dictnew constant special-dict 2 // mode: create register_smc Masterchain swap 6 .Addr cr -} : create-wallet0 +} : create-wallet0a { dup tlb-type-lookup { nip } { "unknown TLB type " swap $+ abort } cond } : $>tlb { bl word $>tlb 1 'nop } ::_ tlb: diff --git a/crypto/smartcont/LICENSE.LGPL b/crypto/smartcont/LICENSE.LGPL new file mode 100644 index 00000000..b482fc4e --- /dev/null +++ b/crypto/smartcont/LICENSE.LGPL @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/crypto/smartcont/auto-dns.fif b/crypto/smartcont/auto-dns.fif new file mode 100644 index 00000000..21420d1e --- /dev/null +++ b/crypto/smartcont/auto-dns.fif @@ -0,0 +1,115 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include +"GetOpt.fif" include + +{ show-options-help 1 halt } : usage + +"dns-msg-body.boc" =: savefile + +begin-options + " [-o] (add|update|prolong) ... " +cr +tab + +"Creates the internal message body containing a request to automatic DNS smart contract created by new-auto-dns.fif, " + +"to be sent later with a suitable payment from a wallet to , and saves it into ('" savefile $+ +"' by default). " + +"The operation to be performed is one of" +cr +tab + +"add { owner | cat (smc | next | adnl | text ) }" +cr +tab + +"update { owner | cat (smc | next | adnl | text ) }" +cr +tab + +"prolong " + disable-digit-options generic-help-setopt + "o" "--output" { =: savefile } short-long-option-arg + "Sets output file for generated initialization message ('" savefile $+ +"' by default)" option-help + "h" "--help" { usage } short-long-option + "Shows a help message" option-help +parse-options + +$# 4 < ' usage if +4 :$1..n + +$1 true parse-load-address =: bounce 2=: dest-addr +$2 dup =: main-op-name atom =: main-op +$3 dup =: subdomain $len 127 > abort"subdomain name too long" +$4 parse-int dup 30 1<< < { now + } if =: expire-at + +{ $* @ dup null? { second $@ ! } { drop } cond } : @skip +{ $* @ null? } : @end? +{ $* @ uncons $* ! } : @next +{ @next drop } 4 times + +main-op dup `add eq? over `update eq? or swap `prolong eq? or +{ "unknown main operation '" main-op-name $+ +"'; one of 'add', 'update' or 'prolong' expected" abort } ifnot +main-op `prolong eq? not =: need-params + +$# 4 > need-params <> abort"extra parameters, or no parameters for chosen main operation" + +variable Values dictnew Values ! +// ( i c -- ) +{ over 0= abort"category cannot be zero" + udict!+ not abort"duplicate category id" + Values ! +} : register-value + +{ @end? abort"category number expected" @next (number) 1 <> abort"category must be integer" + dup 256 fits not abort"category does not fit into 256 bit integer" + dup 0= abort"category must be non-zero" +} : parse-cat-num +{ @end? abort"smart contract address expected" + @next false parse-load-address drop +} : cl-parse-smc-addr +{ @end? abort"adnl address expected" + @next parse-adnl-addr +} : cl-parse-adnl-addr +{ } : serialize-smc-addr +{ } : serialize-next-resolver +{ } : serialize-adnl-addr +{ } : serialize-text +{ @end? abort"subdomain record value expected" @next + dup "smc" $= { drop cl-parse-smc-addr serialize-smc-addr } { + dup "next" $= { drop cl-parse-smc-addr serialize-next-resolver } { + dup "adnl" $= { drop cl-parse-adnl-addr serialize-adnl-addr } { + dup "text" $= { drop @next serialize-text } { + "unknown record type "' swap $+ +"'" abort + } cond } cond } cond } cond +} : parse-value +{ @next dup "owner" $= { drop -2 cl-parse-smc-addr serialize-smc-addr } { + dup "cat" $= { drop parse-cat-num parse-value } { + "unknown action '" swap $+ +"'" abort + } cond } cond + register-value +} : parse-action +{ { @end? not } { parse-action } while } : parse-actions +parse-actions + +// ( S -- S1 .. Sn n ) +{ 1 swap { dup "." $pos dup 0>= } { $| 1 $| nip rot 1+ swap } while drop swap +} : split-by-dots +// ( S -- s ) +{ dup $len dup 0= abort"subdomain cannot be empty" 126 > abort"subdomain too long" + dup 0 chr $pos 1+ abort"subdomain contains null characters" + split-by-dots s + +main-op ( _( `add 0x72656764 ) _( `update 0x75706464 ) _( `prolong 0x70726f6c ) ) +assq-val not abort"unknown main operation" +=: op-id + +."Automatic DNS smart contract address = " dest-addr 2dup .addr cr 6 .Addr cr + +."Action: " main-op .l subdomain type space expire-at . cr +."Operation code: 0x" op-id 8 0X. cr +."Value: " +Values @ dup null? { drop ."(none)" } { =: actions-builder + +// create an internal message +now 32 << actions-builder hashu 32 1<<1- and + =: query_id +s tuck sbits 8 / 7 i, swap s, + main-op `prolong eq? { Values @ ref, } ifnot + expire-at 32 u, b> +dup ."Internal message body is: " B dup Bx. cr +."Query_id is " query_id dup . ."= 0x" X. cr +savefile tuck B>file +."(Saved to file " type .")" cr diff --git a/crypto/smartcont/complaint-vote-req.fif b/crypto/smartcont/complaint-vote-req.fif new file mode 100644 index 00000000..f771f6fd --- /dev/null +++ b/crypto/smartcont/complaint-vote-req.fif @@ -0,0 +1,29 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include +"GetOpt.fif" include + +"validator-to-sign.req" =: savefile + +{ ."usage: " @' $0 type ." []" cr + ."Creates an unsigned request to vote for complaint (decimal; prefix with '0x' if needed) of past validator set on behalf of validator with zero-based index in current validator set (as stored in configuration parameter 34)." cr + ."The result is saved into (" savefile type ." by default) and output in hexadecimal form, to be signed later by the validator public key" cr 1 halt +} : usage + +$# dup 3 < swap 4 > or ' usage if +4 :$1..n + +$1 parse-int dup =: val-idx + 16 ufits not abort"validator index out of range" +$2 parse-int dup =: elect-id + 32 ufits not abort"invalid election id" +$3 parse-int dup =: compl-hash + 256 ufits not abort"invalid complaint hash" +$4 savefile replace-if-null =: savefile + +."Creating a request to vote for complaint 0x" compl-hash 64x. ."of past validator set " elect-id . +."on behalf of current validator with index " val-idx . cr + +B{56744350} val-idx 16 u>B B+ elect-id 32 u>B B+ compl-hash 256 u>B B+ +dup Bx. cr +dup B>base64url type cr +savefile tuck B>file ."Saved to file " type cr diff --git a/crypto/smartcont/complaint-vote-signed.fif b/crypto/smartcont/complaint-vote-signed.fif new file mode 100644 index 00000000..a8498b94 --- /dev/null +++ b/crypto/smartcont/complaint-vote-signed.fif @@ -0,0 +1,44 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include +"GetOpt.fif" include + +"vote-query.boc" =: savefile + +{ ."usage: " @' $0 type ." []" cr + ."Creates an internal message body to be sent from any smart contract residing in the masterchain to the elections smart contract containing a signed request to vote for complaint (decimal; prefix with '0x' if needed) of past validator set on behalf of current validator with zero-based index and (Base64) public key in current validator set (as stored in configuration parameter 34)" cr + ." must be the base64 representation of Ed25519 signature of the previously generated unsigned request by means of " cr + ."The result is saved into (" savefile type ." by default), to be embedded later into an internal message" cr 1 halt +} : usage + +$# dup 5 < swap 6 > or ' usage if +6 :$1..n + +$1 parse-int dup =: val-idx + 16 ufits not abort"validator index out of range" +$2 parse-int dup =: elect-id + 32 ufits not abort"invalid election id" +$3 parse-int dup =: compl-hash + 256 ufits not abort"invalid complaint hash" +$4 base64>B dup Blen 36 <> abort"validator Ed25519 public key must be exactly 36 bytes long" + 32 B>u@+ 0xC6B41348 <> abort"invalid Ed25519 public key: unknown magic number" + =: pubkey +$5 base64>B dup Blen 64 <> abort"validator Ed25519 signature must be exactly 64 bytes long" + =: signature +$6 savefile replace-if-null =: savefile + +."Creating the body of an internal message to be sent to the elections smart contract" cr +."containing a signed request to vote for complaint 0x" compl-hash 64x. ."of past validator set " elect-id . +."on behalf of current validator with index " val-idx . "and public key" pubkey Bx. cr + +B{56744350} val-idx 16 u>B B+ elect-id 32 u>B B+ compl-hash 256 u>B B+ dup =: to_sign +."String to sign is " Bx. cr + +to_sign signature pubkey ed25519_chksign not abort"Ed25519 signature is invalid" +."Provided a valid Ed25519 signature " signature Bx. ." with validator public key " pubkey Bx. cr + +now 32 << compl-hash 32 1<< mod + =: query-id + + +."Internal message body is " dup B savefile tuck B>file ."Saved to file " type cr diff --git a/crypto/smartcont/config-code.fc b/crypto/smartcont/config-code.fc index 15c87b38..b3aa04c4 100644 --- a/crypto/smartcont/config-code.fc +++ b/crypto/smartcont/config-code.fc @@ -1,4 +1,6 @@ ;; Simple configuration smart contract +;; Currently deployed config-contract in mainnet can be found +;; on https://verifier.ton.org/Ef9VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVbxn () set_conf_param(int index, cell value) impure { var cs = get_data().begin_parse(); @@ -23,6 +25,29 @@ .end_cell()); } +;; (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price) +_ parse_vote_config(cell c) inline { + var cs = c.begin_parse(); + throw_unless(44, cs~load_uint(8) == 0x36); + var res = (cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(32), cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + cs.end_parse(); + return res; +} + +;; cfg_vote_setup#91 normal_params:^ConfigProposalSetup critical_params:^ConfigProposalSetup = ConfigVotingSetup; +_ get_vote_config_internal(int critical?, cell cparam11) inline_ref { + var cs = cparam11.begin_parse(); + throw_unless(44, cs~load_uint(8) == 0x91); + if (critical?) { + cs~load_ref(); + } + return parse_vote_config(cs.preload_ref()); +} + +_ get_vote_config(int critical?) inline { + return get_vote_config_internal(critical?, config_param(11)); +} + (int, int) check_validator_set(cell vset) { var cs = vset.begin_parse(); throw_unless(9, cs~load_uint(8) == 0x12); ;; validators_ext#12 only @@ -37,18 +62,409 @@ () send_answer(addr, query_id, ans_tag, mode) impure { ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 - send_raw_message(begin_cell().store_uint(0x18, 6).store_slice(addr).store_uint(0, 5 + 4 + 4 + 64 + 32 + 1 + 1).store_uint(ans_tag, 32).store_uint(query_id, 64).end_cell(), mode); + send_raw_message(begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_uint(0, 5 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(ans_tag, 32) + .store_uint(query_id, 64) + .end_cell(), mode); } -() send_confirmation(addr, query_id, ans_tag) impure { +() send_confirmation(addr, query_id, ans_tag) impure inline { return send_answer(addr, query_id, ans_tag, 64); } -() send_error(addr, query_id, ans_tag) impure { +() send_error(addr, query_id, ans_tag) impure inline { return send_answer(addr, query_id, ans_tag, 64); } -() recv_internal(cell in_msg_cell, slice in_msg) impure { +;; forward a message to elector smart contract to make it upgrade its code +() change_elector_code(slice cs) impure { + var dest_addr = config_param(1).begin_parse().preload_uint(256); + var query_id = now(); + send_raw_message(begin_cell() + .store_uint(0xc4ff, 17) + .store_uint(dest_addr, 256) + .store_grams(1 << 30) ;; ~ 1 Gram (will be returned back) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x4e436f64, 32) ;; action + .store_uint(query_id, 64) + .store_slice(cs) + .end_cell(), 0); +} + +() after_code_upgrade(slice param, cont old_code) impure method_id(1666) { +} + +_ perform_action(cfg_dict, public_key, action, cs) inline_ref { + if (action == 0x43665021) { + ;; change one configuration parameter + var param_index = cs~load_int(32); + var param_value = cs~load_ref(); + cs.end_parse(); + cfg_dict~idict_set_ref(32, param_index, param_value); + return (cfg_dict, public_key); + } elseif (action == 0x4e436f64) { + ;; change configuration smart contract code + var new_code = cs~load_ref(); + set_code(new_code); + var old_code = get_c3(); + set_c3(new_code.begin_parse().bless()); + after_code_upgrade(cs, old_code); + throw(0); + return (cfg_dict, public_key); + } elseif (action == 0x50624b21) { + ;; change configuration master public key + public_key = cs~load_uint(256); + cs.end_parse(); + return (cfg_dict, public_key); + } elseif (action == 0x4e43ef05) { + ;; change election smart contract code + change_elector_code(cs); + return (cfg_dict, public_key); + } else { + throw_if(32, action); + return (cfg_dict, public_key); + } +} + +(cell, int, cell) get_current_vset() inline_ref { + var vset = config_param(34); + var cs = begin_parse(vset); + ;; validators_ext#12 utime_since:uint32 utime_until:uint32 + ;; total:(## 16) main:(## 16) { main <= total } { main >= 1 } + ;; total_weight:uint64 + throw_unless(40, cs~load_uint(8) == 0x12); + cs~skip_bits(32 + 32 + 16 + 16); + var (total_weight, dict) = (cs~load_uint(64), cs~load_dict()); + cs.end_parse(); + return (vset, total_weight, dict); +} + +(slice, int) get_validator_descr(int idx) inline_ref { + var (vset, total_weight, dict) = get_current_vset(); + var (value, _) = dict.udict_get?(16, idx); + return (value, total_weight); +} + +(int, int) unpack_validator_descr(slice cs) inline { + ;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey; + ;; validator#53 public_key:SigPubKey weight:uint64 = ValidatorDescr; + ;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr; + throw_unless(41, (cs~load_uint(8) & ~ 0x20) == 0x53); + throw_unless(41, cs~load_uint(32) == 0x8e81278a); + return (cs~load_uint(256), cs~load_uint(64)); +} + +;; cfg_proposal#f3 param_id:int32 param_value:(Maybe ^Cell) if_hash_equal:(Maybe uint256) +;; c -> (param-id param-cell maybe-hash) +(int, cell, int) parse_config_proposal(cell c) inline_ref { + var cs = c.begin_parse(); + throw_unless(44, cs~load_int(8) == 0xf3 - 0x100); + var (id, val, hash) = (cs~load_int(32), cs~load_maybe_ref(), cs~load_int(1)); + if (hash) { + hash = cs~load_uint(256); + } else { + hash = -1; + } + cs.end_parse(); + return (id, val, hash); +} + +(cell, int, cell) accept_proposal(cell cfg_dict, cell proposal, int critical?) inline_ref { + var (param_id, param_val, req_hash) = parse_config_proposal(proposal); + cell cur_val = cfg_dict.idict_get_ref(32, param_id); + int cur_hash = null?(cur_val) ? 0 : cell_hash(cur_val); + if ((cur_hash != req_hash) & (req_hash >= 0)) { + ;; current value has incorrect hash, do not apply changes + return (cfg_dict, 0, null()); + } + cell mparams = cfg_dict.idict_get_ref(32, 9); ;; mandatory parameters + var (_, found?) = mparams.idict_get?(32, param_id); + if (found? & param_val.null?()) { + ;; cannot set a mandatory parameter to (null) + return (cfg_dict, 0, null()); + } + cell cparams = cfg_dict.idict_get_ref(32, 10); ;; critical parameters + (_, found?) = cparams.idict_get?(32, param_id); + if (found? < critical?) { + ;; trying to set a critical parameter after a non-critical voting + return (cfg_dict, 0, null()); + } + ;; CHANGE ONE CONFIGURATION PARAMETER (!) + cfg_dict~idict_set_ref(32, param_id, param_val); + return (cfg_dict, param_id, param_val); +} + +(cell, int) perform_proposed_action(cell cfg_dict, int public_key, int param_id, cell param_val) inline_ref { + if (param_id == -999) { + ;; appoint or depose dictator + return (cfg_dict, param_val.null?() ? 0 : param_val.begin_parse().preload_uint(256)); + } + if (param_val.null?()) { + return (cfg_dict, public_key); + } + if (param_id == -1000) { + ;; upgrade code + var cs = param_val.begin_parse(); + var new_code = cs~load_ref(); + set_code(new_code); + var old_code = get_c3(); + set_c3(new_code.begin_parse().bless()); + after_code_upgrade(cs, old_code); + throw(0); + return (cfg_dict, public_key); + } + if (param_id == -1001) { + ;; update elector code + var cs = param_val.begin_parse(); + change_elector_code(cs); + } + return (cfg_dict, public_key); +} + +;; cfg_proposal_status#ce expires:uint32 proposal:^ConfigProposal is_critical:Bool +;; voters:(HashmapE 16 True) remaining_weight:int64 validator_set_id:uint256 +;; rounds_remaining:uint8 wins:uint8 losses:uint8 = ConfigProposalStatus; +(int, cell, int, cell, int, int, slice) unpack_proposal_status(slice cs) inline_ref { + throw_unless(44, cs~load_int(8) == 0xce - 0x100); + return (cs~load_uint(32), cs~load_ref(), cs~load_int(1), cs~load_dict(), cs~load_int(64), cs~load_uint(256), cs); +} + +slice update_proposal_status(slice rest, int weight_remaining, int critical?) inline_ref { + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, _, _, _, _) = get_vote_config(critical?); + var (rounds_remaining, wins, losses) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8)); + losses -= (weight_remaining >= 0); + if (losses > max_losses) { + ;; lost too many times + return null(); + } + rounds_remaining -= 1; + if (rounds_remaining < 0) { + ;; existed for too many rounds + return null(); + } + return begin_cell() + .store_uint(rounds_remaining, 8) + .store_uint(wins, 8) + .store_uint(losses, 8) + .end_cell().begin_parse(); +} + +builder begin_pack_proposal_status(int expires, cell proposal, int critical?, cell voters, int weight_remaining, int vset_id) inline { + return begin_cell() + .store_int(0xce - 0x100, 8) + .store_uint(expires, 32) + .store_ref(proposal) + .store_int(critical?, 1) + .store_dict(voters) + .store_int(weight_remaining, 64) + .store_uint(vset_id, 256); +} + +(cell, cell, int) register_vote(vote_dict, phash, idx, weight) inline_ref { + var (pstatus, found?) = vote_dict.udict_get?(256, phash); + ifnot (found?) { + ;; config proposal not found + return (vote_dict, null(), -1); + } + var (cur_vset, total_weight, _) = get_current_vset(); + int cur_vset_id = cur_vset.cell_hash(); + var (expires, proposal, critical?, voters, weight_remaining, vset_id, rest) = unpack_proposal_status(pstatus); + if (expires <= now()) { + ;; config proposal expired, delete and report not found + vote_dict~udict_delete?(256, phash); + return (vote_dict, null(), -1); + } + if (vset_id != cur_vset_id) { + ;; config proposal belongs to a previous validator set + vset_id = cur_vset_id; + rest = update_proposal_status(rest, weight_remaining, critical?); + voters = null(); + weight_remaining = muldiv(total_weight, 3, 4); + } + if (rest.null?()) { + ;; discard proposal (existed for too many rounds, or too many losses) + vote_dict~udict_delete?(256, phash); + return (vote_dict, null(), -1); + } + var (_, found?) = voters.udict_get?(16, idx); + if (found?) { + ;; already voted for this proposal, ignore vote + return (vote_dict, null(), -2); + } + ;; register vote + voters~udict_set_builder(16, idx, begin_cell().store_uint(now(), 32)); + int old_wr = weight_remaining; + weight_remaining -= weight; + if ((weight_remaining ^ old_wr) >= 0) { + ;; not enough votes, or proposal already accepted in this round + ;; simply update weight_remaining + vote_dict~udict_set_builder(256, phash, begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id).store_slice(rest)); + return (vote_dict, null(), 2); + } + ;; proposal wins in this round + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, _, _, _, _) = get_vote_config(critical?); + var (rounds_remaining, wins, losses) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8)); + wins += 1; + if (wins >= min_wins) { + ;; proposal is accepted, remove and process + vote_dict~udict_delete?(256, phash); + return (vote_dict, proposal, 6 - critical?); + } + ;; update proposal info + vote_dict~udict_set_builder(256, phash, + begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id) + .store_uint(rounds_remaining, 8) + .store_uint(wins, 8) + .store_uint(losses, 8)); + return (vote_dict, null(), 2); +} + +int proceed_register_vote(phash, idx, weight) impure inline_ref { + var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data(); + (vote_dict, var accepted_proposal, var status) = register_vote(vote_dict, phash, idx, weight); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + ifnot (accepted_proposal.null?()) { + var critical? = 6 - status; + (cfg_dict, var param_id, var param_val) = accept_proposal(cfg_dict, accepted_proposal, critical?); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + if (param_id) { + commit(); + (cfg_dict, public_key) = perform_proposed_action(cfg_dict, public_key, param_id, param_val); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + } + } + return status; +} + +(slice, int) scan_proposal(int phash, slice pstatus) inline_ref { + var (cur_vset, total_weight, _) = get_current_vset(); + int cur_vset_id = cur_vset.cell_hash(); + var (expires, proposal, critical?, voters, weight_remaining, vset_id, rest) = unpack_proposal_status(pstatus); + if (expires <= now()) { + ;; config proposal expired, delete + return (null(), true); + } + if (vset_id == cur_vset_id) { + ;; config proposal already processed or voted for in this round, change nothing + return (pstatus, false); + } + ;; config proposal belongs to a previous validator set + vset_id = cur_vset_id; + rest = update_proposal_status(rest, weight_remaining, critical?); + voters = null(); + weight_remaining = muldiv(total_weight, 3, 4); + if (rest.null?()) { + ;; discard proposal (existed for too many rounds, or too many losses) + return (null(), true); + } + ;; return updated proposal + return (begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id).store_slice(rest).end_cell().begin_parse(), true); +} + +cell scan_random_proposal(cell vote_dict) inline_ref { + var (phash, pstatus, found?) = vote_dict.udict_get_nexteq?(256, random()); + ifnot (found?) { + return vote_dict; + } + (pstatus, var changed?) = scan_proposal(phash, pstatus); + if (changed?) { + if (pstatus.null?()) { + vote_dict~udict_delete?(256, phash); + } else { + vote_dict~udict_set(256, phash, pstatus); + } + } + return vote_dict; +} + +int register_voting_proposal(slice cs, int msg_value) impure inline_ref { + var (expire_at, proposal, critical?) = (cs~load_uint(32), cs~load_ref(), cs~load_int(1)); + if (expire_at >> 30) { + expire_at -= now(); + } + var (param_id, param_val, hash) = parse_config_proposal(proposal); + if (hash >= 0) { + cell cur_val = config_param(param_id); + int cur_hash = null?(cur_val) ? 0 : cell_hash(cur_val); + if (cur_hash != hash) { + hash = -0xe2646356; ;; bad current value + } + } else { + var m_params = config_param(9); + var (_, found?) = m_params.idict_get?(32, param_id); + if (found?) { + hash = -0xcd506e6c; ;; cannot set mandatory parameter to null + } + } + ;; Note, in config contract currently deployed in mainnet, this limit is 256 + if (param_val.cell_depth() >= 128) { + hash = -0xc2616456; ;; bad value + } + if (hash < -1) { + return hash; ;; return error if any + } + ifnot (critical?) { + var crit_params = config_param(10); + var (_, found?) = crit_params.idict_get?(32, param_id); + if (found?) { + hash = -0xc3726954; ;; trying to set a critical parameter without critical flag + } + } + if (hash < -1) { + return hash; + } + ;; obtain vote proposal configuration + var vote_cfg = get_vote_config(critical?); + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price) = vote_cfg; + if (expire_at < min_store_sec) { + return -0xc5787069; ;; expired + } + expire_at = min(expire_at, max_store_sec); + ;; compute price + var (_, bits, refs) = compute_data_size(param_val, 1024); + var pps = bit_price * (bits + 1024) + cell_price * (refs + 2); + var price = pps * expire_at; + expire_at += now(); + var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data(); + int phash = proposal.cell_hash(); + var (pstatus, found?) = vote_dict.udict_get?(256, phash); + if (found?) { + ;; proposal already exists; we can only extend it + var (expires, r_proposal, r_critical?, voters, weight_remaining, vset_id, rest) = unpack_proposal_status(pstatus); + if (r_critical? != critical?) { + return -0xc3726955; ;; cannot upgrade critical parameter to non-critical... + } + if (expires >= expire_at) { + return -0xc16c7245; ;; proposal already exists + } + ;; recompute price + price = pps * (expire_at - expires + 16384); + if (msg_value - price < (1 << 30)) { + return -0xf0617924; ;; need more money + } + ;; update expiration time + vote_dict~udict_set_builder(256, phash, begin_pack_proposal_status(expire_at, r_proposal, r_critical?, voters, weight_remaining, vset_id).store_slice(rest)); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + return price; + } + if (msg_value - price < (1 << 30)) { + return -0xf0617924; ;; need more money + } + ;; obtain current validator set data + var (vset, total_weight, _) = get_current_vset(); + int weight_remaining = muldiv(total_weight, 3, 4); + ;; create new proposal + vote_dict~udict_set_builder(256, phash, + begin_pack_proposal_status(expire_at, proposal, critical?, null(), weight_remaining, vset.cell_hash()) + .store_uint(max_tot_rounds, 8).store_uint(0, 16)); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + return price; +} + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { var cs = in_msg_cell.begin_parse(); var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool var s_addr = cs~load_msg_addr(); @@ -81,135 +497,38 @@ return send_error(s_addr, query_id, 0xee764f6f); } } + if (tag == 0x6e565052) { + ;; new voting proposal + var price = register_voting_proposal(in_msg, msg_value); + int mode = 64; + int ans_tag = - price; + if (price >= 0) { + ;; ok, debit price + raw_reserve(price, 4); + ans_tag = 0xee565052; + mode = 128; + } + return send_answer(s_addr, query_id, ans_tag, mode); + } + if (tag == 0x566f7465) { + ;; vote for a configuration proposal + var signature = in_msg~load_bits(512); + var msg_body = in_msg; + var (sign_tag, idx, phash) = (in_msg~load_uint(32), in_msg~load_uint(16), in_msg~load_uint(256)); + in_msg.end_parse(); + throw_unless(37, sign_tag == 0x566f7445); + var (vdescr, total_weight) = get_validator_descr(idx); + var (val_pubkey, weight) = unpack_validator_descr(vdescr); + throw_unless(34, check_data_signature(msg_body, signature, val_pubkey)); + int res = proceed_register_vote(phash, idx, weight); + return send_confirmation(s_addr, query_id, res + 0xd6745240); + } ;; if tag is non-zero and its higher bit is zero, throw an exception (the message is an unsupported query) ;; to bounce message back to sender throw_unless(37, (tag == 0) | (tag & (1 << 31))); ;; do nothing for other internal messages } -;; forward a message to elector smart contract to make it upgrade its code -() change_elector_code(slice cs) impure { - var dest_addr = config_param(1).begin_parse().preload_uint(256); - var query_id = now(); - send_raw_message(begin_cell() - .store_uint(0xc4ff, 17) - .store_uint(dest_addr, 256) - .store_grams(1 << 30) ;; ~ 1 Gram (will be returned back) - .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) - .store_uint(0x4e436f64, 32) ;; action - .store_uint(query_id, 64) - .store_slice(cs) - .end_cell(), 0); -} - -() after_code_upgrade(slice param, cont old_code) impure method_id(1666) { -} - -_ perform_action(cfg_dict, public_key, action, cs) { - if (action == 0x43665021) { - ;; change one configuration parameter - var param_index = cs~load_uint(32); - var param_value = cs~load_ref(); - cs.end_parse(); - cfg_dict~idict_set_ref(32, param_index, param_value); - return (cfg_dict, public_key); - } elseif (action == 0x4e436f64) { - ;; change configuration smart contract code - var new_code = cs~load_ref(); - set_code(new_code); - var old_code = get_c3(); - set_c3(new_code.begin_parse().bless()); - after_code_upgrade(cs, old_code); - throw(0); - return (cfg_dict, public_key); - } elseif (action == 0x50624b21) { - ;; change configuration master public key - public_key = cs~load_uint(256); - cs.end_parse(); - return (cfg_dict, public_key); - } elseif (action == 0x4e43ef05) { - ;; change election smart contract code - change_elector_code(cs); - return (cfg_dict, public_key); - } else { - throw_if(32, action); - return (cfg_dict, public_key); - } -} - -(slice, int) get_validator_descr(int idx) inline_ref { - var vset = config_param(34); - if (vset.null?()) { - return (null(), 0); - } - var cs = begin_parse(vset); - ;; validators_ext#12 utime_since:uint32 utime_until:uint32 - ;; total:(## 16) main:(## 16) { main <= total } { main >= 1 } - ;; total_weight:uint64 - throw_unless(40, cs~load_uint(8) == 0x12); - cs~skip_bits(32 + 32 + 16 + 16); - int total_weight = cs~load_uint(64); - var dict = begin_cell().store_slice(cs).end_cell(); - var (value, _) = dict.udict_get?(16, idx); - return (value, total_weight); -} - -(int, int) unpack_validator_descr(slice cs) inline { - ;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey; - ;; validator#53 public_key:SigPubKey weight:uint64 = ValidatorDescr; - ;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr; - throw_unless(41, (cs~load_uint(8) & ~ 0x20) == 0x53); - throw_unless(41, cs~load_uint(32) == 0x8e81278a); - return (cs~load_uint(256), cs~load_uint(64)); -} - -(cell, int, int, slice) new_proposal(cs) inline { - return (null(), 0, 0, cs); -} - -(cell, int, int, slice) unpack_proposal(slice cs) inline { - return (cs~load_dict(), cs~load_uint(64), cs~load_uint(256), cs); -} - -builder pack_proposal(cell voters, int sum_weight, int vset_id, slice body) inline { - return begin_cell().store_dict(voters).store_uint(sum_weight, 64).store_uint(vset_id, 256).store_slice(body); -} - -(cell, slice) register_vote(vote_dict, action, cs, idx, weight, total_weight, cur_vset_id) { - int hash = 0; - int found? = 0; - var entry = null(); - if (action & 1) { - hash = slice_hash(cs); - (entry, found?) = vote_dict.udict_get?(256, hash); - } else { - hash = cs.preload_uint(256); - (entry, found?) = vote_dict.udict_get?(256, hash); - throw_unless(42, found?); - } - var (voters, sum_weight, vset_id, body) = found? ? unpack_proposal(entry) : (null(), 0, cur_vset_id, cs); - if (vset_id != cur_vset_id) { - voters = null(); - sum_weight = 0; - vset_id = cur_vset_id; - } - var (_, found?) = voters.udict_get?(16, idx); - ifnot (found?) { - voters~udict_set_builder(16, idx, begin_cell().store_uint(32, now())); - sum_weight += weight; - if (sum_weight * 3 > total_weight * 2) { - ;; proposal accepted - vote_dict~udict_delete?(256, hash); - return (vote_dict, body); - } else { - vote_dict~udict_set_builder(256, hash, pack_proposal(voters, sum_weight, cur_vset_id, body)); - return (vote_dict, null()); - } - } else { - return (vote_dict, null()); - } -} - () recv_external(slice in_msg) impure { var signature = in_msg~load_bits(512); var cs = in_msg; @@ -217,28 +536,26 @@ builder pack_proposal(cell voters, int sum_weight, int vset_id, slice body) inli int msg_seqno = cs~load_uint(32); var valid_until = cs~load_uint(32); throw_if(35, valid_until < now()); + throw_if(39, slice_depth(cs) > 128); var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data(); throw_unless(33, msg_seqno == stored_seqno); - ifnot ((action - 0x566f7465) & -2) { - var idx = cs~load_uint(16); + if (action == 0x566f7465) { + ;; vote for a configuration proposal + var (idx, phash) = (cs~load_uint(16), cs~load_uint(256)); + cs.end_parse(); var (vdescr, total_weight) = get_validator_descr(idx); var (val_pubkey, weight) = unpack_validator_descr(vdescr); - throw_unless(34, check_signature(slice_hash(in_msg), signature, val_pubkey)); + throw_unless(34, check_data_signature(in_msg, signature, val_pubkey)); accept_message(); - stored_seqno += 1; + stored_seqno = (stored_seqno + 1) % (1 << 32); store_data(cfg_dict, stored_seqno, public_key, vote_dict); commit(); - (vote_dict, var accepted) = register_vote(vote_dict, action, cs, idx, weight, total_weight, config_param(34).cell_hash()); - store_data(cfg_dict, stored_seqno, public_key, vote_dict); - ifnot (accepted.null?()) { - (cfg_dict, public_key) = perform_action(cfg_dict, public_key, accepted~load_uint(32), accepted); - store_data(cfg_dict, stored_seqno, public_key, vote_dict); - } + proceed_register_vote(phash, idx, weight); return (); } throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key)); accept_message(); - stored_seqno += 1; + stored_seqno = (stored_seqno + 1) % (1 << 32); store_data(cfg_dict, stored_seqno, public_key, vote_dict); commit(); (cfg_dict, public_key) = perform_action(cfg_dict, public_key, action, cs); @@ -246,11 +563,10 @@ builder pack_proposal(cell voters, int sum_weight, int vset_id, slice body) inli } () run_ticktock(int is_tock) impure { - var cs = begin_parse(get_data()); - var cfg_dict = cs~load_ref(); + var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data(); int kl = 32; - ;; cfg_dict~idict_set_ref(kl, -17, begin_cell().store_uint(now() >> 16, 32).end_cell()); var next_vset = cfg_dict.idict_get_ref(kl, 36); + var updated? = false; ifnot (next_vset.null?()) { ;; check whether we have to set next_vset as the current validator set var ds = next_vset.begin_parse(); @@ -262,12 +578,69 @@ builder pack_proposal(cell voters, int sum_weight, int vset_id, slice body) inli var cur_vset = cfg_dict~idict_set_get_ref(kl, 34, next_vset); ;; next_vset -> cur_vset cfg_dict~idict_set_get_ref(kl, 32, cur_vset); ;; cur_vset -> prev_vset cfg_dict~idict_delete?(kl, 36); ;; (null) -> next_vset + updated? = true; } } } - set_data(begin_cell().store_ref(cfg_dict).store_slice(cs).end_cell()); + ifnot (updated?) { + ;; if nothing has been done so far, scan a random voting proposal instead + vote_dict = scan_random_proposal(vote_dict); + } + ;; save data and return + return store_data(cfg_dict, stored_seqno, public_key, vote_dict); } -int seqno() impure method_id { +int seqno() method_id { return get_data().begin_parse().preload_uint(32); } + +_ unpack_proposal(slice pstatus) inline_ref { + (int expires, cell proposal, int critical?, cell voters, int weight_remaining, int vset_id, slice rest) = unpack_proposal_status(pstatus); + var voters_list = null(); + var voter_id = (1 << 32); + do { + (voter_id, _, var f) = voters.udict_get_prev?(16, voter_id); + if (f) { + voters_list = cons(voter_id, voters_list); + } + } until (~ f); + ;; Note there is a bug in config contract currently deployed in mainnet: + ;; wins and losses are messed up + var (rounds_remaining, wins, losses) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8)); + rest.end_parse(); + var (param_id, param_val, param_hash) = parse_config_proposal(proposal); + return [expires, critical?, [param_id, param_val, param_hash], vset_id, voters_list, weight_remaining, rounds_remaining, losses, wins]; +} + +_ get_proposal(int phash) method_id { + (_, _, _, var vote_dict) = load_data(); + var (pstatus, found?) = vote_dict.udict_get?(256, phash); + ifnot (found?) { + return null(); + } + return unpack_proposal(pstatus); +} + +_ list_proposals() method_id { + (_, _, _, var vote_dict) = load_data(); + var phash = (1 << 255) + ((1 << 255) - 1); + var list = null(); + do { + (phash, var pstatus, var f) = vote_dict.udict_get_prev?(256, phash); + if (f) { + list = cons([phash, unpack_proposal(pstatus)], list); + } + } until (~ f); + return list; +} + +_ proposal_storage_price(int critical?, int seconds, int bits, int refs) method_id { + var cfg_dict = get_data().begin_parse().preload_ref(); + var cparam11 = cfg_dict.idict_get_ref(32, 11); + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price) = get_vote_config_internal(critical?, cparam11); + if (seconds < min_store_sec) { + return -1; + } + seconds = min(seconds, max_store_sec); + return (bit_price * (bits + 1024) + cell_price * (refs + 2)) * seconds; +} diff --git a/crypto/smartcont/config-proposal-vote-req.fif b/crypto/smartcont/config-proposal-vote-req.fif new file mode 100644 index 00000000..d1eaff9d --- /dev/null +++ b/crypto/smartcont/config-proposal-vote-req.fif @@ -0,0 +1,46 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include +"GetOpt.fif" include + +"validator-to-sign.req" =: savefile +false =: internal? +-1 =: seqno +-1 =: expire-at + +{ ."usage: " @' $0 type ." (-i | ) []" cr + ."Creates an unsigned request expiring at unixtime to vote for configuration proposal (decimal; prefix with '0x' if needed) on behalf of validator with zero-based index in current validator set (as stored in configuration parameter 34)." cr + ."If -i is selected, prepares unsigned request to be incorporated into an internal message instead." cr + ."The result is saved into (" savefile type ." by default) and output in hexadecimal form, to be signed later by the validator public key" cr 1 halt +} : usage + +$# dup 3 < swap 5 > or ' usage if +5 :$1..n +{ $* @ dup null? ' dup ' uncons cond $* ! } : $next + +$1 "-i" $= { $next drop true =: internal? } if + +internal? { + $next parse-int =: seqno + $next parse-int dup 30 1<< < { now + 1000 + 2000 /c 2000 * } if + dup =: expire-at + dup now <= abort"expiration time must be in the future" + 32 ufits not abort"invalid expiration time" +} ifnot +$# dup 2 < swap 3 > or ' usage if +$1 parse-int dup =: val-idx + 16 ufits not abort"validator index out of range" +$2 parse-int dup =: prop-hash + 256 ufits not abort"invalid proposal hash" +$3 savefile replace-if-null =: savefile + +."Creating a request " internal? { ."with seqno " seqno . ."expiring at " expire-at . } ifnot +."to vote for configuration proposal 0x" prop-hash 64x. +."on behalf of validator with index " val-idx . cr + +internal? { B{566f7445} } { + B{566f7465} seqno 32 u>B B+ expire-at 32 u>B B+ +} cond +val-idx 16 u>B B+ prop-hash 256 u>B B+ +dup Bx. cr +dup B>base64url type cr +savefile tuck B>file ."Saved to file " type cr diff --git a/crypto/smartcont/config-proposal-vote-signed.fif b/crypto/smartcont/config-proposal-vote-signed.fif new file mode 100644 index 00000000..5aaf1270 --- /dev/null +++ b/crypto/smartcont/config-proposal-vote-signed.fif @@ -0,0 +1,72 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include +"GetOpt.fif" include + +"vote-query.boc" =: savefile +false =: internal? +-1 =: seqno +0 0 2=: config-addr +-1 =: expire-at + +{ ."usage: " @' $0 type ." (-i | ) []" cr + ."Creates an external message addressed to the configuration smart contract and current sequence number containing a signed request expiring at unixtime to vote for configuration proposal (decimal; prefix with '0x' if needed) on behalf of validator with zero-based index and (Base64) public key in current validator set (as stored in configuration parameter 34)" cr + ." must be the base64 representation of Ed25519 signature of the previously generated unsigned request by means of " cr + ."If -i is selected, instead generates the body of an internal message to be sent to the configuration smart contract from any smartcontract residing in the masterchain" cr + ."The result is saved into (" savefile type ." by default), to be sent later by the lite-client" cr 1 halt +} : usage + +$# dup 5 < swap 8 > or ' usage if +8 :$1..n + +{ $* @ dup null? ' dup ' uncons cond $* ! } : $next +$1 "-i" $= { $next drop true =: internal? "vote-msg-body.boc" =: savefile } if + +internal? { + $next true parse-load-address drop over 1+ abort"configuration smart contract must be in masterchain" + 2=: config-addr + $next parse-int =: seqno + $next parse-int dup 30 1<< < { now + 1000 + 2000 /c 2000 * } if + dup =: expire-at + dup now <= abort"expiration time must be in the future" + 32 ufits not abort"invalid expiration time" +} ifnot +$# dup 4 < swap 5 > or ' usage if +$1 parse-int dup =: val-idx + 16 ufits not abort"validator index out of range" +$2 parse-int dup =: prop-hash + 256 ufits not abort"invalid proposal hash" +$3 base64>B dup Blen 36 <> abort"validator Ed25519 public key must be exactly 36 bytes long" + 32 B>u@+ 0xC6B41348 <> abort"invalid Ed25519 public key: unknown magic number" + =: pubkey +$4 base64>B dup Blen 64 <> abort"validator Ed25519 signature must be exactly 64 bytes long" + =: signature +$5 savefile replace-if-null =: savefile + +internal? { + ."Creating the body of an internal message to be sent to the configuration smart contract" cr } +{ ."Creating an external message to configuration smart contract " + config-addr 2dup 6 .Addr ." = " .addr cr +} cond +."containing a signed request " internal? { ."expiring at " expire-at . } ifnot +."to vote for configuration proposal 0x" prop-hash 64x. +."on behalf of validator with index " val-idx . "and public key" pubkey Bx. cr + +internal? { B{566f7445} } { + B{566f7465} seqno 32 u>B B+ expire-at 32 u>B B+ +} cond +val-idx 16 u>B B+ prop-hash 256 u>B B+ dup =: to_sign +."String to sign is " Bx. cr + +to_sign signature pubkey ed25519_chksign not abort"Ed25519 signature is invalid" +."Provided a valid Ed25519 signature " signature Bx. ." with validator public key " pubkey Bx. cr + +now 32 << prop-hash 32 1<< mod + =: query-id + + +cr +internal? { ."Internal message body is " } { ."External message is " } cond +dup B savefile tuck B>file ."Saved to file " type cr diff --git a/crypto/smartcont/create-config-proposal.fif b/crypto/smartcont/create-config-proposal.fif new file mode 100644 index 00000000..8dbfdd27 --- /dev/null +++ b/crypto/smartcont/create-config-proposal.fif @@ -0,0 +1,67 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include +"GetOpt.fif" include + +{ show-options-help 1 halt } : usage +86400 30 * =: expire-in +false =: critical +-1 =: old-hash + +begin-options + " [-x ] [-c] [-H ] []" +cr +tab + +"Creates a new configuration proposal for setting configuration parameter to (`null` means no new value), " + +"and saves it as an internal message body into .boc ('config-msg-body.boc' by default)" + disable-digit-options generic-help-setopt + "c" "--critical" { true =: critical } short-long-option + "Creates a critical parameter change proposal" option-help + "x" "--expires-in" { parse-int =: expire-in } short-long-option-arg + "Sets proposal expiration time in seconds (default " expire-in (.) $+ +")" option-help + "H" "--old-hash" { (hex-number) 1 = not .s abort"256-bit hex number expected as hash" =: old-hash } + short-long-option-arg + "Sets the required cell hash of existing parameter value (0 means no value)" option-help + "h" "--help" { usage } short-long-option + "Shows a help message" option-help +parse-options + +$# dup 2 < swap 3 > or ' usage if +3 :$1..n + +$1 parse-int dup =: param-idx + 32 fits not abort"parameter index out of range" +$2 =: boc-filename +$3 "config-msg-body.boc" replace-if-null =: savefile +expire-in now + =: expire-at + +boc-filename dup "null" $= { + ."New value of configuration parameter" param-idx . ."is null" cr drop null +} { + ."Loading new value of configuration parameter " param-idx . ."from file " dup type cr + boc-filename file>B B>boc + dup = tuck 1 i, -rot { 256 u, } { drop } cond b> ref, + critical 1 i, b> + +dup ."resulting internal message body: " B dup Bx. cr + +param-value dup null? { drop } { + totalcsize swap ."(a total of " . ."data bits, " . ."cell references -> " + drop dup Blen . ."BoC data bytes)" cr +} cond + +savefile tuck B>file +."(Saved to file " type .")" cr diff --git a/crypto/smartcont/create-config-upgrade-proposal.fif b/crypto/smartcont/create-config-upgrade-proposal.fif new file mode 100644 index 00000000..35caa64b --- /dev/null +++ b/crypto/smartcont/create-config-upgrade-proposal.fif @@ -0,0 +1,65 @@ +#!/usr/bin/fift -s +"Asm.fif" include +"TonUtil.fif" include +"GetOpt.fif" include + +// only the next three lines differ in elector/configuration smart contract upgrades +"configuration" =: name +"auto/config-code.fif" =: src-file +-1000 =: param-idx + +86400 30 * =: expire-in +true =: critical +-1 =: old-hash + +{ show-options-help 1 halt } : usage +{ name $+ } : +name + +begin-options + " [-s ] [-x ] [-H ] []" +cr +tab + +"Creates a new configuration proposal for upgrading the " +name +" smart contract code to ('" src-file $+ +"' by default), " + +"and saves it as an internal message body into .boc ('config-msg-body.boc' by default)" + disable-digit-options generic-help-setopt + "x" "--expires-in" { parse-int =: expire-in } short-long-option-arg + "Sets proposal expiration time in seconds (default " expire-in (.) $+ +")" option-help + "s" "--source" { =: src-file } short-long-option-arg + "Fift assembler source file for the new " +name +" smart contract code ('" + src-file $+ +"' by default)" option-help + "H" "--old-hash" { (hex-number) not abort"256-bit hex number expected as hash" =: old-hash } + short-long-option-arg + "Sets the required cell hash of existing parameter value (0 means no value)" option-help + "h" "--help" { usage } short-long-option + "Shows a help message" option-help +parse-options + +$# 1 > ' usage if +1 :$1..n + +$1 "config-msg-body.boc" replace-if-null =: savefile +expire-in now + =: expire-at + +."Assembling new " name type ." smart contract code from " src-file type cr +src-file include +dup abort"not valid smart contract code" + =: param-value + +critical { ."Critical" } { ."Non-critical" } cond +." configuration proposal for configuration parameter " param-idx . +."will expire at " expire-at . ."(in " expire-in . ."seconds)" cr + +now 32 << param-idx + =: query-id +."Query id is " query-id . cr + +// create message body += tuck 1 i, -rot { 256 u, } { drop } cond b> ref, + critical 1 i, b> + +dup ."resulting internal message body: " B dup Bx. cr +param-value totalcsize swap ."(a total of " . ."data bits, " . ."cell references -> " +drop dup Blen . ."BoC data bytes)" cr + +savefile tuck B>file +."(Saved to file " type .")" cr diff --git a/crypto/smartcont/create-elector-upgrade-proposal.fif b/crypto/smartcont/create-elector-upgrade-proposal.fif new file mode 100644 index 00000000..7eba0b60 --- /dev/null +++ b/crypto/smartcont/create-elector-upgrade-proposal.fif @@ -0,0 +1,65 @@ +#!/usr/bin/fift -s +"Asm.fif" include +"TonUtil.fif" include +"GetOpt.fif" include + +// only the next three lines differ in elector/configuration smart contract upgrades +"elector" =: name +"auto/elector-code.fif" =: src-file +-1001 =: param-idx + +86400 30 * =: expire-in +true =: critical +-1 =: old-hash + +{ show-options-help 1 halt } : usage +{ name $+ } : +name + +begin-options + " [-s ] [-x ] [-H ] []" +cr +tab + +"Creates a new configuration proposal for upgrading the " +name +" smart contract code to ('" src-file $+ +"' by default), " + +"and saves it as an internal message body into .boc ('config-msg-body.boc' by default)" + disable-digit-options generic-help-setopt + "x" "--expires-in" { parse-int =: expire-in } short-long-option-arg + "Sets proposal expiration time in seconds (default " expire-in (.) $+ +")" option-help + "s" "--source" { =: src-file } short-long-option-arg + "Fift assembler source file for the new " +name +" smart contract code ('" + src-file $+ +"' by default)" option-help + "H" "--old-hash" { (hex-number) not abort"256-bit hex number expected as hash" =: old-hash } + short-long-option-arg + "Sets the required cell hash of existing parameter value (0 means no value)" option-help + "h" "--help" { usage } short-long-option + "Shows a help message" option-help +parse-options + +$# 1 > ' usage if +1 :$1..n + +$1 "config-msg-body.boc" replace-if-null =: savefile +expire-in now + =: expire-at + +."Assembling new " name type ." smart contract code from " src-file type cr +src-file include +dup abort"not valid smart contract code" + =: param-value + +critical { ."Critical" } { ."Non-critical" } cond +." configuration proposal for configuration parameter " param-idx . +."will expire at " expire-at . ."(in " expire-in . ."seconds)" cr + +now 32 << param-idx + =: query-id +."Query id is " query-id . cr + +// create message body += tuck 1 i, -rot { 256 u, } { drop } cond b> ref, + critical 1 i, b> + +dup ."resulting internal message body: " B dup Bx. cr +param-value totalcsize swap ."(a total of " . ."data bits, " . ."cell references -> " +drop dup Blen . ."BoC data bytes)" cr + +savefile tuck B>file +."(Saved to file " type .")" cr diff --git a/crypto/smartcont/dns-auto-code.fc b/crypto/smartcont/dns-auto-code.fc new file mode 100644 index 00000000..949f1571 --- /dev/null +++ b/crypto/smartcont/dns-auto-code.fc @@ -0,0 +1,536 @@ +{- + Adapted from original version written by: + /------------------------------------------------------------------------\ + | Created for: Telegram (Open Network) Blockchain Contest | + | Task 2: DNS Resolver (Automatically registering) | + >------------------------------------------------------------------------< + | Author: Oleksandr Murzin (tg: @skydev / em: alexhacker64@gmail.com) | + | October 2019 | + \------------------------------------------------------------------------/ + Updated to actual DNS standard version by starlightduck in 2022 +-} + +;;===========================================================================;; +;; Custom ASM instructions ;; +;;===========================================================================;; + +cell udict_get_ref_(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETOPTREF"; + +;;===========================================================================;; +;; Utility functions ;; +;;===========================================================================;; + +{- + Data structure: + Root cell: [OptRef<1b+1r?>:HashmapUInt<32b>,CatTable>:domains] + [OptRef<1b+1r?>:Hashmap(Time|Hash128)->Slice(DomName)>:gc] + [UInt<32b>:stdperiod] [Gram:PPReg] [Gram:PPCell] [Gram:PPBit] + [UInt<32b>:lasthousekeeping] + := HashmapE 256 (~~16~~) ^DNSRecord + + STORED DOMAIN NAME SLICE FORMAT: (#ZeroChars<7b>) (Domain name value) + #Zeros allows to simultaneously store, for example, com\0 and com\0google\0 + That will be stored as \1com\0 and \2com\0google\0 (pfx tree has restricitons) + This will allow to resolve more specific requests to subdomains, and resort + to parent domain next resolver lookup if subdomain is not found + com\0goo\0 lookup will, for example look up \2com\0goo\0 and then + \1com\0goo\0 which will return \1com\0 (as per pfx tree) with -1 cat +-} + +(cell, cell, cell, [int, int, int, int], int, int) load_data() inline_ref { + slice cs = get_data().begin_parse(); + return ( + cs~load_ref(), ;; control data + cs~load_dict(), ;; pfx tree: domains data and exp + cs~load_dict(), ;; gc auxillary with expiration and 128-bit hash slice + [ cs~load_uint(30), ;; length of this period of time in seconds + cs~load_grams(), ;; standard payment for registering a new subdomain + cs~load_grams(), ;; price paid for each cell (PPC) + cs~load_grams() ], ;; and bit (PPB) + cs~load_uint(32), ;; next housekeeping to be done at + cs~load_uint(32) ;; last housekeeping done at + ); +} + +(int, int, int, int) load_prices() inline_ref { + slice cs = get_data().begin_parse(); + (cs~load_ref(), cs~load_dict(), cs~load_dict()); + return (cs~load_uint(30), cs~load_grams(), cs~load_grams(), cs~load_grams()); +} + +() store_data(cell ctl, cell dd, cell gc, prices, int nhk, int lhk) impure { + var [sp, ppr, ppc, ppb] = prices; + set_data(begin_cell() + .store_ref(ctl) ;; control data + .store_dict(dd) ;; domains data and exp + .store_dict(gc) ;; keyed expiration time and 128-bit hash slice + .store_uint(sp, 30) ;; standard period + .store_grams(ppr) ;; price per registration + .store_grams(ppc) ;; price per cell + .store_grams(ppb) ;; price per bit + .store_uint(nhk, 32) ;; next housekeeping + .store_uint(lhk, 32) ;; last housekeeping + .end_cell()); +} + +global var query_info; + +() send_message(slice addr, int tag, int query_id, + int body, int grams, int mode) impure { + ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + ;; src:MsgAddress -> 011000 0x18 + var msg = begin_cell() + .store_uint (0x18, 6) + .store_slice(addr) + .store_grams(grams) + .store_uint (0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint (tag, 32) + .store_uint (query_id, 64); + if (body >= 0) { + msg~store_uint(body, 32); + } + send_raw_message(msg.end_cell(), mode); +} + +() send_error(int error_code) impure { + var (addr, query_id, op) = query_info; + return send_message(addr, error_code, query_id, op, 0, 64); +} + +() send_ok(int price) impure { + raw_reserve(price, 4); + var (addr, query_id, op) = query_info; + return send_message(addr, 0xef6b6179, query_id, op, 0, 128); +} + +() housekeeping(cell ctl, cell dd, cell gc, prices, int nhk, int lhk, int max_steps) impure { + int n = now(); + if (n < max(nhk, lhk + 60)) { ;; housekeeping cooldown: 1 minute + ;; if housekeeping was done recently, or if next housekeeping is in the future, just save + return store_data(ctl, dd, gc, prices, nhk, lhk); + } + ;; need to do some housekeeping - maybe remove entry with + ;; least expiration but only if it is already expired + ;; no iterating and deleting all to not put too much gas gc + ;; burden on any random specific user request + ;; over time it will do the garbage collection required + (int mkey, _, int found?) = gc.udict_get_min?(256); + while (found? & max_steps) { ;; no short circuit optimization, two nested ifs + nhk = (mkey >> (256 - 32)); + if (nhk < n) { + int key = mkey % (1 << (256 - 32)); + (slice val, found?) = dd.udict_get?(256 - 32, key); + if (found?) { + int exp = val.preload_uint(32); + if (exp <= n) { + dd~udict_delete?(256 - 32, key); + } + } + gc~udict_delete?(256, mkey); + (mkey, _, found?) = gc.udict_get_min?(256); + nhk = (found? ? mkey >> (256 - 32) : 0xffffffff); + max_steps -= 1; + } else { + found? = false; + } + } + store_data(ctl, dd, gc, prices, nhk, n); +} + +int calcprice_internal(slice domain, cell data, ppc, ppb) inline_ref { ;; only for internal calcs + var (_, bits, refs) = compute_data_size(data, 100); ;; 100 cells max + bits += slice_bits(domain) * 2 + (128 + 32 + 32); + return ppc * (refs + 2) + ppb * bits; +} + +int check_owner(cell cat_table, cell owner_info, int src_wc, int src_addr, int strict) inline_ref { + if (strict & cat_table.null?()) { ;; domain not found: return notf | 2^31 + return 0xee6f7466; + } + if (owner_info.null?()) { ;; no owner on this domain: no-2 (in strict mode), ok else + return strict & 0xee6f2d32; + } + var ERR_BAD2 = 0xe2616432; + slice sown = owner_info.begin_parse(); + if (sown.slice_bits() < 16 + 3 + 8 + 256) { ;; bad owner record: bad2 + return ERR_BAD2; + } + if (sown~load_uint(16 + 3) != 0x9fd3 * 8 + 4) { + return ERR_BAD2; + } + (int owner_wc, int owner_addr) = (sown~load_int(8), sown.preload_uint(256)); + if ((owner_wc != src_wc) | (owner_addr != src_addr)) { ;; not owner: nown + return 0xee6f776e; + } + return 0; ;; ok +} + +;;===========================================================================;; +;; Internal message handler (Code 0) ;; +;;===========================================================================;; + +{- + Internal message cell structure: + 8 4 2 1 + int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddressInt dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 + Internal message data structure: + [UInt<32b>:op] [UInt<64b>:query_id] [Ref<1r>:domain] + (if not prolong: [Ref<1r>:value->CatTable]) + +-} + +;; Control operations: permitted only to the owner of this smartcontract +() perform_ctl_op(int op, int src_wc, int src_addr, slice in_msg) impure inline_ref { + var (ctl, domdata, gc, prices, nhk, lhk) = load_data(); + var cs = ctl.begin_parse(); + if ((cs~load_int(8) != src_wc) | (cs~load_uint(256) != src_addr)) { + return send_error(0xee6f776e); + } + if (op == 0x43685072) { ;; ChPr = Change Prices + var (stdper, ppr, ppc, ppb) = (in_msg~load_uint(32), in_msg~load_grams(), in_msg~load_grams(), in_msg~load_grams()); + in_msg.end_parse(); + ;; NB: stdper == 0 -> disable new actions + store_data(ctl, domdata, gc, [stdper, ppr, ppc, ppb], nhk, lhk); + return send_ok(0); + } + var (addr, query_id, op) = query_info; + if (op == 0x4344656c) { ;; CDel = destroy smart contract + ifnot (domdata.null?()) { + ;; domain dictionary not empty, force gc + housekeeping(ctl, domdata, gc, prices, nhk, 1, -1); + } + (ctl, domdata, gc, prices, nhk, lhk) = load_data(); + ifnot (domdata.null?()) { + ;; domain dictionary still not empty, error + return send_error(0xee74656d); + } + return send_message(addr, 0xef6b6179, query_id, op, 0, 128 + 32); + } + if (op == 0x54616b65) { ;; Take = take grams from the contract + var amount = in_msg~load_grams(); + return send_message(addr, 0xef6b6179, query_id, op, amount, 64); + } + return send_error(0xffffffff); +} + +;; Must send at least GR$1 more for possible gas fees! +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + ;; this time very interested in internal messages + if (in_msg.slice_bits() < 32) { + return (); ;; simple transfer or short + } + slice cs = in_msg_cell.begin_parse(); + int flags = cs~load_uint(4); + if (flags & 1) { + return (); ;; bounced messages + } + slice s_addr = cs~load_msg_addr(); + (int src_wc, int src_addr) = s_addr.parse_std_addr(); + int op = in_msg~load_uint(32); + ifnot (op) { + return (); ;; simple transfer with comment + } + int query_id = 0; + if (in_msg.slice_bits() >= 64) { + query_id = in_msg~load_uint(64); + } + + query_info = (s_addr, query_id, op); + + if (op & (1 << 31)) { + return (); ;; an answer to our query + } + if ((op >> 24) == 0x43) { + ;; Control operations + return perform_ctl_op(op, src_wc, src_addr, in_msg); + } + + int qt = (op == 0x72656764) * 1 + (op == 0x70726f6c) * 2 + (op == 0x75706464) * 4 + (op == 0x676f6763) * 8; + ifnot (qt) { ;; unknown query, return error + return send_error(0xffffffff); + } + qt = - qt; + + (cell ctl, cell domdata, cell gc, [int, int, int, int] prices, int nhk, int lhk) = load_data(); + + if (qt == 8) { ;; 0x676f6763 -> GO, GC! go!!! + ;; Manual garbage collection iteration + int max_steps = in_msg~load_int(32); ;; -1 = infty + housekeeping(ctl, domdata, gc, prices, nhk, 1, max_steps); ;; forced + return send_error(0xef6b6179); + } + + slice domain = null(); + cell domain_cell = in_msg~load_maybe_ref(); + int fail = 0; + if (domain_cell.null?()) { + int bytes = in_msg~load_uint(6); + fail = (bytes == 0); + domain = in_msg~load_bits(bytes * 8); + } else { + domain = domain_cell.begin_parse(); + var (bits, refs) = slice_bits_refs(domain); + fail = (refs | ((bits - 8) & (7 - 128))); + } + + ifnot (fail) { + ;; domain must end with \0! no\0 error + fail = domain.slice_last(8).preload_uint(8); + } + if (fail) { + return send_error(0xee6f5c30); + } + + int n = now(); + cell cat_table = cell owner_info = null(); + int key = int exp = int zeros = 0; + slice tail = domain; + repeat (tail.slice_bits() ^>> 3) { + cat_table = null(); + int z = (tail~load_uint(8) == 0); + zeros -= z; + if (z) { + key = (string_hash(domain.skip_last_bits(tail.slice_bits())) >> 32); + var (val, found?) = domdata.udict_get?(256 - 32, key); + if (found?) { + exp = val~load_uint(32); + if (exp >= n) { ;; entry not expired + cell cat_table = val~load_ref(); + val.end_parse(); + ;; update: category length now u256 instead of i16, owner index is now 0 instead of -2 + var (cown, ok) = cat_table.udict_get_ref?(256, 0); + if (ok) { + owner_info = cown; + } + } + } + } + } + + if (zeros > 4) { ;; too much zero chars (overflow): ov\0 + return send_error(0xef765c30); + } + + ;; ########################################################################## + + int err = check_owner(cat_table, owner_info, src_wc, src_addr, qt != 1); + if (err) { + return send_error(err); + } + + ;; ########################################################################## + + ;; load desired data (reuse old for a "prolong" operation) + cell data = null(); + + if (qt != 2) { ;; not a "prolong", load data dictionary + data = in_msg~load_ref(); + ;; basic integrity check of (client-provided) dictionary + ifnot (data.dict_empty?()) { ;; 1000 gas! + ;; update: category length now u256 instead of i16, owner index is now 0 instead of -2 + var (oinfo, ok) = data.udict_get_ref?(256, 0); + if (ok) { + var cs = oinfo.begin_parse(); + throw_unless(31, cs.slice_bits() >= 16 + 3 + 8 + 256); + throw_unless(31, cs.preload_uint(19) == 0x9fd3 * 8 + 4); + } + (_, _, int minok) = data.udict_get_min?(256); ;; update: category length now u256 instead of i16 + (_, _, int maxok) = data.udict_get_max?(256); ;; update: category length now u256 instead of i16 + throw_unless(31, minok & maxok); + } + } else { + data = cat_table; + } + + ;; load prices + var [stdper, ppr, ppc, ppb] = prices; + ifnot (stdper) { ;; smart contract disabled by owner, no new actions + return send_error(0xd34f4646); + } + + ;; compute action price + int price = calcprice_internal(domain, data, ppc, ppb) + (ppr & (qt != 4)); + if (msg_value - (1 << 30) < price) { ;; gr prol | prolong domain + if (exp > n + stdper) { ;; does not expire soon, cannot prolong + return send_error(0xf365726f); + } + domdata~udict_set_builder(256 - 32, key, begin_cell().store_uint(exp + stdper, 32).store_ref(data)); + + int gckeyO = (exp << (256 - 32)) + key; + int gckeyN = gckeyO + (stdper << (256 - 32)); + gc~udict_delete?(256, gckeyO); ;; delete old gc entry, add new + gc~udict_set_builder(256, gckeyN, begin_cell()); + + housekeeping(ctl, domdata, gc, prices, nhk, lhk, 1); + return send_ok(price); + } + + ;; ########################################################################## + if (qt == 1) { ;; 0x72656764 -> regd | register domain + ifnot (cat_table.null?()) { ;; domain already exists: return alre | 2^31 + return send_error(0xe16c7265); + } + int expires_at = n + stdper; + domdata~udict_set_builder(256 - 32, key, begin_cell().store_uint(expires_at, 32).store_ref(data)); + + int gckey = (expires_at << (256 - 32)) | key; + gc~udict_set_builder(256, gckey, begin_cell()); + + housekeeping(ctl, domdata, gc, prices, min(nhk, expires_at), lhk, 1); + return send_ok(price); + } + + ;; ########################################################################## + if (qt == 4) { ;; 0x75706464 -> updd | update domain (data) + domdata~udict_set_builder(256 - 32, key, begin_cell().store_uint(exp, 32).store_ref(data)); + housekeeping(ctl, domdata, gc, prices, nhk, lhk, 1); + return send_ok(price); + } + ;; ########################################################################## + + return (); ;; should NEVER reach this part of code! +} + +;;===========================================================================;; +;; External message handler (Code -1) ;; +;;===========================================================================;; + +() recv_external(slice in_msg) impure { + ;; only for initialization + (cell ctl, cell dd, cell gc, var prices, int nhk, int lhk) = load_data(); + ifnot (lhk) { + accept_message(); + return store_data(ctl, dd, gc, prices, 0xffffffff, now()); + } +} + +;;===========================================================================;; +;; Getter methods ;; +;;===========================================================================;; + +(int, cell, int, slice) dnsdictlookup(slice domain, int nowtime) inline_ref { + (int bits, int refs) = domain.slice_bits_refs(); + throw_if(30, refs | (bits & 7)); ;; malformed input (~ 8n-bit) + ifnot (bits) { + ;; return (0, null(), 0, null()); ;; zero-length input + throw(30); ;; update: throw exception for empty input + } + + int domain_last_byte = domain.slice_last(8).preload_uint(8); + if (domain_last_byte) { + domain = begin_cell().store_slice(domain) ;; append zero byte + .store_uint(0, 8).end_cell().begin_parse(); + bits += 8; + } + if (bits == 8) { + return (0, null(), 8, null()); ;; zero-length input, but with zero byte + ;; update: return 8 as resolved, but with no data + } + int domain_first_byte = domain.preload_uint(8); + if (domain_first_byte == 0) { + ;; update: remove prefix \0 + domain~load_uint(8); + bits -= 8; + } + var ds = get_data().begin_parse(); + (_, cell root) = (ds~load_ref(), ds~load_dict()); + + slice val = null(); + int tail_bits = -1; + slice tail = domain; + + repeat (bits >> 3) { + if (tail~load_uint(8) == 0) { + var key = (string_hash(domain.skip_last_bits(tail.slice_bits())) >> 32); + var (v, found?) = root.udict_get?(256 - 32, key); + if (found?) { + if (v.preload_uint(32) >= nowtime) { ;; entry not expired + val = v; + tail_bits = tail.slice_bits(); + } + } + } + } + + if (val.null?()) { + return (0, null(), 0, null()); ;; failed to find entry in subdomain dictionary + } + + return (val~load_uint(32), val~load_ref(), tail_bits == 0, domain.skip_last_bits(tail_bits)); +} + +;;8m dns-record-value +(int, cell) dnsresolve(slice domain, int category) method_id { + (int exp, cell cat_table, int exact?, slice pfx) = dnsdictlookup(domain, now()); + ifnot (exp) { + return (exact?, null()); ;; update: reuse exact? to return 8 for \0 + } + ifnot (exact?) { ;; incomplete subdomain found, must return next resolver (-1) + category = "dns_next_resolver"H; ;; 0x19f02441ee588fdb26ee24b2568dd035c3c9206e11ab979be62e55558a1d17ff + ;; update: next resolver is now sha256("dns_next_resolver") instead of -1 + } + + int pfx_bits = pfx.slice_bits(); + + ;; pfx.slice_bits() will contain 8m, where m is number of bytes in subdomain + ;; COUNTING the zero byte (if structurally correct: no multiple-ZB keys) + ;; which corresponds to 8m, m=one plus the number of bytes in the subdomain found) + ifnot (category) { + return (pfx_bits, cat_table); ;; return cell with entire dictionary for 0 + } else { + cell cat_found = cat_table.udict_get_ref_(256, category); ;; update: category length now u256 instead of i16 + return (pfx_bits, cat_found); + } +} + +;; getexpiration needs to know the current time to skip any possible expired +;; subdomains in the chain. it will return 0 if not found or expired. +int getexpirationx(slice domain, int nowtime) inline method_id { + (int exp, _, _, _) = dnsdictlookup(domain, nowtime); + return exp; +} + +int getexpiration(slice domain) method_id { + return getexpirationx(domain, now()); +} + +int getstdperiod() method_id { + (int stdper, _, _, _) = load_prices(); + return stdper; +} + +int getppr() method_id { + (_, int ppr, _, _) = load_prices(); + return ppr; +} + +int getppc() method_id { + (_, _, int ppc, _) = load_prices(); + return ppc; +} + +int getppb() method_id { + ( _, _, _, int ppb) = load_prices(); + return ppb; +} + +int calcprice(slice domain, cell val) method_id { ;; only for external gets (not efficient) + (_, _, int ppc, int ppb) = load_prices(); + return calcprice_internal(domain, val, ppc, ppb); +} + +int calcregprice(slice domain, cell val) method_id { ;; only for external gets (not efficient) + (_, int ppr, int ppc, int ppb) = load_prices(); + return ppr + calcprice_internal(domain, val, ppc, ppb); +} diff --git a/crypto/smartcont/dns-manual-code.fc b/crypto/smartcont/dns-manual-code.fc new file mode 100644 index 00000000..555e5952 --- /dev/null +++ b/crypto/smartcont/dns-manual-code.fc @@ -0,0 +1,361 @@ +{- + Originally created by: + /------------------------------------------------------------------------\ + | Created for: Telegram (Open Network) Blockchain Contest | + | Task 3: DNS Resolver (Manually controlled) | + >------------------------------------------------------------------------< + | Author: Oleksandr Murzin (tg: @skydev / em: alexhacker64@gmail.com) | + | October 2019 | + \------------------------------------------------------------------------/ + Updated to actual DNS standard version by starlightduck in 2022 +-} + +;;===========================================================================;; +;; Custom ASM instructions ;; +;;===========================================================================;; + +cell udict_get_ref_(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETOPTREF"; + +(cell, ()) pfxdict_set_ref(cell dict, int key_len, slice key, cell value) { + throw_unless(33, dict~pfxdict_set?(key_len, key, begin_cell().store_maybe_ref(value).end_cell().begin_parse())); + return (dict, ()); +} + +(slice, cell, slice, int) pfxdict_get_ref(cell dict, int key_len, slice key) inline_ref { + (slice pfx, slice val, slice tail, int succ) = dict.pfxdict_get?(key_len, key); + cell res = succ ? val~load_maybe_ref() : null(); + return (pfx, res, tail, succ); +} + +;;===========================================================================;; +;; Utility functions ;; +;;===========================================================================;; + +(int, int, int, cell, cell) load_data() inline_ref { + slice cs = get_data().begin_parse(); + var res = (cs~load_uint(32), cs~load_uint(64), cs~load_uint(256), cs~load_dict(), cs~load_dict()); + cs.end_parse(); + return res; +} + +() store_data(int contract_id, int last_cleaned, int public_key, cell root, old_queries) impure { + set_data(begin_cell() + .store_uint(contract_id, 32) + .store_uint(last_cleaned, 64) + .store_uint(public_key, 256) + .store_dict(root) + .store_dict(old_queries) + .end_cell()); +} + +;;===========================================================================;; +;; Internal message handler (Code 0) ;; +;;===========================================================================;; + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + ;; not interested at all +} + +;;===========================================================================;; +;; External message handler (Code -1) ;; +;;===========================================================================;; + +{- + External message structure: + [Bytes<512b>:signature] [UInt<32b>:seqno] [UInt<6b>:operation] + [Either b0: inline name (<= 58-x Bytes) or b1: reference-stored name) + x depends on operation + Use of 6-bit op instead of 32-bit allows to save 4 bytes for inline name + Inline [Name] structure: [UInt<6b>:length] [Bytes:data] + Operations (continuation of message): + 00 Contract initialization message (only if seqno = 0) (x=-) + 11 VSet: set specified value to specified subdomain->category (x=2) + [UInt<256b>:category] [Name:subdomain] [Cell<1r>:value] + 12 VDel: delete specified subdomain->category (x=2) + [UInt<256b>:category] [Name:subdomain] + 21 DSet: replace entire category dictionary of domain with provided (x=0) + [Name:subdomain] [Cell<1r>:new_cat_table] + 22 DDel: delete entire category dictionary of specified domain (x=0) + [Name:subdomain] + 31 TSet: replace ENTIRE DOMAIN TABLE with the provided tree root cell (x=-) + [Cell<1r>:new_domains_table] + 32 TDel: nullify ENTIRE DOMAIN TABLE (x=-) + 51 OSet: replace owner public key with a new one (x=-) + [UInt<256b>:new_public_key] +-} + +() after_code_upgrade(cell root, slice ops, cont old_code) impure method_id(1666); + +(cell, slice) process_op(cell root, slice ops) inline_ref { + int op = ops~load_uint(6); + if (op < 10) { + ifnot (op) { + ;; 00 Noop: No operation + return (root, ops); + } + if (op == 1) { + ;; 01 SMsg: Send Message + var mode = ops~load_uint(8); + send_raw_message(ops~load_ref(), mode); + return (root, ops); + } + if (op == 9) { + ;; 09 CodeUpgrade + var new_code = ops~load_ref(); + set_code(new_code); + var old_code = get_c3(); + set_c3(new_code.begin_parse().bless()); + after_code_upgrade(root, ops, old_code); + throw(0); + return (root, ops); + } + throw(45); + return (root, ops); + } + int cat = 0; + if (op < 20) { + ;; for operations with codes 10..19 category is required + cat = ops~load_uint(256); ;; update: category length now u256 instead of i16 + } + slice name = null(); ;; any slice value + cell cat_table = null(); + if (op < 30) { + ;; for operations with codes 10..29 name is required + int is_name_ref = (ops~load_uint(1) == 1); + if (is_name_ref) { + ;; name is stored in separate referenced cell + name = ops~load_ref().begin_parse(); + } else { + ;; name is stored inline + int name_len = ops~load_uint(6) * 8; + name = ops~load_bits(name_len); + } + ;; at least one character not counting \0 + throw_unless(38, name.slice_bits() >= 16); + ;; name shall end with \0 + int name_last_byte = name.slice_last(8).preload_uint(8); + throw_if(40, name_last_byte); + ;; count zero separators + int zeros = 0; + slice cname = name; + repeat (cname.slice_bits() ^>> 3) { + int c = cname~load_uint(8); + zeros -= (c == 0); + } + ;; throw_unless(39, zeros == 1); + name = begin_cell().store_uint(zeros, 7).store_slice(name).end_cell().begin_parse(); + } + ;; operation with codes 10..19 manipulate category dict + ;; lets try to find it and store into a variable + ;; operations with codes 20..29 replace / delete dict, no need + if (op < 20) { + ;; lets resolve the name here so as not to duplicate the code + (slice pfx, cell val, slice tail, int succ) = + root.pfxdict_get_ref(1023, name); + if (succ) { + ;; must match EXACTLY to prevent accident changes + throw_unless(35, tail.slice_empty?()); + cat_table = val; + } + ;; otherwise cat_table is null which is reasonable for actions + } + ;; 11 VSet: set specified value to specified subdomain->category + if (op == 11) { + cell new_value = ops~load_maybe_ref(); + cat_table~udict_set_get_ref(256, cat, new_value); ;; update: category length now u256 instead of i16 + root~pfxdict_set_ref(1023, name, cat_table); + return (root, ops); + } + ;; 12 VDel: delete specified subdomain->category value + if (op == 12) { + if (cat_table~udict_delete?(256, cat)) { ;; update: category length now u256 instead of i16 + root~pfxdict_set_ref(1023, name, cat_table); + } + return (root, ops); + } + ;; 21 DSet: replace entire category dictionary of domain with provided + if (op == 21) { + cell new_cat_table = ops~load_maybe_ref(); + root~pfxdict_set_ref(1023, name, new_cat_table); + return (root, ops); + } + ;; 22 DDel: delete entire category dictionary of specified domain + if (op == 22) { + root~pfxdict_delete?(1023, name); + return (root, ops); + } + ;; 31 TSet: replace ENTIRE DOMAIN TABLE with the provided tree root cell + if (op == 31) { + cell new_tree_root = ops~load_maybe_ref(); + ;; no sanity checks cause they would cost immense gas + return (new_tree_root, ops); + } + ;; 32 TDel: nullify ENTIRE DOMAIN TABLE + if (op == 32) { + return (null(), ops); + } + throw(44); ;; invalid operation + return (null(), ops); +} + +cell process_ops(cell root, slice ops) inline_ref { + var stop = false; + root~touch(); + ops~touch(); + do { + (root, ops) = process_op(root, ops); + if (ops.slice_data_empty?()) { + if (ops.slice_refs()) { + ops = ops~load_ref().begin_parse(); + } else { + stop = true; + } + } + } until (stop); + return root; +} + +() recv_external(slice in_msg) impure { + ;; Load data + (int contract_id, int last_cleaned, int public_key, cell root, cell old_queries) = load_data(); + + ;; validate signature and seqno + slice signature = in_msg~load_bits(512); + int shash = slice_hash(in_msg); + var (query_contract, query_id) = (in_msg~load_uint(32), in_msg~load_uint(64)); + var bound = (now() << 32); + throw_if(35, query_id < bound); + (_, var found?) = old_queries.udict_get?(64, query_id); + throw_if(32, found?); + throw_unless(34, contract_id == query_contract); + throw_unless(35, check_signature(shash, signature, public_key)); + accept_message(); ;; message is signed by owner, sanity not guaranteed yet + + int op = in_msg.preload_uint(6); + if (op == 51) { + in_msg~skip_bits(6); + public_key = in_msg~load_uint(256); + } else { + root = process_ops(root, in_msg); + } + + bound -= (64 << 32); ;; clean up records expired more than 64 seconds ago + old_queries~udict_set_builder(64, query_id, begin_cell()); + var queries = old_queries; + do { + var (old_queries', i, _, f) = old_queries.udict_delete_get_min(64); + f~touch(); + if (f) { + f = (i < bound); + } + if (f) { + old_queries = old_queries'; + last_cleaned = i; + } + } until (~ f); + + store_data(contract_id, last_cleaned, public_key, root, old_queries); +} + +() after_code_upgrade(cell root, slice ops, cont old_code) impure method_id(1666) { +} + +{- + Data structure: + Root cell: [UInt<32b>:seqno] [UInt<256b>:owner_public_key] + [OptRef<1b+1r?>:HashmapCatTable>:domains] + := HashmapE 256 (~~16~~) ^DNSRecord + + STORED DOMAIN NAME SLICE FORMAT: (#ZeroChars<7b>) (Domain name value) + #Zeros allows to simultaneously store, for example, com\0 and com\0google\0 + That will be stored as \1com\0 and \2com\0google\0 (pfx tree has restricitons) + This will allow to resolve more specific requests to subdomains, and resort + to parent domain next resolver lookup if subdomain is not found + com\0goo\0 lookup will, for example look up \2com\0goo\0 and then + \1com\0goo\0 which will return \1com\0 (as per pfx tree) with -1 cat +-} + +;;===========================================================================;; +;; Getter methods ;; +;;===========================================================================;; + +;; Retrieve contract id (in case several contracts are managed with the same private key) +int get_contract_id() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(32 + 64); + return cs.preload_uint(256); +} + +;;8m dns-record-value +(int, cell) dnsresolve(slice subdomain, int category) method_id { + int bits = subdomain.slice_bits(); + ifnot (bits) { + ;; return (0, null()); ;; zero-length input + throw(30); ;; update: throw exception for empty input + } + throw_if(30, bits & 7); ;; malformed input (~ 8n-bit) + + int name_last_byte = subdomain.slice_last(8).preload_uint(8); + if (name_last_byte) { + subdomain = begin_cell().store_slice(subdomain) ;; append zero byte + .store_uint(0, 8).end_cell().begin_parse(); + bits += 8; + } + if (bits == 8) { + return (8, null()); ;; zero-length input, but with zero byte + ;; update: return 8 as resolved, but with no data + } + int name_first_byte = subdomain.preload_uint(8); + if (name_first_byte == 0) { + ;; update: remove prefix \0 + subdomain~load_uint(8); + bits -= 8; + } + (_, _, _, cell root, _) = load_data(); + + slice cname = subdomain; + int zeros = 0; + repeat (bits >> 3) { + int c = cname~load_uint(8); + zeros -= (c == 0); + } + + ;; can't move these declarations lower, will cause errors! + slice pfx = cname; + cell val = null(); + slice tail = cname; + + do { + slice pfxname = begin_cell().store_uint(zeros, 7) + .store_slice(subdomain).end_cell().begin_parse(); + (pfx, val, tail, int succ) = root.pfxdict_get_ref(1023, pfxname); + zeros = succ ^ (zeros - 1); ;; break on success + } until (zeros <= 0); + + ifnot (zeros) { + return (0, null()); ;; failed to find entry in prefix dictionary + } + + zeros = - zeros; + + ifnot (tail.slice_empty?()) { ;; if we have tail then len(pfx) < len(subdomain) + ;; incomplete subdomain found, must return next resolver + category = "dns_next_resolver"H; ;; 0x19f02441ee588fdb26ee24b2568dd035c3c9206e11ab979be62e55558a1d17ff + ;; update: next resolver is now sha256("dns_next_resolver") instead of -1 + } + int pfx_bits = pfx.slice_bits() - 7; + cell cat_table = val; + ;; pfx.slice_bits() will contain 8m, where m is number of bytes in subdomain + ;; COUNTING the zero byte (if structurally correct: no multiple-ZB keys) + ;; which corresponds to 8m, m=one plus the number of bytes in the subdomain found) + if (category == 0) { + return (pfx_bits, cat_table); ;; return cell with entire dictionary for 0 + } else { + cell cat_found = cat_table.udict_get_ref_(256, category); ;; update: category length now u256 instead of i16 + return (pfx_bits, cat_found); + } +} diff --git a/crypto/smartcont/elector-code.fc b/crypto/smartcont/elector-code.fc index 563aa7bc..97185fc2 100644 --- a/crypto/smartcont/elector-code.fc +++ b/crypto/smartcont/elector-code.fc @@ -1,19 +1,19 @@ ;; Elector smartcontract -;; cur_elect credits past_elect grams active_id active_hash -(cell, cell, cell, int, int, int) load_data() { +;; cur_elect credits past_elections grams active_id active_hash +(cell, cell, cell, int, int, int) load_data() inline_ref { var cs = get_data().begin_parse(); var res = (cs~load_dict(), cs~load_dict(), cs~load_dict(), cs~load_grams(), cs~load_uint(32), cs~load_uint(256)); cs.end_parse(); return res; } -;; cur_elect credits past_elect grams active_id active_hash -() store_data(elect, credits, past_elect, grams, active_id, active_hash) impure { +;; cur_elect credits past_elections grams active_id active_hash +() store_data(elect, credits, past_elections, grams, active_id, active_hash) impure inline_ref { set_data(begin_cell() .store_dict(elect) .store_dict(credits) - .store_dict(past_elect) + .store_dict(past_elections) .store_grams(grams) .store_uint(active_id, 32) .store_uint(active_hash, 256) @@ -21,14 +21,14 @@ } ;; elect -> elect_at elect_close min_stake total_stake members failed finished -_ unpack_elect(elect) { +_ unpack_elect(elect) inline_ref { var es = elect.begin_parse(); var res = (es~load_uint(32), es~load_uint(32), es~load_grams(), es~load_grams(), es~load_dict(), es~load_int(1), es~load_int(1)); es.end_parse(); return res; } -cell pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, finished) { +cell pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, finished) inline_ref { return begin_cell() .store_uint(elect_at, 32) .store_uint(elect_close, 32) @@ -40,13 +40,117 @@ cell pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, .end_cell(); } +;; slice -> unfreeze_at stake_held vset_hash frozen_dict total_stake bonuses complaints +_ unpack_past_election(slice fs) inline_ref { + var res = (fs~load_uint(32), fs~load_uint(32), fs~load_uint(256), fs~load_dict(), fs~load_grams(), fs~load_grams(), fs~load_dict()); + fs.end_parse(); + return res; +} + +builder pack_past_election(int unfreeze_at, int stake_held, int vset_hash, cell frozen_dict, int total_stake, int bonuses, cell complaints) inline_ref { + return begin_cell() + .store_uint(unfreeze_at, 32) + .store_uint(stake_held, 32) + .store_uint(vset_hash, 256) + .store_dict(frozen_dict) + .store_grams(total_stake) + .store_grams(bonuses) + .store_dict(complaints); +} + +;; complaint_status#2d complaint:^ValidatorComplaint voters:(HashmapE 16 True) +;; vset_id:uint256 weight_remaining:int64 = ValidatorComplaintStatus; +_ unpack_complaint_status(slice cs) inline_ref { + throw_unless(9, cs~load_uint(8) == 0x2d); + var res = (cs~load_ref(), cs~load_dict(), cs~load_uint(256), cs~load_int(64)); + cs.end_parse(); + return res; +} + +builder pack_complaint_status(cell complaint, cell voters, int vset_id, int weight_remaining) inline_ref { + return begin_cell() + .store_uint(0x2d, 8) + .store_ref(complaint) + .store_dict(voters) + .store_uint(vset_id, 256) + .store_int(weight_remaining, 64); +} + +;; validator_complaint#bc validator_pubkey:uint256 description:^ComplaintDescr +;; created_at:uint32 severity:uint8 reward_addr:uint256 paid:Grams suggested_fine:Grams +;; suggested_fine_part:uint32 = ValidatorComplaint; +_ unpack_complaint(slice cs) inline_ref { + throw_unless(9, cs~load_int(8) == 0xbc - 0x100); + var res = (cs~load_uint(256), cs~load_ref(), cs~load_uint(32), cs~load_uint(8), cs~load_uint(256), cs~load_grams(), cs~load_grams(), cs~load_uint(32)); + cs.end_parse(); + return res; +} + +builder pack_complaint(int validator_pubkey, cell description, int created_at, int severity, int reward_addr, int paid, int suggested_fine, int suggested_fine_part) inline_ref { + return begin_cell() + .store_int(0xbc - 0x100, 8) + .store_uint(validator_pubkey, 256) + .store_ref(description) + .store_uint(created_at, 32) + .store_uint(severity, 8) + .store_uint(reward_addr, 256) + .store_grams(paid) + .store_grams(suggested_fine) + .store_uint(suggested_fine_part, 32); +} + +;; complaint_prices#1a deposit:Grams bit_price:Grams cell_price:Grams = ComplaintPricing; +(int, int, int) parse_complaint_prices(cell info) inline { + var cs = info.begin_parse(); + throw_unless(9, cs~load_uint(8) == 0x1a); + var res = (cs~load_grams(), cs~load_grams(), cs~load_grams()); + cs.end_parse(); + return res; +} + +;; deposit bit_price cell_price +(int, int, int) get_complaint_prices() inline_ref { + var info = config_param(13); + return info.null?() ? (1 << 36, 1, 512) : info.parse_complaint_prices(); +} + ;; elected_for elections_begin_before elections_end_before stake_held_for (int, int, int, int) get_validator_conf() { var cs = config_param(15).begin_parse(); return (cs~load_int(32), cs~load_int(32), cs~load_int(32), cs.preload_int(32)); } -() send_message_back(addr, ans_tag, query_id, body, grams, mode) impure { +;; next three functions return information about current validator set (config param #34) +;; they are borrowed from config-code.fc +(cell, int, cell) get_current_vset() inline_ref { + var vset = config_param(34); + var cs = begin_parse(vset); + ;; validators_ext#12 utime_since:uint32 utime_until:uint32 + ;; total:(## 16) main:(## 16) { main <= total } { main >= 1 } + ;; total_weight:uint64 + throw_unless(40, cs~load_uint(8) == 0x12); + cs~skip_bits(32 + 32 + 16 + 16); + var (total_weight, dict) = (cs~load_uint(64), cs~load_dict()); + cs.end_parse(); + return (vset, total_weight, dict); +} + +(slice, int) get_validator_descr(int idx) inline_ref { + var (vset, total_weight, dict) = get_current_vset(); + var (value, _) = dict.udict_get?(16, idx); + return (value, total_weight); +} + +(int, int) unpack_validator_descr(slice cs) inline { + ;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey; + ;; validator#53 public_key:SigPubKey weight:uint64 = ValidatorDescr; + ;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr; + throw_unless(41, (cs~load_uint(8) & ~ 0x20) == 0x53); + throw_unless(41, cs~load_uint(32) == 0x8e81278a); + return (cs~load_uint(256), cs~load_uint(64)); +} + +() send_message_back(addr, ans_tag, query_id, body, grams, mode) impure inline_ref { ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 var msg = begin_cell() .store_uint(0x18, 6) @@ -61,15 +165,15 @@ cell pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, send_raw_message(msg.end_cell(), mode); } -() return_stake(addr, query_id, reason) impure { +() return_stake(addr, query_id, reason) impure inline_ref { return send_message_back(addr, 0xee6f454c, query_id, reason, 0, 64); } -() send_confirmation(addr, query_id, comment) impure { +() send_confirmation(addr, query_id, comment) impure inline_ref { return send_message_back(addr, 0xf374484c, query_id, comment, 1000000000, 2); } -() send_validator_set_to_config(config_addr, vset, query_id) impure { +() send_validator_set_to_config(config_addr, vset, query_id) impure inline_ref { var msg = begin_cell() .store_uint(0xc4ff, 17) ;; 0 11000100 0xff .store_uint(config_addr, 256) @@ -82,7 +186,7 @@ cell pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, } ;; credits 'amount' to 'addr' inside credit dictionary 'credits' -_ ~credit_to(credits, addr, amount) { +_ ~credit_to(credits, addr, amount) inline_ref { var (val, f) = credits.udict_get?(256, addr); if (f) { amount += val~load_grams(); @@ -91,11 +195,11 @@ _ ~credit_to(credits, addr, amount) { return (credits, ()); } -() process_new_stake(s_addr, msg_value, cs, query_id) impure { +() process_new_stake(s_addr, msg_value, cs, query_id) impure inline_ref { var (src_wc, src_addr) = parse_std_addr(s_addr); var ds = get_data().begin_parse(); var elect = ds~load_dict(); - if (null?(elect) | (src_wc + 1)) { + if (elect.null?() | (src_wc + 1)) { ;; no elections active, or source is not in masterchain ;; bounce message return return_stake(s_addr, query_id, 0); @@ -123,7 +227,7 @@ _ ~credit_to(credits, addr, amount) { } ;; parse current election data var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); - elect_at~dump(); + ;; elect_at~dump(); msg_value -= 1000000000; ;; deduct GR$1 for sending confirmation if ((msg_value << 12) < total_stake) { ;; stake smaller than 1/4096 of the total accumulated stakes, return @@ -172,7 +276,7 @@ _ ~credit_to(credits, addr, amount) { return (); } -(cell, int) unfreeze_without_bonuses(credits, freeze_dict, tot_stakes) { +(cell, int) unfreeze_without_bonuses(credits, freeze_dict, tot_stakes) inline_ref { var total = var recovered = 0; var pubkey = -1; do { @@ -192,7 +296,7 @@ _ ~credit_to(credits, addr, amount) { return (credits, recovered); } -(cell, int) unfreeze_with_bonuses(credits, freeze_dict, tot_stakes, tot_bonuses) { +(cell, int) unfreeze_with_bonuses(credits, freeze_dict, tot_stakes, tot_bonuses) inline_ref { var total = var recovered = var returned_bonuses = 0; var pubkey = -1; do { @@ -214,21 +318,34 @@ _ ~credit_to(credits, addr, amount) { return (credits, recovered + tot_bonuses - returned_bonuses); } -_ unfreeze_all(credits, past_elections, elect_id) { +int stakes_sum(frozen_dict) inline_ref { + var total = 0; + var pubkey = -1; + do { + (pubkey, var cs, var f) = frozen_dict.udict_get_next?(256, pubkey); + if (f) { + cs~skip_bits(256 + 64); + total += cs~load_grams(); + } + } until (~ f); + return total; +} + +_ unfreeze_all(credits, past_elections, elect_id) inline_ref { var (fs, f) = past_elections~udict_delete_get?(32, elect_id); ifnot (f) { ;; no elections with this id return (credits, past_elections, 0); } - var (data1, vset_hash, fdict, tot_stakes, bonuses, complaints) = (fs~load_uint(64), fs~load_uint(256), fs~load_dict(), fs~load_grams(), fs~load_grams(), fs~load_dict()); - fs.end_parse(); + var (unfreeze_at, stake_held, vset_hash, fdict, tot_stakes, bonuses, complaints) = fs.unpack_past_election(); + ;; tot_stakes = fdict.stakes_sum(); ;; TEMP BUGFIX var unused_prizes = (bonuses > 0) ? credits~unfreeze_with_bonuses(fdict, tot_stakes, bonuses) : credits~unfreeze_without_bonuses(fdict, tot_stakes); return (credits, past_elections, unused_prizes); } -() config_set_confirmed(s_addr, cs, query_id, ok) impure { +() config_set_confirmed(s_addr, cs, query_id, ok) impure inline_ref { var (src_wc, src_addr) = parse_std_addr(s_addr); var config_addr = config_param(0).begin_parse().preload_uint(256); var ds = get_data().begin_parse(); @@ -259,37 +376,31 @@ _ unfreeze_all(credits, past_elections, elect_id) { ;; ... do not remove elect until we see this set as the next elected validator set } -() process_simple_transfer(s_addr, msg_value) impure { - var (elect, credits, past_elect, grams, active_id, active_hash) = load_data(); +() process_simple_transfer(s_addr, msg_value) impure inline_ref { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); (int src_wc, int src_addr) = parse_std_addr(s_addr); if (src_addr | (src_wc + 1) | (active_id == 0)) { ;; simple transfer to us (credit "nobody's" account) ;; (or no known active validator set) grams += msg_value; - return store_data(elect, credits, past_elect, grams, active_id, active_hash); + return store_data(elect, credits, past_elections, grams, active_id, active_hash); } ;; zero source address -1:00..00 (collecting validator fees) - var (fs, f) = past_elect.udict_get?(32, active_id); + var (fs, f) = past_elections.udict_get?(32, active_id); ifnot (f) { ;; active validator set not found (?) grams += msg_value; } else { ;; credit active validator set bonuses - var (data, hash, dict, total_stake, bonuses) = (fs~load_uint(64), fs~load_uint(256), fs~load_dict(), fs~load_grams(), fs~load_grams()); + var (unfreeze_at, stake_held, hash, dict, total_stake, bonuses, complaints) = fs.unpack_past_election(); bonuses += msg_value; - past_elect~udict_set_builder(32, active_id, begin_cell() - .store_uint(data, 64) - .store_uint(hash, 256) - .store_dict(dict) - .store_grams(total_stake) - .store_grams(bonuses) - .store_slice(fs)); + past_elections~udict_set_builder(32, active_id, + pack_past_election(unfreeze_at, stake_held, hash, dict, total_stake, bonuses, complaints)); } - store_data(elect, credits, past_elect, grams, active_id, active_hash); - return (); + return store_data(elect, credits, past_elections, grams, active_id, active_hash); } -() recover_stake(op, s_addr, cs, query_id) impure { +() recover_stake(op, s_addr, cs, query_id) impure inline_ref { (int src_wc, int src_addr) = parse_std_addr(s_addr); if (src_wc + 1) { ;; not from masterchain, return error @@ -322,7 +433,7 @@ _ unfreeze_all(credits, past_elections, elect_id) { return send_message_back(s_addr, 0xce436f64, query_id, op, 0, 64); } -int upgrade_code(s_addr, cs, query_id) { +int upgrade_code(s_addr, cs, query_id) inline_ref { var c_addr = config_param(0); if (c_addr.null?()) { ;; no configuration smart contract known @@ -339,13 +450,155 @@ int upgrade_code(s_addr, cs, query_id) { set_code(code); ifnot(cs.slice_empty?()) { set_c3(code.begin_parse().bless()); - ;; run_method3(1666, s_addr, cs, query_id); after_code_upgrade(s_addr, cs, query_id); throw(0); } return true; } +int register_complaint(s_addr, complaint, msg_value) { + var (src_wc, src_addr) = parse_std_addr(s_addr); + if (src_wc + 1) { ;; not from masterchain, return error + return -1; + } + if (complaint.slice_depth() >= 128) { + return -3; ;; invalid complaint + } + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var election_id = complaint~load_uint(32); + var (fs, f) = past_elections.udict_get?(32, election_id); + ifnot (f) { ;; election not found + return -2; + } + var expire_in = fs.preload_uint(32) - now(); + if (expire_in <= 0) { ;; already expired + return -4; + } + var (validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part) = unpack_complaint(complaint); + reward_addr = src_addr; + created_at = now(); + ;; compute complaint storage/creation price + var (deposit, bit_price, cell_price) = get_complaint_prices(); + var (_, bits, refs) = slice_compute_data_size(complaint, 4096); + var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price; + paid = pps * expire_in + deposit; + if (msg_value < paid + (1 << 30)) { ;; not enough money + return -5; + } + ;; re-pack modified complaint + cell complaint = pack_complaint(validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part).end_cell(); + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + var (fs, f) = frozen_dict.udict_get?(256, validator_pubkey); + ifnot (f) { ;; no such validator, cannot complain + return -6; + } + fs~skip_bits(256 + 64); ;; addr weight + var validator_stake = fs~load_grams(); + int fine = suggested_fine + muldiv(validator_stake, suggested_fine_part, 1 << 32); + if (fine > validator_stake) { ;; validator's stake is less than suggested fine + return -7; + } + if (fine <= paid) { ;; fine is less than the money paid for creating complaint + return -8; + } + ;; create complaint status + var cstatus = pack_complaint_status(complaint, null(), 0, 0); + ;; save complaint status into complaints + var cpl_id = complaint.cell_hash(); + ifnot (complaints~udict_add_builder?(256, cpl_id, cstatus)) { + return -9; ;; complaint already exists + } + ;; pack past election info + past_elections~udict_set_builder(32, election_id, pack_past_election(unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints)); + ;; pack persistent data + ;; next line can be commented, but it saves a lot of stack manipulations + var (elect, credits, _, grams, active_id, active_hash) = load_data(); + store_data(elect, credits, past_elections, grams, active_id, active_hash); + return paid; +} + +(cell, cell, int, int) punish(credits, frozen, complaint) inline_ref { + var (validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part) = complaint.begin_parse().unpack_complaint(); + var (cs, f) = frozen.udict_get?(256, validator_pubkey); + ifnot (f) { + ;; no validator to punish + return (credits, frozen, 0, 0); + } + var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1)); + cs.end_parse(); + int fine = min(stake, suggested_fine + muldiv(stake, suggested_fine_part, 1 << 32)); + stake -= fine; + frozen~udict_set_builder(256, validator_pubkey, begin_cell() + .store_uint(addr, 256) + .store_uint(weight, 64) + .store_grams(stake) + .store_int(banned, 1)); + int reward = min(fine >> 3, paid * 8); + credits~credit_to(reward_addr, reward); + return (credits, frozen, fine - reward, fine); +} + +(cell, cell, int) register_vote(complaints, chash, idx, weight) inline_ref { + var (cstatus, found?) = complaints.udict_get?(256, chash); + ifnot (found?) { + ;; complaint not found + return (complaints, null(), -1); + } + var (cur_vset, total_weight, _) = get_current_vset(); + int cur_vset_id = cur_vset.cell_hash(); + var (complaint, voters, vset_id, weight_remaining) = unpack_complaint_status(cstatus); + int vset_old? = (vset_id != cur_vset_id); + if ((weight_remaining < 0) & vset_old?) { + ;; previous validator set already collected 2/3 votes, skip new votes + return (complaints, null(), -3); + } + if (vset_old?) { + ;; complaint votes belong to a previous validator set, reset voting + vset_id = cur_vset_id; + voters = null(); + weight_remaining = muldiv(total_weight, 2, 3); + } + var (_, found?) = voters.udict_get?(16, idx); + if (found?) { + ;; already voted for this proposal, ignore vote + return (complaints, null(), 0); + } + ;; register vote + voters~udict_set_builder(16, idx, begin_cell().store_uint(now(), 32)); + int old_wr = weight_remaining; + weight_remaining -= weight; + old_wr ^= weight_remaining; + ;; save voters and weight_remaining + complaints~udict_set_builder(256, chash, pack_complaint_status(complaint, voters, vset_id, weight_remaining)); + if (old_wr >= 0) { + ;; not enough votes or already accepted + return (complaints, null(), 1); + } + ;; complaint wins, prepare punishment + return (complaints, complaint, 2); +} + +int proceed_register_vote(election_id, chash, idx, weight) impure inline_ref { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var (fs, f) = past_elections.udict_get?(32, election_id); + ifnot (f) { ;; election not found + return -2; + } + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + (complaints, var accepted_complaint, var status) = register_vote(complaints, chash, idx, weight); + if (status <= 0) { + return status; + } + ifnot (accepted_complaint.null?()) { + (credits, frozen_dict, int fine_unalloc, int fine_collected) = punish(credits, frozen_dict, accepted_complaint); + grams += fine_unalloc; + total_stake -= fine_collected; + } + past_elections~udict_set_builder(32, election_id, pack_past_election(unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints)); + store_data(elect, credits, past_elections, grams, active_id, active_hash); + return status; +} + () recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { ;; do nothing for internal messages var cs = in_msg_cell.begin_parse(); @@ -383,6 +636,33 @@ int upgrade_code(s_addr, cs, query_id) { ;; confirmation from configuration smart contract return config_set_confirmed(s_addr, in_msg, query_id, cfg_ok); } + if (op == 0x52674370) { + ;; new complaint + var price = register_complaint(s_addr, in_msg, msg_value); + int mode = 64; + int ans_tag = - price; + if (price >= 0) { + ;; ok, debit price + raw_reserve(price, 4); + ans_tag = 0; + mode = 128; + } + return send_message_back(s_addr, ans_tag + 0xf2676350, query_id, op, 0, mode); + } + if (op == 0x56744370) { + ;; vote for a complaint + var signature = in_msg~load_bits(512); + var msg_body = in_msg; + var (sign_tag, idx, elect_id, chash) = (in_msg~load_uint(32), in_msg~load_uint(16), in_msg~load_uint(32), in_msg~load_uint(256)); + in_msg.end_parse(); + throw_unless(37, sign_tag == 0x56744350); + var (vdescr, total_weight) = get_validator_descr(idx); + var (val_pubkey, weight) = unpack_validator_descr(vdescr); + throw_unless(34, check_data_signature(msg_body, signature, val_pubkey)); + int res = proceed_register_vote(elect_id, chash, idx, weight); + return send_message_back(s_addr, res + 0xd6745240, query_id, op, 0, 64); + } + ifnot (op & (1 << 31)) { ;; unknown query, return error return send_message_back(s_addr, 0xffffffff, query_id, op, 0, 64); @@ -396,7 +676,7 @@ int postpone_elections() impure { } ;; computes the total stake out of the first n entries of list l -_ compute_total_stake(l, n, m_stake) { +_ compute_total_stake(l, n, m_stake) inline_ref { int tot_stake = 0; repeat (n) { (var h, l) = uncons(l); @@ -443,7 +723,7 @@ _ compute_total_stake(l, n, m_stake) { if (f) { var (stake, _, pubkey) = (min(key~load_uint(128), max_stake), key~load_uint(32), key.preload_uint(256)); var (max_f, _, adnl_addr) = (cs~load_uint(32), cs~load_uint(256), cs.preload_uint(256)); - l = cons(tuple4(stake, max_f, pubkey, adnl_addr), l); + l = cons([stake, max_f, pubkey, adnl_addr], l); } } until (~ f); ;; l is the list of all stakes in decreasing order @@ -468,7 +748,7 @@ _ compute_total_stake(l, n, m_stake) { } ;; we have to select first m validators from list l l1 = touch(l); - l1~dump(); ;; DEBUG + ;; l1~dump(); ;; DEBUG repeat (m - 1) { l1 = cdr(l1); } @@ -480,7 +760,7 @@ _ compute_total_stake(l, n, m_stake) { var vset = new_dict(); var frozen = new_dict(); do { - var (stake, max_f, pubkey, adnl_addr) = l~list_next().untuple4(); + var [stake, max_f, pubkey, adnl_addr] = l~list_next(); ;; lookup source address first var (val, f) = members.udict_get?(256, pubkey); throw_unless(61, f); @@ -574,27 +854,22 @@ int conduct_elections(ds, elect, credits) impure { var config_addr = config_param(0).begin_parse().preload_uint(256); send_validator_set_to_config(config_addr, vset, elect_at); ;; add frozen to the dictionary of past elections - var past_elect = ds~load_dict(); - past_elect~udict_set_builder(32, elect_at, begin_cell() - .store_uint(start + elect_for + stake_held, 32) - .store_uint(stake_held, 32) - .store_uint(cell_hash(vset), 256) - .store_dict(frozen) - .store_grams(total_stakes) - .store_grams(0) - .store_int(false, 1)); + var past_elections = ds~load_dict(); + past_elections~udict_set_builder(32, elect_at, pack_past_election( + start + elect_for + stake_held, stake_held, vset.cell_hash(), + frozen, total_stakes, 0, null())); ;; store credits and frozen until end set_data(begin_cell() .store_dict(elect) .store_dict(credits) - .store_dict(past_elect) + .store_dict(past_elections) .store_slice(ds) .end_cell()); return true; } int update_active_vset_id() impure { - var (elect, credits, past_elect, grams, active_id, active_hash) = load_data(); + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); var cur_hash = config_param(34).cell_hash(); if (cur_hash == active_hash) { ;; validator set unchanged @@ -602,7 +877,7 @@ int update_active_vset_id() impure { } if (active_id) { ;; active_id becomes inactive - var (fs, f) = past_elect.udict_get?(32, active_id); + var (fs, f) = past_elections.udict_get?(32, active_id); if (f) { ;; adjust unfreeze time of this validator set var unfreeze_time = fs~load_uint(32); @@ -610,7 +885,7 @@ int update_active_vset_id() impure { var (stake_held, hash) = (fs~load_uint(32), fs~load_uint(256)); throw_unless(57, hash == active_hash); unfreeze_time = now() + stake_held; - past_elect~udict_set_builder(32, active_id, begin_cell() + past_elections~udict_set_builder(32, active_id, begin_cell() .store_uint(unfreeze_time, 32) .store_slice(fs0)); } @@ -618,7 +893,7 @@ int update_active_vset_id() impure { ;; look up new active_id by hash var id = -1; do { - (id, var fs, var f) = past_elect.udict_get_next?(32, id); + (id, var fs, var f) = past_elections.udict_get_next?(32, id); if (f) { var (tm, hash) = (fs~load_uint(64), fs~load_uint(256)); if (hash == cur_hash) { @@ -629,7 +904,7 @@ int update_active_vset_id() impure { grams -= amount; bonuses += amount; ;; serialize back - past_elect~udict_set_builder(32, id, begin_cell() + past_elections~udict_set_builder(32, id, begin_cell() .store_uint(tm, 64) .store_uint(hash, 256) .store_dict(dict) @@ -643,11 +918,11 @@ int update_active_vset_id() impure { } until (~ f); active_id = (id.null?() ? 0 : id); active_hash = cur_hash; - store_data(elect, credits, past_elect, grams, active_id, active_hash); + store_data(elect, credits, past_elections, grams, active_id, active_hash); return true; } -int cell_hash_eq?(cell vset, int expected_vset_hash) { +int cell_hash_eq?(cell vset, int expected_vset_hash) inline_ref { return vset.null?() ? false : cell_hash(vset) == expected_vset_hash; } @@ -680,18 +955,18 @@ int validator_set_installed(ds, elect, credits) impure { } int check_unfreeze() impure { - var (elect, credits, past_elect, grams, active_id, active_hash) = load_data(); + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); int id = -1; do { - (id, var fs, var f) = past_elect.udict_get_next?(32, id); + (id, var fs, var f) = past_elections.udict_get_next?(32, id); if (f) { var unfreeze_at = fs~load_uint(32); if ((unfreeze_at <= now()) & (id != active_id)) { ;; unfreeze! - (credits, past_elect, var unused_prizes) = unfreeze_all(credits, past_elect, id); + (credits, past_elections, var unused_prizes) = unfreeze_all(credits, past_elections, id); grams += unused_prizes; ;; unfreeze only one at time, exit loop - store_data(elect, credits, past_elect, grams, active_id, active_hash); + store_data(elect, credits, past_elections, grams, active_id, active_hash); ;; exit loop f = false; } @@ -733,7 +1008,7 @@ int announce_new_elections(ds, elect, credits) { (_, var min_stake) = config_param(17).begin_parse().load_grams(); ;; announce new elections var elect_at = t + elect_begin_before; - elect_at~dump(); + ;; elect_at~dump(); var elect_close = elect_at - elect_end_before; elect = pack_elect(elect_at, elect_close, min_stake, 0, new_dict(), false, false); set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell()); @@ -786,7 +1061,7 @@ _ participant_list() method_id { do { (id, var fs, var f) = members.udict_get_prev?(256, id); if (f) { - l = cons(pair(id, fs~load_grams()), l); + l = cons([id, fs~load_grams()], l); } } until (~ f); return l; @@ -806,7 +1081,7 @@ _ participant_list_extended() method_id { if (f) { var (stake, time, max_factor, addr, adnl_addr) = (cs~load_grams(), cs~load_uint(32), cs~load_uint(32), cs~load_uint(256), cs~load_uint(256)); cs.end_parse(); - l = cons(pair(id, tuple4(stake, max_factor, addr, adnl_addr)), l); + l = cons([id, [stake, max_factor, addr, adnl_addr]], l); } } until (~ f); return (elect_at, elect_close, min_stake, total_stake, l, failed, finished); @@ -819,3 +1094,94 @@ int compute_returned_stake(int wallet_addr) method_id { var (val, f) = credits.udict_get?(256, wallet_addr); return f ? val~load_grams() : 0; } + +;; returns the list of past election ids +tuple past_election_ids() method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var id = (1 << 32); + var list = null(); + do { + (id, var fs, var f) = past_elections.udict_get_prev?(32, id); + if (f) { + list = cons(id, list); + } + } until (~ f); + return list; +} + +tuple past_elections() method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var id = (1 << 32); + var list = null(); + do { + (id, var fs, var found) = past_elections.udict_get_prev?(32, id); + if (found) { + list = cons([id, unpack_past_election(fs)], list); + } + } until (~ found); + return list; +} + +tuple past_elections_list() method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var id = (1 << 32); + var list = null(); + do { + (id, var fs, var found) = past_elections.udict_get_prev?(32, id); + if (found) { + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + list = cons([id, unfreeze_at, vset_hash, stake_held], list); + } + } until (~ found); + return list; +} + +_ complete_unpack_complaint(slice cs) inline_ref { + var (complaint, voters, vset_id, weight_remaining) = cs.unpack_complaint_status(); + var voters_list = null(); + var voter_id = (1 << 32); + do { + (voter_id, _, var f) = voters.udict_get_prev?(16, voter_id); + if (f) { + voters_list = cons(voter_id, voters_list); + } + } until (~ f); + return [[complaint.begin_parse().unpack_complaint()], voters_list, vset_id, weight_remaining]; +} + +cell get_past_complaints(int election_id) inline_ref method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var (fs, found?) = past_elections.udict_get?(32, election_id); + ifnot (found?) { + return null(); + } + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + return complaints; +} + +_ show_complaint(int election_id, int chash) method_id { + var complaints = get_past_complaints(election_id); + var (cs, found) = complaints.udict_get?(256, chash); + return found ? complete_unpack_complaint(cs) : null(); +} + +tuple list_complaints(int election_id) method_id { + var complaints = get_past_complaints(election_id); + int id = (1 << 255) + ((1 << 255) - 1); + var list = null(); + do { + (id, var cs, var found?) = complaints.udict_get_prev?(256, id); + if (found?) { + list = cons(pair(id, complete_unpack_complaint(cs)), list); + } + } until (~ found?); + return list; +} + +int complaint_storage_price(int bits, int refs, int expire_in) method_id { + ;; compute complaint storage/creation price + var (deposit, bit_price, cell_price) = get_complaint_prices(); + var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price; + var paid = pps * expire_in + deposit; + return paid + (1 << 30); +} diff --git a/crypto/smartcont/envelope-complaint.fif b/crypto/smartcont/envelope-complaint.fif new file mode 100644 index 00000000..e9a65c72 --- /dev/null +++ b/crypto/smartcont/envelope-complaint.fif @@ -0,0 +1,50 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include +"GetOpt.fif" include + +{ show-options-help 1 halt } : usage +86400 3 * =: expire-in +false =: critical +-1 =: old-hash + +begin-options + " [-x ] []" +cr +tab + +"Embeds a validator complaint loaded from file into an internal message body to be sent to the elector smart contract " + +"and saves it as an internal message body into .boc ('complaint-msg-body.boc' by default)" + disable-digit-options generic-help-setopt + "x" "--expires-in" { parse-int =: expire-in } short-long-option-arg + "Sets complaint expiration time in seconds (default " expire-in (.) $+ +")" option-help + "h" "--help" { usage } short-long-option + "Shows a help message" option-help +parse-options + +$# dup 2 < swap 3 > or ' usage if +3 :$1..n + +$1 parse-int dup =: election-id + 32 ufits not abort"invalid election id" +$2 =: boc-filename +$3 "complaint-msg-body.boc" replace-if-null =: savefile +expire-in now + =: expire-at + +boc-filename dup ."Loading complaint from file `" type ."`" cr +file>B B>boc dup =: complaint hash =: c-hash +complaint abort"Not a valid ValidatorComplaint" +complaint + +dup ."resulting internal message body: " B dup Bx. cr + +complaint +totalcsize swap ."(a total of " . ."data bits, " . ."cell references -> " +drop dup Blen . ."BoC data bytes)" cr + +savefile tuck B>file +."(Saved to file " type .")" cr diff --git a/crypto/smartcont/gen-zerostate-test.fif b/crypto/smartcont/gen-zerostate-test.fif index 6fe3fbed..d98c7a26 100644 --- a/crypto/smartcont/gen-zerostate-test.fif +++ b/crypto/smartcont/gen-zerostate-test.fif @@ -1,11 +1,14 @@ +#!/usr/bin/create-state -s "TonUtil.fif" include "Asm.fif" include +"Lists.fif" include def? $1 { @' $1 } { "" } cond constant suffix { suffix $+ } : +suffix +256 1<<1- 15 / constant AllOnes wc_master setworkchain --17 setglobalid // negative value means a test instance of the blockchain +-239 setglobalid // negative value means a test instance of the blockchain // Initial state of Workchain 0 (Basic workchain) @@ -16,30 +19,35 @@ dup dup 31 boc+>B dup Bx. cr dup "basestate0" +suffix +".boc" tuck B>file ."(Initial basechain state saved to file " type .")" cr Bhashu dup =: basestate0_fhash -."file hash=" dup x. space 256 u>B dup B>base64url type cr +."file hash=" dup 64x. space 256 u>B dup B>base64url type cr "basestate0" +suffix +".fhash" B>file hashu dup =: basestate0_rhash -."root hash=" dup x. space 256 u>B dup B>base64url type cr +."root hash=" dup 64x. space 256 u>B dup B>base64url type cr "basestate0" +suffix +".rhash" B>file -basestate0_rhash basestate0_fhash now 0 2 32 0 add-std-workchain +// root-hash file-hash start-at actual-min-split min-split-depth max-split-depth wc-id +basestate0_rhash basestate0_fhash now 0 4 8 0 add-std-workchain config.workchains! // SmartContract #1 (Simple wallet) -<{ SETCP0 DUP IFNOTRET INC 32 THROWIF // return if recv_internal, fail unless recv_external - 512 INT LDSLICEX DUP 32 PLDU // sign cs cnt - c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS // sign cs cnt cnt' pubk - s1 s2 XCPU // sign cs cnt pubk cnt' cnt - EQUAL 33 THROWIFNOT // ( seqno mismatch? ) - s2 PUSH HASHSU // sign cs cnt pubk hash - s0 s4 s4 XC2PU // pubk cs cnt hash sign pubk - CHKSIGNU // pubk cs cnt ? - 34 THROWIFNOT // signature mismatch - ACCEPT - SWAP 32 LDU NIP 8 LDU LDREF ENDS // pubk cnt mode msg - SWAP SENDRAWMSG // pubk cnt ; ( message sent ) - INC NEWC 32 STU 256 STU ENDC c4 POPCTR +<{ SETCP0 DUP IFNOTRET // return if recv_internal + DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method + DROP c4 PUSHCTR CTOS 32 PLDU // cnt + }> + INC 32 THROWIF // fail unless recv_external + 512 INT LDSLICEX DUP 32 PLDU // sign cs cnt + c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS // sign cs cnt cnt' pubk + s1 s2 XCPU // sign cs cnt pubk cnt' cnt + EQUAL 33 THROWIFNOT // ( seqno mismatch? ) + s2 PUSH HASHSU // sign cs cnt pubk hash + s0 s4 s4 XC2PU // pubk cs cnt hash sign pubk + CHKSIGNU // pubk cs cnt ? + 34 THROWIFNOT // signature mismatch + ACCEPT + SWAP 32 LDU NIP 8 LDU LDREF ENDS // pubk cnt mode msg + SWAP SENDRAWMSG // pubk cnt ; ( message sent ) + INC NEWC 32 STU 256 STU ENDC c4 POPCTR }>c // code c public_lib x{1234} x{5678} |_ s>c private_lib }Libs // libraries -GR$1700000000 // balance +GR$4999990000 // balance 0 // split_depth 0 // ticktock -2 // mode: create +AllOnes 0 * // address +6 // mode: create+setaddr register_smc dup make_special dup constant smc1_addr Masterchain over @@ -61,7 +70,11 @@ Masterchain over "main-wallet" +suffix +".addr" save-address-verbose // SmartContract #2 (Simple money giver for test network) -<{ SETCP0 DUP IFNOTRET INC 32 THROWIF // return if recv_internal, fail unless recv_external +<{ SETCP0 DUP IFNOTRET // return if recv_internal + DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method + DROP c4 PUSHCTR CTOS 32 PLDU // cnt + }> + INC 32 THROWIF // fail unless recv_external 32 LDU SWAP // cs cnt c4 PUSHCTR CTOS 32 LDU ENDS // cs cnt cnt' TUCK EQUAL 33 THROWIFNOT // ( seqno mismatch? ) @@ -74,8 +87,8 @@ Masterchain over // code // data empty_cell // libraries -GR$1000000 // initial balance (1m test Grams) -0 0 2 register_smc +GR$1000 // initial balance (1k test Grams) +0 0 AllOnes 6 * 6 register_smc dup make_special dup constant smc2_addr Masterchain over 2dup ."free test gram giver address = " .addr cr 2dup 6 .Addr cr @@ -112,60 +125,95 @@ Libs{ x{ABACABADABACABA} s>c public_lib x{1234} x{5678} |_ s>c public_lib }Libs // libraries -0x333333333 // balance +GR$666 // balance 0 // split_depth 3 // ticktock: tick 2 // mode: create register_smc dup make_special dup constant smc3_addr -."address = " x. cr +."address = " 64x. cr -// SmartContract #4 (elector) +/* + * + * SmartContract #4 (elector) + * + */ "auto/elector-code.fif" include // code in separate source file - // data + // data: dict dict dict grams uint32 uint256 +empty_cell // libraries GR$10 // balance: 10 grams 0 // split_depth -1 // ticktock: tick -2 // mode: create +2 // ticktock: tick +AllOnes 3 * // address: -1:333...333 +6 // mode: create + setaddr register_smc dup make_special dup constant smc4_addr dup constant elector_addr +Masterchain swap +."elector smart contract address = " 2dup .addr cr 2dup 7 .Addr cr +"elector" +suffix +".addr" save-address-verbose -// Configuration Parameters - +/* + * + * Configuration Parameters + * + */ +// version capabilities +1 capCreateStats capBounceMsgBody or capReportVersion or capShortDequeue or config.version! // max-validators max-main-validators min-validators -// 9 3 2 config.validator_num! -1000 100 5 config.validator_num! +// 9 4 1 config.validator_num! +1000 100 13 config.validator_num! // min-stake max-stake min-total-stake max-factor -GR$10000 GR$10000000 GR$1000000 sg~10 config.validator_stake_limits! +GR$10000 GR$10000000 GR$500000 sg~3 config.validator_stake_limits! // elected-for elect-start-before elect-end-before stakes-frozen-for -400000 200000 4000 400000 config.election_params! +// 400000 200000 4000 400000 config.election_params! +// 4000 2000 500 1000 config.election_params! // DEBUG +65536 32768 8192 32768 config.election_params! // TestNet DEBUG +// config-addr = -1:5555...5555 +AllOnes 5 * constant config_addr +config_addr config.config_smc! // elector-addr elector_addr config.elector_smc! -1 sg* 100 sg* 1000 sg* 1000000 sg* config.storage_prices! +// 1 sg* 100 sg* 1000 sg* 1000000 sg* config.storage_prices! // old values (too high) +1 500 1000 500000 config.storage_prices! config.special! -// gas_price gas_limit gas_credit block_gas_limit freeze_due_limit delete_due_limit -- -1000 sg* 1000000 10000 10000000 GR$0.1 GR$1.0 config.gas_prices! -10000 sg* 1000000 10000 10000000 GR$0.1 GR$1.0 config.mc_gas_prices! +// gas_price gas_limit special_gas_limit gas_credit block_gas_limit freeze_due_limit delete_due_limit flat_gas_limit flat_gas_price -- +1000 sg* 1 *M dup 10000 10 *M GR$0.1 GR$1.0 1000 1000000 config.gas_prices! +10000 sg* 1 *M 10 *M 10000 10 *M GR$0.1 GR$1.0 1000 10000000 config.mc_gas_prices! // lump_price bit_price cell_price ihr_factor first_frac next_frac 1000000 1000 sg* 100000 sg* 3/2 sg*/ 1/3 sg*/ 1/3 sg*/ config.fwd_prices! 10000000 10000 sg* 1000000 sg* 3/2 sg*/ 1/3 sg*/ 1/3 sg*/ config.mc_fwd_prices! -// mc-cc-lifetime sh-cc-lifetime sh-val-lifetime sh-val-num -250 250 1000 7 config.catchain_params! +// mc-cc-lifetime sh-cc-lifetime sh-val-lifetime sh-val-num mc-shuffle +250 250 1000 7 true config.catchain_params! +// round-candidates next-cand-delay-ms consensus-timeout-ms fast-attempts attempt-duration cc-max-deps max-block-size max-collated-size new-cc-ids +3 2000 16000 3 8 4 2 *Mi 2 *Mi true config.consensus_params! -128 *Ki 512 *Ki 1 *Mi triple // [ underload soft hard ] : block bytes limit -100000 500000 1000000 triple // gas limits -1000 5000 10000 triple // lt limits + +128 *Ki 512 *Ki 1 *Mi triple // [ underload soft hard ] : block bytes limit +2000000 10000000 20000000 triple // gas limits +1000 5000 10000 triple // lt limits triple dup untriple config.mc_block_limits! untriple config.block_limits! GR$1.7 GR$1 config.block_create_fees! -smc1_addr config.collector_smc! +// smc1_addr config.collector_smc! smc1_addr config.minter_smc! 1000000000000 -17 of-cc 666666666666 239 of-cc cc+ config.to_mint! +( 0 1 9 10 12 14 15 16 17 18 20 21 22 23 24 25 28 34 ) config.mandatory_params! +( -999 -1000 -1001 0 1 9 10 12 14 15 16 17 32 34 36 ) config.critical_params! + +// [ min_tot_rounds max_tot_rounds min_wins max_losses min_store_sec max_store_sec bit_pps cell_pps ] +// first for ordinary proposals, then for critical proposals +_( 2 3 2 2 1000000 10000000 1 500 ) +_( 4 7 4 2 5000000 20000000 2 1000 ) +config.param_proposals_setup! + +// deposit bit_pps cell_pps +GR$100 1 500 config.complaint_prices! + "validator-keys" +suffix +".pub" file>B { dup Blen } { 32 B| swap dup ."Validator public key = " Bx. cr 17 add-validator } while drop @@ -173,66 +221,57 @@ smc1_addr config.minter_smc! // 17 add-validator // newkeypair nip dup ."Validator #2 public key = " Bx. cr // 239 add-validator -now dup 1000000 + 0 config.validators! +100000 =: orig_vset_valid_for // original validator set valid 100000 seconds +now dup orig_vset_valid_for + 0 config.validators! -// SmartContract #5 (Config) -PROGRAM{ - recv_internal x{} PROC - recv_external PROC:<{ - 512 INT LDSLICEX DUP 32 LDU 32 PLDU // sign cs cnt valid-until - NOW LEQ 35 THROWIF // ( fail if now >= valid-until ) - c4 PUSH CTOS LDREF 32 LDU 256 LDU ENDS // sign cs cnt dict cnt' pubk - s1 s3 XCPU // sign cs cnt dict pubk cnt' cnt - EQUAL 33 THROWIFNOT // ( seqno mismatch? ) - s3 PUSH HASHSU // sign cs cnt dict pubk hash - s0 s5 s5 XC2PU // pubk cs cnt dict hash sign pubk - CHKSIGNU // pubk cs cnt dict ? - 34 THROWIFNOT // signature mismatch - ACCEPT - ROT 64 LDU NIP 32 LDI LDREF ENDS // pubk cnt dict index value - s0 s2 XCHG 32 INT // pubk cnt value index dict 32 - DICTISETREF ROTREV // dict' pubk cnt - INC NEWC 32 STU 256 STU STREF ENDC c4 POP - }> - run_ticktock PROC:<{ - // store (now >> 8) into config param #-17, if the new value is different - c4 PUSH CTOS LDREF SWAP // s' D - NOW 8 RSHIFT# TUCK // s' t D t - NEWC 32 STU ENDC // s' t D c - -17 INT ROT 32 INT // s' t c -17 D 32 - DICTISETGETREF // s' t D' c' -1 or s' t D' 0 - IF:<{ CTOS 32 LDU ENDS }>ELSE<{ ZERO }> // s' t D' y' - ROT EQUAL IFNOT: // s' D' - NEWC STREF STSLICE ENDC // c - c4 POP - }> -}END>c -// code - // data empty_cell // libraries -1 // balance -0 1 2 register_smc // tock +GR$10 // balance +0 1 config_addr 6 register_smc // tock dup set_config_smc Masterchain swap ."config smart contract address = " 2dup .addr cr 2dup 7 .Addr cr "config-master" +suffix +".addr" save-address-verbose // Other data +/* + * + * Initial wallets (test) + * + */ + +// pubkey amount `create-wallet1` or pubkey amount `create-wallet2` +PK'PuZPPXK5Rff9SvtoS7Y9lUuEixvy-J6aishYFj3Qn6P0pJMb GR$1000000000 create-wallet1 +PK'PuYiB1zAWzr4p8j6I681+sGUrRGcn6Ylf7vXl0xaUl/w6Xfg GR$1700000000 create-wallet0 + +/* + * + * Create state + * + */ + create_state cr cr ."new state is:" cr dup B dup Bx. cr dup "zerostate" +suffix +".boc" tuck B>file ."(Initial masterchain state saved to file " type .")" cr Bhashu dup =: zerostate_fhash -."file hash=" dup x. space 256 u>B dup B>base64url type cr +."file hash= " dup X. space 256 u>B dup B>base64url type cr "zerostate" +suffix +".fhash" B>file -hashu dup =: zerostate_rhash ."root hash=" dup x. space 256 u>B dup B>base64url type cr +hashu dup =: zerostate_rhash ."root hash= " dup X. space 256 u>B dup B>base64url type cr "zerostate" +suffix +".rhash" B>file -basestate0_rhash ."Basestate0 root hash=" dup x. space 256 u>B B>base64url type cr -basestate0_fhash ."Basestate0 file hash=" dup x. space 256 u>B B>base64url type cr -zerostate_rhash ."Zerostate root hash=" dup x. space 256 u>B B>base64url type cr -zerostate_fhash ."Zerostate file hash=" dup x. space 256 u>B B>base64url type cr +basestate0_rhash ."Basestate0 root hash= " dup X. space 256 u>B B>base64url type cr +basestate0_fhash ."Basestate0 file hash= " dup X. space 256 u>B B>base64url type cr +zerostate_rhash ."Zerostate root hash= " dup X. space 256 u>B B>base64url type cr +zerostate_fhash ."Zerostate file hash= " dup X. space 256 u>B B>base64url type cr diff --git a/crypto/smartcont/gen-zerostate.fif b/crypto/smartcont/gen-zerostate.fif index 5c4de564..d41abaee 100644 --- a/crypto/smartcont/gen-zerostate.fif +++ b/crypto/smartcont/gen-zerostate.fif @@ -1,12 +1,15 @@ +#!/usr/bin/create-state -s "TonUtil.fif" include "Asm.fif" include +"Lists.fif" include def? $1 { @' $1 } { "" } cond constant suffix { suffix $+ } : +suffix 256 1<<1- 15 / constant AllOnes wc_master setworkchain --239 setglobalid // negative value means a test instance of the blockchain +13 setglobalid // negative value means a test instance of the blockchain +0x11EF55AA default-subwallet-id! // use this subwallet id in user wallets by default // Initial state of Workchain 0 (Basic workchain) @@ -17,119 +20,67 @@ dup dup 31 boc+>B dup Bx. cr dup "basestate0" +suffix +".boc" tuck B>file ."(Initial basechain state saved to file " type .")" cr Bhashu dup =: basestate0_fhash -."file hash=" dup x. space 256 u>B dup B>base64url type cr +."file hash=" dup 64x. space 256 u>B dup B>base64url type cr "basestate0" +suffix +".fhash" B>file hashu dup =: basestate0_rhash -."root hash=" dup x. space 256 u>B dup B>base64url type cr +."root hash=" dup 64x. space 256 u>B dup B>base64url type cr "basestate0" +suffix +".rhash" B>file -basestate0_rhash basestate0_fhash now 0 2 32 0 add-std-workchain +// root-hash file-hash start-at actual-min-split min-split-depth max-split-depth wc-id +basestate0_rhash basestate0_fhash now 0 4 8 0 add-std-workchain config.workchains! -// SmartContract #1 (Simple wallet) +/* + * + * Initial wallets (test) + * + */ -<{ SETCP0 DUP IFNOTRET // return if recv_internal - DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method - DROP c4 PUSHCTR CTOS 32 PLDU // cnt - }> - INC 32 THROWIF // fail unless recv_external - 512 INT LDSLICEX DUP 32 PLDU // sign cs cnt - c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS // sign cs cnt cnt' pubk - s1 s2 XCPU // sign cs cnt pubk cnt' cnt - EQUAL 33 THROWIFNOT // ( seqno mismatch? ) - s2 PUSH HASHSU // sign cs cnt pubk hash - s0 s4 s4 XC2PU // pubk cs cnt hash sign pubk - CHKSIGNU // pubk cs cnt ? - 34 THROWIFNOT // signature mismatch - ACCEPT - SWAP 32 LDU NIP 8 LDU LDREF ENDS // pubk cnt mode msg - SWAP SENDRAWMSG // pubk cnt ; ( message sent ) - INC NEWC 32 STU 256 STU ENDC c4 POPCTR -}>c -// code -GR bl word parse-pubkey } : parse-amount-pubkey +{ parse-amount-pubkey stage1 } : StA + +// test +StA 1001 PuZ8WoEOTgSR8-HopmCIVlOVSL94tNn9zgraiJqMk1SnioEQ + +// Stage 2 wallets + +{ swap create-wallet0 } : stage2 +{ parse-amount-pubkey stage2 } : StB + +// test +StB 999. PubMMGvqM08jx_6BibYldMclwjl-D88r7-u0_IEcDXHA30-G + +// Lockdowns + +{ swap create-wallet3b } : stage3 +{ parse-amount-pubkey stage3 } : StC + +// SmartContract #1 (Advanced wallet) + +// Create new advanced wallet; code adapted from `auto/wallet3-code.fif` +WCode3 // code + // data -Libs{ - x{ABACABADABACABA} s>c public_lib - x{1234} x{5678} |_ s>c private_lib -}Libs // libraries -GR$4999990000 // balance +empty_cell // libs +GR$4999999000 allocated-balance - // balance 0 // split_depth 0 // ticktock -AllOnes 0 * // address +AllOnes 1 * // address 6 // mode: create+setaddr register_smc dup make_special dup constant smc1_addr Masterchain over -2dup ."wallet address = " .addr cr 2dup 6 .Addr cr +2dup ."main wallet address = " .addr cr 2dup 6 .Addr cr "main-wallet" +suffix +".addr" save-address-verbose -// SmartContract #2 (Simple money giver for test network) -<{ SETCP0 DUP IFNOTRET // return if recv_internal - DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method - DROP c4 PUSHCTR CTOS 32 PLDU // cnt - }> - INC 32 THROWIF // fail unless recv_external - 32 LDU SWAP // cs cnt - c4 PUSHCTR CTOS 32 LDU ENDS // cs cnt cnt' - TUCK EQUAL 33 THROWIFNOT // ( seqno mismatch? ) - ACCEPT // cs cnt' - SWAP 8 LDU LDREF ENDS // cnt'' mode msg - GR$20 INT 3 INT RAWRESERVE // reserve all but 20 Grams from the balance - SWAP SENDRAWMSG - INC NEWC 32 STU ENDC c4 POPCTR // store cnt'' -}>c -// code - // data -empty_cell // libraries -GR$1000 // initial balance (1k test Grams) -0 0 AllOnes 6 * 6 register_smc -dup make_special dup constant smc2_addr -Masterchain over -2dup ."free test gram giver address = " .addr cr 2dup 6 .Addr cr -"testgiver" +suffix +".addr" save-address-verbose - -// SmartContract #3 -PROGRAM{ - recv_internal x{} PROC - run_ticktock PROC:<{ - c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS - NEWC ROT INC 32 STUR OVER 256 STUR ENDC - c4 POPCTR - // first 32 bits of persistent data have been increased - // remaining 256 bits with an address have been fetched - // create new empty message with 0.1 Grams to that address - NEWC b{00100010011111111} STSLICECONST TUCK 256 STU - 100000000 INT STGRAMS // store 0.1 Grams - 1 4 + 4 + 64 + 32 + 1+ 1+ INT STZEROES ENDC - // send raw message from Cell - ZERO SENDRAWMSG - -17 INT 256 STIR 130000000 INT STGRAMS - 107 INT STZEROES ENDC - ZERO // another message with 0.13 Grams to account -17 - NEWC b{11000100100000} "test" $>s |+ STSLICECONST - 123456789 INT STGRAMS - 107 INT STZEROES "Hello, world!" $>s STSLICECONST ENDC - ZERO SENDRAWMSG SENDRAWMSG // external message to address "test" - }> -}END>c -// code - // data -// empty_cell // libraries -Libs{ - x{ABACABADABACABA} s>c public_lib - x{1234} x{5678} |_ s>c public_lib -}Libs // libraries -GR$666 // balance -0 // split_depth -3 // ticktock: tick -2 // mode: create -register_smc -dup make_special dup constant smc3_addr -."address = " x. cr - /* * * SmartContract #4 (elector) @@ -138,7 +89,7 @@ dup make_special dup constant smc3_addr "auto/elector-code.fif" include // code in separate source file // data: dict dict dict grams uint32 uint256 empty_cell // libraries -GR$10 // balance: 10 grams +GR$500 // balance: 500 grams 0 // split_depth 2 // ticktock: tick AllOnes 3 * // address: -1:333...333 @@ -155,12 +106,12 @@ Masterchain swap * */ // version capabilities -0 capCreateStats config.version! +1 capCreateStats capBounceMsgBody or capReportVersion or capShortDequeue or config.version! // max-validators max-main-validators min-validators // 9 4 1 config.validator_num! 1000 100 13 config.validator_num! // min-stake max-stake min-total-stake max-factor -GR$10000 GR$10000000 GR$500000 sg~10 config.validator_stake_limits! +GR$10000 GR$10000000 GR$500000 sg~3 config.validator_stake_limits! // elected-for elect-start-before elect-end-before stakes-frozen-for // 400000 200000 4000 400000 config.election_params! // 4000 2000 500 1000 config.election_params! // DEBUG @@ -176,37 +127,61 @@ elector_addr config.elector_smc! config.special! // gas_price gas_limit special_gas_limit gas_credit block_gas_limit freeze_due_limit delete_due_limit flat_gas_limit flat_gas_price -- -1000 sg* 1 *M dup 10000 10 *M GR$0.1 GR$1.0 100 100000 config.gas_prices! -10000 sg* 1 *M 10 *M 10000 10 *M GR$0.1 GR$1.0 100 1000000 config.mc_gas_prices! +1000 sg* 1 *M dup 10000 10 *M GR$0.1 GR$1.0 1000 1000000 config.gas_prices! +10000 sg* 1 *M 10 *M 10000 10 *M GR$0.1 GR$1.0 1000 10000000 config.mc_gas_prices! // lump_price bit_price cell_price ihr_factor first_frac next_frac 1000000 1000 sg* 100000 sg* 3/2 sg*/ 1/3 sg*/ 1/3 sg*/ config.fwd_prices! 10000000 10000 sg* 1000000 sg* 3/2 sg*/ 1/3 sg*/ 1/3 sg*/ config.mc_fwd_prices! -// mc-cc-lifetime sh-cc-lifetime sh-val-lifetime sh-val-num -250 250 1000 7 config.catchain_params! -// round-candidates next-cand-delay-ms consensus-timeout-ms fast-attempts attempt-duration cc-max-deps max-block-size max-collated-size -3 2000 16000 3 8 4 2 *Mi 2 *Mi config.consensus_params! +// mc-cc-lifetime sh-cc-lifetime sh-val-lifetime sh-val-num mc-shuffle +250 250 1000 7 true config.catchain_params! +// round-candidates next-cand-delay-ms consensus-timeout-ms fast-attempts attempt-duration cc-max-deps max-block-size max-collated-size new-cc-ids +3 2000 16000 3 8 4 2 *Mi 2 *Mi true config.consensus_params! -128 *Ki 512 *Ki 1 *Mi triple // [ underload soft hard ] : block bytes limit -100000 500000 1000000 triple // gas limits -1000 5000 10000 triple // lt limits +128 *Ki 512 *Ki 1 *Mi triple // [ underload soft hard ] : block bytes limit +2000000 10000000 20000000 triple // gas limits +1000 5000 10000 triple // lt limits triple dup untriple config.mc_block_limits! untriple config.block_limits! -GR$1.7 GR$1 config.block_create_fees! +GR$160 GR$100 config.block_create_fees! // smc1_addr config.collector_smc! smc1_addr config.minter_smc! 1000000000000 -17 of-cc 666666666666 239 of-cc cc+ config.to_mint! -"validator-keys" +suffix +".pub" file>B -{ dup Blen } { 32 B| swap dup ."Validator public key = " Bx. cr - 17 add-validator } while drop +( 0 1 9 10 12 14 15 16 17 18 20 21 22 23 24 25 28 34 ) config.mandatory_params! +( -999 -1000 -1001 0 1 9 10 12 14 15 16 17 32 34 36 ) config.critical_params! + +// [ min_tot_rounds max_tot_rounds min_wins max_losses min_store_sec max_store_sec bit_pps cell_pps ] +// first for ordinary proposals, then for critical proposals +_( 2 3 2 2 1000000 10000000 1 500 ) +_( 4 7 4 2 5000000 20000000 2 1000 ) +config.param_proposals_setup! + +// deposit bit_pps cell_pps +GR$100 1 500 config.complaint_prices! + +{ file>B { dup Blen } { + 32 B| swap dup ."Validator public key = " Bx. cr + 17 add-validator + } while drop +} : load-keys-from-file + +false =: keys-from-file +keys-from-file +{ "validator-keys" +suffix +".pub" load-keys-from-file +} { + VPK'xrQTSOn2F9RjLgeHdSS0uIvde4Lv49l+/KXfJe0I7wYatz6e + B64{8E9AbL8blhRiBUoZt/b/vIo3+iZzglLKiL8MTf0g6Sg=} 256 B>u@ + 1 add-adnl-validator +} cond // newkeypair nip dup ."Validator #1 public key = " Bx. cr // 17 add-validator // newkeypair nip dup ."Validator #2 public key = " Bx. cr // 239 add-validator -100000 =: orig_vset_valid_for // original validator set valid 100000 seconds + +3000 =: orig_vset_valid_for // original validator set valid 3000 seconds now dup orig_vset_valid_for + 0 config.validators! /* @@ -222,24 +197,14 @@ now dup orig_vset_valid_for + 0 config.validators! dictnew dict, // vote dict b> // data empty_cell // libraries -GR$10 // balance +GR$500 // balance 0 1 config_addr 6 register_smc // tock dup set_config_smc Masterchain swap ."config smart contract address = " 2dup .addr cr 2dup 7 .Addr cr "config-master" +suffix +".addr" save-address-verbose // Other data - -/* - * - * Initial wallets (test) - * - */ -// pubkey amount `create-wallet1` or pubkey amount `create-wallet2` -PK'PuZPPXK5Rff9SvtoS7Y9lUuEixvy-J6aishYFj3Qn6P0pJMb GR$1000000000 create-wallet1 -PK'PuYiB1zAWzr4p8j6I681+sGUrRGcn6Ylf7vXl0xaUl/w6Xfg GR$1700000000 create-wallet0 - /* * * Create state diff --git a/crypto/smartcont/highload-wallet-code.fc b/crypto/smartcont/highload-wallet-code.fc index 70abb48e..8420c1d8 100644 --- a/crypto/smartcont/highload-wallet-code.fc +++ b/crypto/smartcont/highload-wallet-code.fc @@ -39,3 +39,9 @@ int seqno() method_id { return get_data().begin_parse().preload_uint(32); } + +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(64); + return cs.preload_uint(256); +} diff --git a/crypto/smartcont/highload-wallet-v2-code.fc b/crypto/smartcont/highload-wallet-v2-code.fc index 88d78855..b7626bbe 100644 --- a/crypto/smartcont/highload-wallet-v2-code.fc +++ b/crypto/smartcont/highload-wallet-v2-code.fc @@ -3,6 +3,22 @@ ;; this version does not use seqno for replay protection; instead, it remembers all recent query_ids ;; in this way several external messages with different query_id can be sent in parallel + +;; Note, when dealing with highload-wallet the following limits need to be checked and taken into account: +;; 1) Storage size limit. Currently, size of contract storage should be less than 65535 cells. If size of +;; old_queries will grow above this limit, exception in ActionPhase will be thrown and transaction will fail. +;; Failed transaction may be replayed. +;; 2) Gas limit. Currently, gas limit is 1'000'000 gas units, that means that there is a limit of how much +;; old queries may be cleaned in one tx. If number of expired queries will be higher, contract will stuck. + +;; That means that it is not recommended to set too high expiration date: +;; number of queries during expiration timespan should not exceed 1000. +;; Also, number of expired queries cleaned in one transaction should be below 100. + +;; Such precautions are not easy to follow, so it is recommended to use highload contract +;; only when strictly necessary and the developer understands the above details. + + () recv_internal(slice in_msg) impure { ;; do nothing for internal messages } @@ -63,3 +79,9 @@ int processed?(int query_id) method_id { (_, var found) = old_queries.udict_get?(64, query_id); return found ? true : - (query_id <= last_cleaned); } + +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(32 + 64); + return cs.preload_uint(256); +} diff --git a/crypto/smartcont/highload-wallet-v2-one.fif b/crypto/smartcont/highload-wallet-v2-one.fif new file mode 100644 index 00000000..bd7fa5aa --- /dev/null +++ b/crypto/smartcont/highload-wallet-v2-one.fif @@ -0,0 +1,112 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include +"GetOpt.fif" include + +{ show-options-help 1 halt } : usage + +"" =: comment // comment for simple transfers +true =: allow-bounce +false =: force-bounce +3 =: send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors +60 =: timeout // external message expires in 60 seconds +variable extra-currencies +{ extra-currencies @ cc+ extra-currencies ! } : extra-cc+! + +begin-options + " [-x *] [-n|-b] [-t] [-B ] [-C ] []" +cr +tab + +"Creates one request to highload wallet created by new-highload-wallet-v2.fif, with private key loaded from file .pk " + +"and address from .addr, and saves it into .boc ('wallet-query.boc' by default)" + disable-digit-options generic-help-setopt + "n" "--no-bounce" { false =: allow-bounce } short-long-option + "Clears bounce flag" option-help + "b" "--force-bounce" { true =: force-bounce } short-long-option + "Forces bounce flag" option-help + "x" "--extra" { $>xcc extra-cc+! } short-long-option-arg + "Indicates the amount of extra currencies to be transfered" option-help + "t" "--timeout" { parse-int =: timeout } short-long-option-arg + "Sets expiration timeout in seconds (" timeout (.) $+ +" by default)" option-help + "B" "--body" { =: body-boc-file } short-long-option-arg + "Sets the payload of the transfer message" option-help + "C" "--comment" { =: comment } short-long-option-arg + "Sets the comment to be sent in the transfer message" option-help + "m" "--mode" { parse-int =: send-mode } short-long-option-arg + "Sets transfer mode (0..255) for SENDRAWMSG (" send-mode (.) $+ +" by default)" + option-help + "h" "--help" { usage } short-long-option + "Shows a help message" option-help +parse-options + +$# dup 4 < swap 5 > or ' usage if +5 :$1..n + +true constant bounce +$1 =: file-base +$2 bounce parse-load-address force-bounce or allow-bounce and =: bounce 2=: dest_addr +$3 parse-int =: subwallet-id +$4 $>cc extra-cc+! extra-currencies @ 2=: amount +$5 "wallet-query" replace-if-null =: savefile +{ subwallet-id (.) $+ } : +subwallet + +file-base +subwallet +".addr" load-address +2dup 2constant wallet_addr +."Source wallet address = " 2dup .addr cr 6 .Addr cr +file-base +".pk" load-keypair nip constant wallet_pk + +def? body-boc-file { @' body-boc-file file>B B>boc } { comment simple-transfer-body } cond +constant body-cell + +."Transferring " amount .GR+cc ."to account " +dest_addr 2dup bounce 7 + .Addr ." = " .addr +."subwallet-id=0x" subwallet-id x. +."timeout=" timeout . ."bounce=" bounce . cr +."Body of transfer message is " body-cell = abort"more than 254 orders" + orders @ 16 udict!+ not abort"cannot add order to dictionary" + orders ! order# 1+! +} : add-order +// b body -- b' +{ tuck +} : create-int-msg +// ng wc addr bnc -- +{ ."Transferring " 3 roll .GR ."to account " + -rot 2dup 4 pick 7 + .Addr ." = " .addr ." bounce=" . cr +} : .transfer +// addr$ ng -- c +{ swap parse-smc-addr force-bounce or allow-bounce and // ng wc addr bnc + 2over 2over .transfer + create-int-msg +} : create-simple-transfer +// c m -- c' +{ } : create-order + +// addr$ ng -- +{ create-simple-transfer send-mode create-order add-order } : send +{ bl word bl word $>GR send } : SEND + +// create internal message + +send-mode create-order add-order + +// create external message +now timeout + 32 << hashu 32 1<<1- and + =: query_id + +dup ."signing message: " +dup ."resulting external message: " B dup Bx. cr +."Query_id is " query_id dup . ."= 0x" X. cr +savefile +".boc" tuck B>file +."(Saved to file " type .")" cr diff --git a/crypto/smartcont/highload-wallet-v2.fif b/crypto/smartcont/highload-wallet-v2.fif index ba1fc801..fd71f52a 100755 --- a/crypto/smartcont/highload-wallet-v2.fif +++ b/crypto/smartcont/highload-wallet-v2.fif @@ -50,7 +50,7 @@ variable order# order# 0! orders ! order# 1+! } : add-order // b body -- b' -{ tuck [-t] [-o] [...]" +cr +tab + +"Creates a request to managed DNS smart contract created by new-manual-dns.fif, with private key loaded from file .pk " + +"and address from -dns.addr, and saves it into ('" savefile $+ +"' by default)" + +cr +" is an operation description, one of" +cr +tab + +"add cat (smc | next | adnl | text )" +cr +tab + +"delete cat " +cr +tab + +"drop " + disable-digit-options generic-help-setopt + "t" "--timeout" { parse-int =: timeout } short-long-option-arg + "Sets expiration timeout in seconds (" timeout (.) $+ +" by default)" option-help + "o" "--output" { =: savefile } short-long-option-arg + "Sets output file for generated initialization message ('" savefile $+ +"' by default)" option-help + "h" "--help" { usage } short-long-option + "Shows a help message" option-help +parse-options + +$# 2 < ' usage if +2 :$1..n + +$1 =: file-base +$2 parse-int dup =: contract-id 32 fits ' usage ifnot +{ contract-id (.) $+ } : +contractid + +{ $* @ dup null? { second $@ ! } { drop } cond } : @skip +{ $* @ null? } : @end? +{ $* @ uncons $* ! } : @next +@next @next 2drop + +variable Actions +{ Actions @ cons Actions ! } : register-action + +{ @end? abort"subdomain name expected" @next dup $len 127 > abort"subdomain name too long" +} : parse-domain +{ @end? abort"category number expected" @next (number) 1 <> abort"category must be integer" + dup 256 fits not abort"category does not fit into 256 bit integer" + dup 0= abort"category must be non-zero" +} : parse-cat-num +{ @end? abort"`cat` expected" @next "cat" $= not abort"`cat` expected" parse-cat-num +} : parse-cat +{ @end? abort"smart contract address expected" + @next false parse-load-address drop triple +} : cl-parse-smc-addr +{ @end? abort"adnl address expected" + `adnl @next parse-adnl-addr pair +} : cl-parse-adnl-addr +{ @end? abort"subdomain record value expected" @next + dup "smc" $= { drop `smc cl-parse-smc-addr } { + dup "next" $= { drop `next cl-parse-smc-addr } { + dup "adnl" $= { drop cl-parse-adnl-addr } { + dup "text" $= { drop `text @next pair } { + "unknown record type "' swap $+ +"'" abort + } cond } cond } cond } cond +} : parse-value +{ ."Loading new code BoC from " dup type cr + file>B B>boc +} : load-new-code-from +{ @next dup "add" $= { drop `add parse-domain parse-cat parse-value 4 tuple register-action } { + dup "delete" $= { drop `delete parse-domain parse-cat triple register-action } { + dup "drop" $= { drop `drop parse-domain pair register-action } { + dup "upgrade" $= { drop `upgrade @next load-new-code-from pair register-action } { + "unknown action '" swap $+ +"'" abort + } cond } cond } cond } cond +} : parse-action +{ { @end? not } { parse-action } while } : parse-actions +parse-actions + +file-base +".pk" load-keypair nip constant wallet_pk +file-base +"-dns" +contractid +".addr" load-address +2dup 2constant smc_addr +."Managed manual DNS smart contract address = " 2dup .addr cr 6 .Addr cr + +."Actions: " Actions @ list-reverse .l cr + +// ( S -- S1 .. Sn n ) +{ 1 swap { dup "." $pos dup 0>= } { $| 1 $| nip rot 1+ swap } while drop swap +} : split-by-dots +// ( S -- s ) +{ dup $len dup 0= abort"subdomain cannot be empty" 126 > abort"subdomain too long" + dup 0 chr $pos 1+ abort"subdomain contains null characters" + split-by-dots s +// ( b V -- b' ) +{ dup first + dup `smc eq? { drop untriple 2swap drop x{9fd3} s, -rot Addr, 0 8 u, } { + dup `next eq? { drop untriple 2swap drop x{ba93} s, -rot Addr, } { + dup `adnl eq? { drop second swap x{ad01} s, swap 256 u, 0 8 u, } { + dup `text eq? { drop second swap x{1eda01} s, over $len 8 u, swap $, } { + abort"unknown value type" + } cond } cond } cond } cond +} : value, +{ subdomain>s dup sbits 3 >> + dup 63 > { drop s>c dict, } { rot swap 7 u, swap s, } cond +} : subdomain, +// ( A -- b ) +{ dup first + dup `add eq? { + drop 4 untuple -rot + b +// ( -- b ) +{ Actions @ dup null? { drop } { + uncons swap action>b { over null? not } { + b> swap uncons swap action>b rot ref, + } while nip } cond +} : serialize-actions +serialize-actions +dup brembits 888 < { b> +."Serialized actions are " hashu 32 1<<1- and + =: query_id + +dup ."signing message: " +dup ."resulting external message: " B dup Bx. cr +."Query_id is " query_id dup . ."= 0x" X. cr +."Query expires in " timeout . ."seconds" cr +savefile tuck B>file +."(Saved to file " type .")" cr diff --git a/crypto/smartcont/mathlib.fc b/crypto/smartcont/mathlib.fc new file mode 100644 index 00000000..f2dfd73f --- /dev/null +++ b/crypto/smartcont/mathlib.fc @@ -0,0 +1,939 @@ +{- + - + - FunC fixed-point mathematical library + - + -} + +{- + This file is part of TON FunC Standard Library. + + FunC Standard Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + FunC Standard Library 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 Lesser General Public License for more details. + +-} + +#include "stdlib.fc"; +#pragma version >=0.4.2; + +{---------------- HIGH-LEVEL FUNCTION DECLARATIONS -----------------} +{- + Most functions declared here work either with integers or with fixed-point numbers of type `fixed248`. + `fixedNNN` informally denotes an alias for type `int` used to represent fixed-point numbers with scale 2^NNN. + Prefix `fixedNNN::` is prepended to the names of high-level functions that accept arguments and return values of type `fixedNNN`. +-} + +{- function declarations have been commented out, otherwise they are not inlined by the current FunC compiler + +;; nearest integer to sqrt(a*b) for non-negative integers or fixed-point numbers a and b +int geom_mean(int a, int b) inline_ref; +;; integer square root +int sqrt(int a) inline; +;; fixed-point square root +;; fixed248 sqrt(fixed248 x) +int fixed248::sqrt(int x) inline; + +int fixed248::sqr(int x) inline; +const int fixed248::One; + +;; log(2) as fixed248 +int fixed248::log2_const() inline; +;; Pi as fixed248 +int fixed248::Pi_const() inline; + +;; fixed248 exp(fixed248 x) +int fixed248::exp(int x) inline_ref; +;; fixed248 exp2(fixed248 x) +int fixed248::exp2(int x) inline_ref; + +;; fixed248 log(fixed248 x) +int fixed248::log(int x) inline_ref; +;; fixed248 log2(fixed248 x) +int fixed248::log2(int x) inline; + +;; fixed248 pow(fixed248 x, fixed248 y) +int fixed248::pow(int x, int y) inline_ref; + +;; (fixed248, fixed248) sincos(fixed248 x); +(int, int) fixed248::sincos(int x) inline_ref; +;; fixed248 sin(fixed248 x); +int fixed248::sin(int x) inline; +;; fixed248 cos(fixed248 x); +int fixed248::cos(int x) inline; +;; fixed248 tan(fixed248 x); +int fixed248::tan(int x) inline_ref; +;; fixed248 cot(fixed248 x); +int fixed248::cot(int x) inline_ref; + + +;; fixed248 asin(fixed248 x); +int fixed248::asin(int x) inline; +;; fixed248 acos(fixed248 x); +int fixed248::acos(int x) inline; +;; fixed248 atan(fixed248 x); +int fixed248::atan(int x) inline_ref; +;; fixed248 acot(fixed248 x); +int fixed248::acot(int x) inline_ref; + +;; random number uniformly distributed in [0..1) +;; fixed248 random(); +int fixed248::random() impure inline; +;; random number with standard normal distribution (2100 gas on average) +;; fixed248 nrand(); +int fixed248::nrand() impure inline; +;; generates a random number approximately distributed according to the standard normal distribution (1200 gas) +;; (fails chi-squared test, but it is shorter and faster than fixed248::nrand()) +;; fixed248 nrand_fast(); +int fixed248::nrand_fast() impure inline; + +-} ;; end (declarations) + +{-------------------- INTERMEDIATE FUNCTIONS -----------------------} + +{- + Intermediate functions are used in the implementations of high-level `fixedNNN::...` functions + if necessary, they can be used to define additional high-level functions for other fixed-point types, such as fixed128, outside this library. They can be also used in a hypothetical floating-point FunC library. + For these reasons, the declarations of these functions are collected here. +-} + +{- function declarations have been commented out, otherwise they are not inlined by the current FunC compiler + +;; fixed258 tanh(fixed258 x, int steps); +int tanh_f258(int x, int n); + +;; computes exp(x)-1 for |x| <= log(2)/2. +;; fixed257 expm1(fixed257 x); +int expm1_f257(int x); + +;; computes (sin(x+xe),-cos(x+xe)) for |x| <= Pi/4, xe very small +;; this function is very accurate, error less than 0.7 ulp (consumes ~ 5500 gas) +;; (fixed256, fixed256) sincosn(fixed256 x, fixed259 xe) +(int, int) sincosn_f256(int x, int xe); + +;; compute (sin(x),1-cos(x)) in fixed256 for |x| < 16*atan(1/16) = 0.9987 +;; (fixed256, fixed257) sincosm1_f256(fixed256 x); +;; slightly less accurate than sincosn_f256() (error up to 3/2^256), but faster (~ 4k gas) and shorter +(int, int) sincosm1_f256(int x); + +;; compute (p, q) such that p/q = tan(x) for |x|<2*atan(1/2)=1899/2048=0.927 +;; (int, int) tan_aux(fixed256 x); +(int, int) tan_aux_f256(int x); + +;; returns (y, s) such that log(x) = y/2^256 + s*log(2) for positive integer x +;; this function is very precise (error less than 0.6 ulp) and consumes < 7k gas +;; (fixed256, int) log_aux_f256(int x); +(int, int) log_aux_f256(int x); + +;; returns (y, s) such that log2(x) = y/2^256 + s for positive integer x +;; this function is very precise (error less than 0.6 ulp) and consumes < 7k gas +;; (fixed256, int) log2_aux_f256(int x); +(int, int) log2_aux_f256(int x); + +;; compute (q, z) such that atan(x)=q*atan(1/32)+z for -1 <= x < 1 +;; this function is reasonably accurate (error < 7 ulp with ulp = 2^-261), but it consumes >7k gas +;; this is sufficient for most purposes +;; (int, fixed261) atan_aux(fixed256 x) +(int, int) atan_aux_f256(int x); + +;; fixed255 atan(fixed255 x); +int atan_f255(int x); + +;; for -1 <= x < 1 only +;; fixed256 atan_small(fixed256 x); +int atan_f256_small(int x); + +;; fixed255 asin(fixed255 x); +int asin_f255(int x); + +;; fixed254 acos(fixed255 x); +int acos_f255(int x); + +;; generates normally distributed pseudo-random number +;; fixed252 nrand(); +int nrand_f252(int x); + +;; a faster and shorter variant of nrand_f252() that fails chi-squared test +;; (should suffice for most purposes) +;; fixed252 nrand_fast(); +int nrand_fast_f252(int x); + +-} ;; end (declarations) + +{---------------- MISSING OPERATIONS AND BUILT-INS -----------------} + +int sgn(int x) asm "SGN"; + +;; compute floor(log2(x))+1 +int log2_floor_p1(int x) asm "UBITSIZE"; + +int mulrshiftr(int x, int y, int s) asm "MULRSHIFTR"; +int mulrshiftr256(int x, int y) asm "256 MULRSHIFTR#"; +(int, int) mulrshift256mod(int x, int y) asm "256 MULRSHIFT#MOD"; +(int, int) mulrshiftr256mod(int x, int y) asm "256 MULRSHIFTR#MOD"; +(int, int) mulrshiftr255mod(int x, int y) asm "255 MULRSHIFTR#MOD"; +(int, int) mulrshiftr248mod(int x, int y) asm "248 MULRSHIFTR#MOD"; +(int, int) mulrshiftr5mod(int x, int y) asm "5 MULRSHIFTR#MOD"; +(int, int) mulrshiftr6mod(int x, int y) asm "6 MULRSHIFTR#MOD"; +(int, int) mulrshiftr7mod(int x, int y) asm "7 MULRSHIFTR#MOD"; + +int lshift256divr(int x, int y) asm "256 LSHIFT#DIVR"; +(int, int) lshift256divmodr(int x, int y) asm "256 LSHIFT#DIVMODR"; +(int, int) lshift255divmodr(int x, int y) asm "255 LSHIFT#DIVMODR"; +(int, int) lshift2divmodr(int x, int y) asm "2 LSHIFT#DIVMODR"; +(int, int) lshift7divmodr(int x, int y) asm "7 LSHIFT#DIVMODR"; +(int, int) lshiftdivmodr(int x, int y, int s) asm "LSHIFTDIVMODR"; + +(int, int) rshiftr256mod(int x) asm "256 RSHIFTR#MOD"; +(int, int) rshiftr248mod(int x) asm "248 RSHIFTR#MOD"; +(int, int) rshiftr4mod(int x) asm "4 RSHIFTR#MOD"; +(int, int) rshift3mod(int x) asm "3 RSHIFT#MOD"; + +;; computes y - x (FunC compiler does not try to use this by itself) +int sub_rev(int x, int y) asm "SUBR"; + +int nan() asm "PUSHNAN"; +int is_nan(int x) asm "ISNAN"; + +{------------------------ SQUARE ROOTS ----------------------------} + +;; computes sqrt(a*b) exactly rounded to the nearest integer +;; for all 0 <= a, b <= 2^256-1 +;; may be used with b=1 or b=scale of fixed-point numbers +int geom_mean(int a, int b) inline_ref { + ifnot (min(a, b)) { + return 0; + } + int s = log2_floor_p1(a); ;; throws out of range error if a < 0 or b < 0 + int t = log2_floor_p1(b); + ;; NB: (a-b)/2+b == (a+b)/2, but without overflow for large a and b + int x = (s == t ? (a - b) / 2 + b : 1 << ((s + t) / 2)); + do { + ;; if always used with b=2^const, may be optimized to "const LSHIFTDIVC#" + ;; it is important to use `muldivc` here, not `muldiv` or `muldivr` + int q = (muldivc(a, b, x) - x) / 2; + x += q; + } until (q == 0); + return x; +} + +;; integer square root, computes round(sqrt(a)) for all a>=0. +;; note: `inline` is better than `inline_ref` for such simple functions +int sqrt(int a) inline { + return geom_mean(a, 1); +} + +;; version for fixed248 = fixed-point numbers with scale 2^248 +;; fixed248 sqrt(fixed248 x) +int fixed248::sqrt(int x) inline { + return geom_mean(x, 1 << 248); +} + +;; fixed255 sqrt(fixed255 x) +int fixed255::sqrt(int x) inline { + return geom_mean(x, 1 << 255); +} + +;; fixed248 sqr(fixed248 x); +int fixed248::sqr(int x) inline { + return muldivr(x, x, 1 << 248); +} + +;; fixed255 sqr(fixed255 x); +int fixed255::sqr(int x) inline { + return muldivr(x, x, 1 << 255); +} + +const int fixed248::One = (1 << 248); +const int fixed255::One = (1 << 255); + +{-------------------- USEFUL CONSTANTS --------------------} + +;; store huge constants in inline_ref functions for reuse +;; (y,z) where y=round(log(2)*2^256), z=round((log(2)*2^256-y)*2^128) +;; then log(2) = y/2^256 + z/2^384 +(int, int) log2_xconst_f256() inline_ref { + return (80260960185991308862233904206310070533990667611589946606122867505419956976172, -32272921378999278490133606779486332143); +} + +;; (y,z) where Pi = y/2^254 + z/2^382 +(int, int) Pi_xconst_f254() inline_ref { + return (90942894222941581070058735694432465663348344332098107489693037779484723616546, 108051869516004014909778934258921521947); +} + +;; atan(1/16) as fixed260 +int Atan1_16_f260() inline_ref { + return 115641670674223639132965820642403718536242645001775371762318060545014644837101; ;; true value is ...101.0089... +} + +;; atan(1/8) as fixed259 +int Atan1_8_f259() inline_ref { + return 115194597005316551477397594802136977648153890007566736408151129975021336532841; ;; correction -0.1687... +} + +;; atan(1/32) as fixed261 +int Atan1_32_f261() inline_ref { + return 115754418570128574501879331591757054405465733718902755858991306434399246026247; ;; correction 0.395... +} + +;; inline is better than inline_ref for such very small functions +int log2_const_f256() inline { + (int c, _) = log2_xconst_f256(); + return c; +} + +int fixed248::log2_const() inline { + return log2_const_f256() ~>> 8; +} + +int Pi_const_f254() inline { + (int c, _) = Pi_xconst_f254(); + return c; +} + +int fixed248::Pi_const() inline { + return Pi_const_f254() ~>> 6; +} + +{--------------- HYPERBOLIC TANGENT AND EXPONENT -------------------} + +;; hyperbolic tangent of small x via n+2 terms of Lambert's continued fraction +;; n=17: good for |x| < log(2)/4 = 0.173 +;; fixed258 tanh_f258(fixed258 x, int n) +int tanh_f258(int x, int n) inline_ref { + int x2 = muldivr(x, x, 1 << 255); ;; x^2 as fixed261 + int c = int a = (2 * n + 5) << 250; ;; a=2n+5 as fixed250 + int Two = (1 << 251); ;; 2. as fixed250 + repeat (n) { + a = (c -= Two) + muldivr(x2, 1 << 239, a); ;; a := 2k+1+x^2/a as fixed250, k=n+1,n,...,2 + } + a = (touch(3) << 254) + muldivr(x2, 1 << 243, a); ;; a := 3+x^2/a as fixed254 + ;; y = x/(1+a') = x - x*a'/(1+a') = x - x*x^2/(a+x^2) where a' = x^2/a + return x - (muldivr(x, x2, a + (x2 ~>> 7)) ~>> 7); +} + +;; fixed257 expm1_f257(fixed257 x) +;; computes exp(x)-1 for small x via 19 terms of Lambert's continued fraction for tanh(x/2) +;; good for |x| < log(2)/2 = 0.347 (n=17); consumes ~3500 gas +int expm1_f257(int x) inline_ref { + ;; (almost) compute tanh(x/2) first; x/2 as fixed258 = x as fixed257 + int x2 = muldivr(x, x, 1 << 255); ;; x^2 as fixed261 + int Two = (1 << 251); ;; 2. as fixed250 + int c = int a = touch(39) << 250; ;; a=2n+5 as fixed250 + repeat (17) { + a = (c -= Two) + muldivr(x2, 1 << 239, a); ;; a := 2k+1+x^2/a as fixed250, k=n+1,n,...,2 + } + a = (touch(3) << 254) + muldivr(x2, 1 << 243, a); ;; a := 3+x^2/a as fixed254 + ;; now tanh(x/2) = x/(1+a') where a'=x^2/a ; apply exp(x)-1=2*tanh(x/2)/(1-tanh(x/2)) + int t = (x ~>> 4) - a; ;; t:=x-a as fixed254 + return x - muldivr(x2, t / 2, a + mulrshiftr256(x, t) ~/ 4) ~/ 4; ;; x - x^2 * (x-a) / (a + x*(x-a)) +} + +;; expm1_f257() may be used to implement specific fixed-point exponentials +;; example: +;; fixed248 exp(fixed248 x) +int fixed248::exp(int x) inline_ref { + var (l2c, l2d) = log2_xconst_f256(); + ;; divide x by log(2) and convert to fixed257 + ;; (int q, x) = muldivmodr(x, 256, l2c); ;; unfortunately, no such built-in + (int q, x) = lshiftdivmodr(x, l2c, 8); + x = 2 * x - muldivr(q, l2d, 1 << 127); + int y = expm1_f257(x); + ;; result is (1 + y) * (2^q) --> ((1 << 257) + y) >> (9 - q) + return (y ~>> (9 - q)) - (-1 << (248 + q)); + ;; note that (y ~>> (9 - q)) + (1 << (248 + q)) leads to overflow when q=8 +} + +;; compute 2^x in fixed248 +;; fixed248 exp2(fixed248 x) +int fixed248::exp2(int x) inline_ref { + ;; (int q, x) = divmodr(x, 1 << 248); ;; no such built-in + (int q, x) = rshiftr248mod(x); + x = muldivr(x, log2_const_f256(), 1 << 247); + int y = expm1_f257(x); + return (y ~>> (9 - q)) - (-1 << (248 + q)); +} + +{--------------------- TRIGONOMETRIC FUNCTIONS -----------------------} + +;; fixed260 tan(fixed260 x); +;; computes tan(x) for small |x|> 10)) ~>> 9); +} + +;; fixed260 tan(fixed260 x); +int tan_f260(int x) inline_ref { + return tan_f260_inlined(x); +} + +;; fixed258 tan(fixed258 x); +;; computes tan(x) for small |x|> 6)) ~>> 5); +} + +;; fixed258 tan(fixed258 x); +int tan_f258(int x) inline_ref { + return tan_f258_inlined(x); +} + +;; (fixed259, fixed263) sincosm1(fixed259 x) +;; computes (sin(x), 1-cos(x)) for small |x|<2*atan(1/16) +(int, int) sincosm1_f259_inlined(int x) inline { + int t = tan_f260_inlined(x); ;; t=tan(x/2) as fixed260 + int tt = mulrshiftr256(t, t); ;; t^2 as fixed264 + int y = tt ~/ 512 + (1 << 255); ;; 1+t^2 as fixed255 + ;; 2*t/(1+t^2) as fixed259 and 2*t^2/(1+t^2) as fixed263 + ;; return (muldivr(t, 1 << 255, y), muldivr(tt, 1 << 255, y)); + return (t - muldivr(t / 2, tt, y) ~/ 256, tt - muldivr(tt / 2, tt, y) ~/ 256); +} + +(int, int) sincosm1_f259(int x) inline_ref { + return sincosm1_f259_inlined(x); +} + +;; computes (sin(x+xe),-cos(x+xe)) for |x| <= Pi/4, xe very small +;; this function is very accurate, error less than 0.7 ulp (consumes ~ 5500 gas) +;; (fixed256, fixed256) sincosn(fixed256 x, fixed259 xe) +(int, int) sincosn_f256(int x, int xe) inline_ref { + ;; var (q, x1) = muldivmodr(x, 8, Atan1_8_f259()); ;; no muldivmodr() builtin + var (q, x1) = lshift2divmodr(abs(x), Atan1_8_f259()); ;; reduce mod theta where theta=2*atan(1/8) + var (si, co) = sincosm1_f259(x1 * 2 + xe); + var (a, b, c) = (-1, 0, 1); + repeat (q) { ;; (a+b*I) *= (8+I)^2 = 63+16*I + (a, b, c) = (63 * a - 16 * b, 16 * a + 63 * b, 65 * c); + } + ;; now a/c = cos(q*theta), b/c = sin(q*theta) exactly(!) + ;; compute (a+b*I)*(1-co+si*I)/c + ;; (b, a) = (lshift256divr(b, c), lshift256divr(a, c)); + (b, int br) = lshift256divmodr(b, c); br = muldivr(br, 128, c); + (a, int ar) = lshift256divmodr(a, c); ar = muldivr(ar, 128, c); + return (sgn(x) * (((mulrshiftr256(b, co) - br) ~/ 16 - mulrshiftr256(a, si)) ~/ 8 - b), + a - ((mulrshiftr256(a, co) - ar) ~/ 16 + mulrshiftr256(b, si)) ~/ 8); +} + +;; compute (sin(x),1-cos(x)) in fixed256 for |x| < 16*atan(1/16) = 0.9987 +;; (fixed256, fixed257) sincosm1_f256(fixed256 x); +;; slightly less accurate than sincosn_f256() (error up to 3/2^256), but faster (~ 4k gas) and shorter +(int, int) sincosm1_f256(int x) inline_ref { + var (si, co) = sincosm1_f259_inlined(x); ;; compute (sin,1-cos)(x/8) in (fixed259,fixed263) + int r = 7; + repeat (r / 2) { + ;; 1-cos(2*x) = 2*sin(x)^2, sin(2*x) = 2*sin(x)*cos(x) + (co, si) = (mulrshiftr256(si, si), si - (mulrshiftr256(si, co) ~>> r)); + r -= 2; + } + return (si, co); +} + +;; compute (p, q) such that p/q = tan(x) for |x|<2*atan(1/2)=1899/2048=0.927 +;; (int, int) tan_aux(fixed256 x); +(int, int) tan_aux_f256(int x) inline_ref { + int t = tan_f258_inlined(x); ;; t=tan(x/4) as fixed258 + ;; t:=2*t/(1-t^2)=2*(t-t^3/(t^2-1)) + int tt = mulrshiftr256(t, t); ;; t^2 as fixed260 + t = muldivr(t, tt, tt ~/ 16 + (-1 << 256)) ~/ 16 - t; ;; now t=-tan(x/2) as fixed259 + return (t, mulrshiftr256(t, t) ~/ 4 + (-1 << 256)); ;; return (2*t, t^2-1) as fixed256 +} + +;; sincosm1_f256() and sincosn_f256() may be used to implement trigonometric functions for different fixed-point types +;; example: +;; (fixed248, fixed248) sincos(fixed248 x); +(int, int) fixed248::sincos(int x) inline_ref { + var (Pic, Pid) = Pi_xconst_f254(); + ;; (int q, x) = muldivmodr(x, 128, Pic); ;; no muldivmodr() builtin + (int q, x) = lshift7divmodr(x, Pic); ;; reduce mod Pi/2 + x = 2 * x - muldivr(q, Pid, 1 << 127); + (int si, int co) = sincosm1_f256(x); ;; doesn't make sense to use more accurate sincosn_f256() + co = (1 << 248) - (co ~>> 9); + si ~>>= 8; + repeat (q & 3) { + (si, co) = (co, - si); + } + return (si, co); +} + +;; fixed248 sin(fixed248 x); +;; inline is better than inline_ref for such simple functions +int fixed248::sin(int x) inline { + (int si, _) = fixed248::sincos(x); + return si; +} + +;; fixed248 cos(fixed248 x); +int fixed248::cos(int x) inline { + (_, int co) = fixed248::sincos(x); + return co; +} + +;; similarly, tan_aux_f256() may be used to implement tan() and cot() for specific fixed-point formats +;; fixed248 tan(fixed248 x); +;; not very accurate when |tan(x)| is very large (difficult to do better without floating-point numbers) +;; however, the relative accuracy is approximately 2^-247 in all cases, which is good enough for arguments given up to 2^-249 +int fixed248::tan(int x) inline_ref { + var (Pic, Pid) = Pi_xconst_f254(); + ;; (int q, x) = muldivmodr(x, 128, Pic); ;; no muldivmodr() builtin + (int q, x) = lshift7divmodr(x, Pic); ;; reduce mod Pi/2 + x = 2 * x - muldivr(q, Pid, 1 << 127); + var (a, b) = tan_aux_f256(x); ;; now a/b = tan(x') + if (q & 1) { + (a, b) = (b, - a); + } + return muldivr(a, 1 << 248, b); ;; either -b/a or a/b as fixed248 +} + +;; fixed248 cot(fixed248 x); +int fixed248::cot(int x) inline_ref { + var (Pic, Pid) = Pi_xconst_f254(); + (int q, x) = lshift7divmodr(x, Pic); ;; reduce mod Pi/2 + x = 2 * x - muldivr(q, Pid, 1 << 127); + var (b, a) = tan_aux_f256(x); ;; now b/a = tan(x') + if (q & 1) { + (a, b) = (b, - a); + } + return muldivr(a, 1 << 248, b); ;; either -b/a or a/b as fixed248 +} + +{----------------- INVERSE HYPERBOLIC TANGENT AND LOGARITHMS -----------------} + +;; inverse hyperbolic tangent of small x, evaluated by means of n terms of the continued fraction +;; valid for |x| < 2^-2.5 ~ 0.18 if n=37 (slightly less accurate with n=36) +;; |x| < 1/8 if n=32; |x| < 2^-3.5 if n=28; |x| < 1/16 if n=25 +;; |x| < 2^-4.5 if n=23; |x| < 1/32 if n=21; |x| < 1/64 if n=18 +;; fixed258 atanh(fixed258 x); +int atanh_f258(int x, int n) inline_ref { + int x2 = mulrshiftr256(x, x); ;; x^2 as fixed260 + int One = (1 << 254); + int a = One ~/ n + (1 << 255); ;; a := 2 + 1/n as fixed254 + repeat (n - 1) { + ;; a := 1 + (1 - x^2 / a)(1 + 1/n) as fixed254 + int t = One - muldivr(x2, 1 << 248, a); ;; t := 1 - x^2 / a + a = muldivr(t, n, (int n1 = n - 1)) + One; + n = n1; + } + ;; x / (1 - x^2 / a) = x / (1 - d) = x + x * d / (1 - d) for d = x^2 / a + ;; int d = muldivr(x2, 1 << 255, a - (x2 ~>> 6)); ;; d/(1-d) = x^2/(a-x^2) as fixed261 + ;; return x + (mulrshiftr256(x, d) ~>> 5); + return x + muldivr(x, x2 / 2, a - x2 ~/ 64) ~/ 32; +} + +;; number of terms n should be chosen as for atanh_f258() +;; fixed261 atanh(fixed261 x); +int atanh_f261_inlined(int x, int n) inline { + int x2 = mulrshiftr256(x, x); ;; x^2 as fixed266 + int One = (1 << 254); + int a = One ~/ n + (1 << 255); ;; a := 2 + 1/n as fixed254 + repeat (n - 1) { + ;; a := 1 + (1 - x^2 / a)(1 + 1/n) as fixed254 + int t = One - muldivr(x2, 1 << 242, a); ;; t := 1 - x^2 / a + a = muldivr(t, n, (int n1 = n - 1)) + One; + n = n1; + } + ;; x / (1 - x^2 / a) = x / (1 - d) = x + x * d / (1 - d) for d = x^2 / a + ;; int d = muldivr(x2, 1 << 255, a - (x2 ~>> 12)); ;; d/(1-d) = x^2/(a-x^2) as fixed267 + ;; return x + (mulrshiftr256(x, d) ~>> 11); + return x + muldivr(x, x2, a - x2 ~/ 4096) ~/ 4096; +} + +;; fixed261 atanh(fixed261 x); +int atanh_f261(int x, int n) inline_ref { + return atanh_f261_inlined(x, n); +} + +;; returns (y, s) such that log(x) = y/2^257 + s*log(2) for positive integer x +;; (fixed257, int) log_aux(int x) +(int, int) log_aux_f257(int x) inline_ref { + int s = log2_floor_p1(x); + x <<= 256 - s; + int t = touch(-1 << 256); + if ((x >> 249) <= 90) { + ;; t~touch(); + t >>= 1; + s -= 1; + } + x += t; + int 2x = 2 * x; + int y = lshift256divr(2x, (x >> 1) - t); + ;; y = 2x - (mulrshiftr256(2x, y) ~>> 2); ;; this line could improve precision on very rare occasions + return (atanh_f258(y, 36), s); +} + +;; computes 33^m for small m +int pow33(int m) inline { + int t = 1; + repeat (m) { t *= 33; } + return t; +} + +;; computes 33^m for small 0<=m<=22 +;; slightly faster than pow33() +int pow33b(int m) inline { + (int mh, int ml) = m /% 5; + int t = 1; + repeat (ml) { t *= 33; } + repeat (mh) { t *= 33 * 33 * 33 * 33 * 33; } + return t; +} + +;; returns (s, q, y) such that log(x) = s*log(2) + q*log(33/32) + y/2^260 for positive integer x +;; (int, int, fixed260) log_auxx_f260(int x); +(int, int, int) log_auxx_f260(int x) inline_ref { + int s = log2_floor_p1(x) - 1; + x <<= 255 - s; ;; rescale to 1 <= x < 2 as fixed255 + int t = touch(2873) << 244; ;; ~ (33/32)^11 ~ sqrt(2) as fixed255 + int x1 = (x - t) >> 1; + int q = muldivr(x1, 65, x1 + t) + 11; ;; crude approximation to round(log(x)/log(33/32)) + ;; t = 1; repeat (q) { t *= 33; } ;; t:=33^q, 0<=q<=22 + t = pow33b(q); + t <<= (51 - q) * 5; ;; t:=(33/32)^q as fixed255, nearest power of 33/32 to x + x -= t; + int y = lshift256divr(x << 4, (x >> 1) + t); ;; y = (x-t)/(x+t) as fixed261 + y = atanh_f261(y, 18); ;; atanh((x-t)/(x+t)) as fixed261, or log(x/t) as fixed260 + return (s, q, y); +} + +;; returns (y, s) such that log(x) = y/2^256 + s*log(2) for positive integer x +;; this function is very precise (error less than 0.6 ulp) and consumes < 7k gas +;; (fixed256, int) log_aux_f256(int x); +(int, int) log_aux_f256(int x) inline_ref { + var (s, q, y) = log_auxx_f260(x); + var (yh, yl) = rshiftr4mod(y); ;; y ~/% 16 , but FunC does not optimize this to RSHIFTR#MOD + ;; int Log33_32 = 3563114646320977386603103333812068872452913448227778071188132859183498739150; ;; log(33/32) as fixed256 + ;; int Log33_32_l = -3769; ;; log(33/32) = Log33_32 / 2^256 + Log33_32_l / 2^269 + yh += (yl * 512 + q * -3769) ~>> 13; ;; compensation, may be removed if slightly worse accuracy is acceptable + int Log33_32 = 3563114646320977386603103333812068872452913448227778071188132859183498739150; ;; log(33/32) as fixed256 + return (yh + q * Log33_32, s); +} + +;; returns (y, s) such that log2(x) = y/2^256 + s for positive integer x +;; this function is very precise (error less than 0.6 ulp) and consumes < 7k gas +;; (fixed256, int) log2_aux_f256(int x); +(int, int) log2_aux_f256(int x) inline_ref { + var (s, q, y) = log_auxx_f260(x); + y = lshift256divr(y, log2_const_f256()) ~>> 4; ;; y/log(2) as fixed256 + int Log33_32 = 5140487830366106860412008603913034462883915832139695448455767612111363481357; ;; log_2(33/32) as fixed256 + ;; Log33_32/2^256 happens to be a very precise approximation to log_2(33/32), no compensation required + return (y + q * Log33_32, s); +} + +;; functions log_aux_f256() and log2_aux_f256() may be used to implement specific fixed-point instances of log() and log2() + +;; fixed248 log(fixed248 x) +int fixed248::log(int x) inline_ref { + var (y, s) = log_aux_f256(x); + return muldivr(s - 248, log2_const_f256(), 1 << 8) + (y ~>> 8); + ;; return muldivr(s - 248, 80260960185991308862233904206310070533990667611589946606122867505419956976172, 1 << 8) + (y ~>> 8); +} + +;; fixed248 log2(fixed248 x) +int fixed248::log2(int x) inline { + var (y, s) = log2_aux_f256(x); + return ((s - 248) << 248) + (y ~>> 8); +} + +;; computes x^y as exp(y*log(x)), x >= 0 +;; fixed248 pow(fixed248 x, fixed248 y); +int fixed248::pow(int x, int y) inline_ref { + ifnot (y) { + return 1 << 248; ;; x^0 = 1 + } + if (x <= 0) { + int bad = (x | y) < 0; + return 0 >> bad; ;; 0^y = 0 if x=0 and y>=0; "out of range" exception otherwise + } + var (l, s) = log2_aux_f256(x); + s -= 248; ;; log_2(x) = s+l, l is fixed256, 0<=l<1 + ;; compute (s+l)*y = q+ll + var (q1, r1) = mulrshiftr248mod(s, y); ;; muldivmodr(s, y, 1 << 248) + var (q2, r2) = mulrshift256mod(l, y); + r2 >>= 247; + var (q3, r3) = rshiftr248mod(q2); ;; divmodr(q2, 1 << 248); + var (q, ll) = rshiftr248mod(r1 + r3); + ll = 512 * ll + r2; + q += q1 + q3; + ;; now log_2(x^y) = y*log_2(x) = q + ll, ss integer, ll fixed257, -1/2<=ll<1/2 + int sq = q + 248; + if (sq <= 0) { + return - (sq == 0); ;; underflow + } + int y = expm1_f257(mulrshiftr256(ll, log2_const_f256())); + return (y ~>> (9 - q)) - (-1 << sq); +} + +{--------------------- INVERSE TRIGONOMETRIC FUNCTIONS -------------------} + +;; number of terms n should be chosen as for atanh_f258() +;; fixed259 atan(fixed259 x); +int atan_f259(int x, int n) inline_ref { + int x2 = mulrshiftr256(x, x); ;; x^2 as fixed262 + int One = (1 << 254); + int a = One ~/ n + (1 << 255); ;; a := 2 + 1/n as fixed254 + repeat (n - 1) { + ;; a := 1 + (1 + x^2 / a)(1 + 1/n) as fixed254 + int t = One + muldivr(x2, 1 << 246, a); ;; t := 1 + x^2 / a + a = muldivr(t, n, (int n1 = n - 1)) + One; + n = n1; + } + ;; x / (1 + x^2 / a) = x / (1 + d) = x - x * d / (1 + d) = x - x * x^2/(a+x^2) for d = x^2 / a + return x - muldivr(x, x2, a + x2 ~/ 256) ~/ 256; +} + +;; number of terms n should be chosen as for atanh_f261() +;; fixed261 atan(fixed261 x); +int atan_f261_inlined(int x, int n) inline { + int x2 = mulrshiftr256(x, x); ;; x^2 as fixed266 + int One = (1 << 254); + int a = One ~/ n + (1 << 255); ;; a := 2 + 1/n as fixed254 + repeat (n - 1) { + ;; a := 1 + (1 + x^2 / a)(1 + 1/n) as fixed254 + int t = One + muldivr(x2, 1 << 242, a); ;; t := 1 + x^2 / a + a = muldivr(t, n, (int n1 = n - 1)) + One; + n = n1; + } + ;; x / (1 + x^2 / a) = x / (1 + d) = x - x * d / (1 + d) = x - x * x^2/(a+x^2) for d = x^2 / a + return x - muldivr(x, x2, a + x2 ~/ 4096) ~/ 4096; +} + +;; fixed261 atan(fixed261 x); +int atan_f261(int x, int n) inline_ref { + return atan_f261_inlined(x, n); +} + +;; computes (q,a,b) such that q is approximately atan(x)/atan(1/32) and a+b*I=(1+I/32)^q as fixed255 +;; then b/a=atan(q*atan(1/32)) exactly, and (a,b) is almost a unit vector pointing in the direction of (1,x) +;; must have |x|<1.1, x is fixed24 +;; (int, fixed255, fixed255) atan_aux_prereduce(fixed24 x); +(int, int, int) atan_aux_prereduce(int x) inline_ref { + int xu = abs(x); + int tc = 7214596; ;; tan(13*theta) as fixed24 where theta=atan(1/32) + int t1 = muldivr(xu - tc, 1 << 88, xu * tc + (1 << 48)); ;; tan(x') as fixed64 where x'=atan(x)-13*theta + ;; t1/(3+t1^2) * 3073/32 = x'/3 * 3072/32 = x' / (96/3072) = x' / theta + int q = muldivr(t1 * 3073, 1 << 59, t1 * t1 + (touch(3) << 128)) + 13; ;; approximately round(atan(x)/theta), 0<=q<=25 + var (pa, pb) = (33226912, 5232641); ;; (32+I)^5 + var (qh, ql) = q /% 5; + var (a, b) = (1 << (5 * (51 - q)), 0); ;; (1/32^q, 0) as fixed255 + repeat (ql) { ;; a+b*I *= 32+I + (a, b) = (sub_rev(touch(b), 32 * a), a + 32 * b); ;; same as (32 * a - b, 32 * b + a), but more efficient + } + repeat (qh) { ;; a+b*I *= (32+I)^5 = pa + pb*I + (a, b) = (a * pa - b * pb, a * pb + b * pa); + } + int xs = sgn(x); + return (xs * q, a, xs * b); +} + +;; compute (q, z) such that atan(x)=q*atan(1/32)+z for -1 <= x < 1 +;; this function is reasonably accurate (error < 7 ulp with ulp = 2^-261), but it consumes >7k gas +;; this is sufficient for most purposes +;; (int, fixed261) atan_aux(fixed256 x) +(int, int) atan_aux_f256(int x) inline_ref { + var (q, a, b) = atan_aux_prereduce(x ~>> 232); ;; convert x to fixed24 + ;; now b/a = tan(q*atan(1/32)) exactly, where q is near atan(x)/atan(1/32); so b/a is near x + ;; compute y = u/v = (a*x-b)/(a+b*x) as fixed261 ; then |y|<0.0167 = 1.07/64 and atan(x)=atan(y)+q*atan(1/32) + var (u, ul) = mulrshiftr256mod(a, x); + u = (ul ~>> 250) + ((u - b) << 6); ;; |u| < 1/32, convert fixed255 -> fixed261 + int v = a + mulrshiftr256(b, x); ;; v is scalar product of (a,b) and (1,x), it is approximately in [1..sqrt(2)] as fixed255 + int y = muldivr(u, 1 << 255, v); ;; y = u/v as fixed261 + int z = atan_f261_inlined(y, 18); ;; z = atan(x)-q*atan(1/32) + return (q, z); +} + +;; compute (q, z) such that atan(x)=q*atan(1/32)+z for -1 <= x < 1 +;; this function is very accurate (error < 2 ulp), but it consumes >7k gas +;; in most cases, faster function atan_aux_f256() should be used +;; (int, fixed261) atan_auxx(fixed256 x) +(int, int) atan_auxx_f256(int x) inline_ref { + var (q, a, b) = atan_aux_prereduce(x ~>> 232); ;; convert x to fixed24 + ;; now b/a = tan(q*atan(1/32)) exactly, where q is near atan(x)/atan(1/32); so b/a is near x + ;; compute y = (a*x-b)/(a+b*x) as fixed261 ; then |y|<0.0167 = 1.07/64 and atan(x)=atan(y)+q*atan(1/32) + ;; use sort of double precision arithmetic for this + var (u, ul) = mulrshiftr256mod(a, x); + ul /= 2; + u -= b; ;; |u| < 1/32 as fixed255 + var (v, vl) = mulrshiftr256mod(b, x); + vl /= 2; + v += a; ;; v is scalar product of (a,b) and (1,x), it is approximately in [1..sqrt(2)] as fixed255 + ;; y = (u + ul*eps) / (v + vl*eps) = u/v + (ul - vl * u/v)/v * eps where eps=1/2^255 + var (y, r) = lshift255divmodr(u, v); ;; y = u/v as fixed255 + int yl = muldivr(ul + r, 1 << 255, v) - muldivr(vl, y, v); ;; y/2^255 + yl/2^510 represent u/v + y = (yl ~>> 249) + (y << 6); ;; convert y to fixed261 + int z = atan_f261_inlined(y, 18); ;; z = atan(x)-q*atan(1/32) + return (q, z); +} + +;; consumes ~ 8k gas +;; fixed255 atan(fixed255 x); +int atan_f255(int x) inline_ref { + int s = (x ~>> 256); + touch(x); + if (s) { + x = lshift256divr(-1 << 255, x); ;; x:=-1/x as fixed256 + } else { + x *= 2; ;; convert to fixed256 + } + var (q, z) = atan_aux_f256(x); + ;; now atan(x) = z + q*atan(1/32) + s*(Pi/2), z is fixed261 + var (Pi_h, Pi_l) = Pi_xconst_f254(); ;; Pi/2 as fixed255 + fixed383 + var (qh, ql) = mulrshiftr6mod (q, Atan1_32_f261()); + return qh + s * Pi_h + (z + ql + muldivr(s, Pi_l, 1 << 122)) ~/ 64; +} + +;; computes atan(x) for -1 <= x < 1 only +;; fixed256 atan_small(fixed256 x); +int atan_f256_small(int x) inline_ref { + var (q, z) = atan_aux_f256(x); + ;; now atan(x) = z + q*atan(1/32), z is fixed261 + var (qh, ql) = mulrshiftr5mod (q, Atan1_32_f261()); + return qh + (z + ql) ~/ 32; +} + +;; fixed255 asin(fixed255 x); +int asin_f255(int x) inline_ref { + int a = fixed255::One - fixed255::sqr(x); ;; a:=1-x^2 + ifnot (a) { + return sgn(x) * Pi_const_f254(); ;; Pi/2 or -Pi/2 + } + int y = fixed255::sqrt(a); ;; sqrt(1-x^2) + int t = - lshift256divr(x, (-1 << 255) - y); ;; t = x/(1+sqrt(1-x^2)) avoiding overflow + return atan_f256_small(t); ;; asin(x)=2*atan(t) +} + +;; fixed254 acos(fixed255 x); +int acos_f255(int x) inline_ref { + int Pi = Pi_const_f254(); + if (x == (-1 << 255)) { + return Pi; ;; acos(-1) = Pi + } + Pi /= 2; + int y = fixed255::sqrt(fixed255::One - fixed255::sqr(x)); ;; sqrt(1-x^2) + int t = lshift256divr(x, (-1 << 255) - y); ;; t = -x/(1+sqrt(1-x^2)) avoiding overflow + return Pi + atan_f256_small(t) ~/ 2; ;; acos(x)=Pi/2 + 2*atan(t) +} + +;; consumes ~ 10k gas +;; fixed248 asin(fixed248 x) +int fixed248::asin(int x) inline { + return asin_f255(x << 7) ~>> 7; +} + +;; consumes ~ 10k gas +;; fixed248 acos(fixed248 x) +int fixed248::acos(int x) inline { + return acos_f255(x << 7) ~>> 6; +} + +;; consumes ~ 7500 gas +;; fixed248 atan(fixed248 x); +int fixed248::atan(int x) inline_ref { + int s = (x ~>> 249); + touch(x); + if (s) { + s = sgn(s); + x = lshift256divr(-1 << 248, x); ;; x:=-1/x as fixed256 + } else { + x <<= 8; ;; convert to fixed256 + } + var (q, z) = atan_aux_f256(x); + ;; now atan(x) = z + q*atan(1/32) + s*(Pi/2), z is fixed261 + return (z ~/ 64 + s * Pi_const_f254() + muldivr(q, Atan1_32_f261(), 64)) ~/ 128; ;; compute in fixed255, then convert +} + +;; fixed248 acot(fixed248 x); +int fixed248::acot(int x) inline_ref { + int s = (x ~>> 249); + touch(x); + if (s) { + x = lshift256divr(-1 << 248, x); ;; x:=-1/x as fixed256 + s = 0; + } else { + x <<= 8; ;; convert to fixed256 + s = sgn(x); + } + var (q, z) = atan_aux_f256(x); + ;; now acot(x) = - z - q*atan(1/32) + s*(Pi/2), z is fixed261 + return (s * Pi_const_f254() - z ~/ 64 - muldivr(q, Atan1_32_f261(), 64)) ~/ 128; ;; compute in fixed255, then convert +} + +{--------------------- PSEUDO-RANDOM NUMBERS -------------------} + +;; random number with standard normal distribution N(0,1) +;; generated by Kinderman--Monahan ratio method modified by J.Leva +;; spends ~ 2k..3k gas on average +;; fixed252 nrand(); +int nrand_f252() impure inline_ref { + var (x, s, t, A, B, r0) = (nan(), touch(29483) << 236, touch(-3167) << 239, 12845, 16693, 9043); + ;; 4/sqrt(e*Pi) = 1.369 loop iterations on average + do { + var (u, v) = (random() / 16 + 1, muldivr(random() - (1 << 255), 7027, 1 << 16)); ;; fixed252; 7027=ceil(sqrt(8/e)*2^12) + int va = abs(v); + var (u1, v1) = (u - s, va - t); ;; (u - 29483/2^16, abs(v) + 3167/2^13) as fixed252 + ;; Q := u1^2 + v1 * (A*v1 - B*u1) as fixed252 where A=12845/2^16, B=16693/2^16 + int Q = muldivr(u1, u1, 1 << 252) + muldivr(v1, muldivr(v1, A, 1 << 16) - muldivr(u1, B, 1 << 16), 1 << 252); + ;; must have 9043 / 2^15 < Q < 9125 / 2^15, otherwise accept if smaller, reject if larger + int Qd = (Q >> 237) - r0; + if ((Qd < 9125 - 9043) & (va / u < 16)) { + x = muldivr(v, 1 << 252, u); ;; x:=v/u as fixed252; reject immediately if |v/u| >= 16 + if (Qd >= 0) { ;; immediately accept if Qd < 0 + ;; rarely taken branch - 0.012 times per call on average + ;; check condition v^2 < -4*u^2*log(u), or equivalent condition u < exp(-x^2/4) for x=v/u + int xx = mulrshiftr256(x, x) ~/ 4; ;; x^2/4 as fixed248 + int ex = fixed248::exp(- xx) * 16; ;; exp(-x^2/4) as fixed252 + if (u > ex) { + x = nan(); ;; condition false, reject + } + } + } + } until (~ is_nan(x)); + return x; +} + +;; generates a random number approximately distributed according to the standard normal distribution +;; much faster than nrand_f252(), should be suitable for most purposes when only several random numbers are needed +;; fixed252 nrand_fast(); +int nrand_fast_f252() impure inline_ref { + int t = touch(-3) << 253; ;; -6. as fixed252 + repeat (12) { + t += random() / 16; ;; add together 12 uniformly random numbers + } + return t; +} + +;; random number uniformly distributed in [0..1) +;; fixed248 random(); +int fixed248::random() impure inline { + return random() >> 8; +} + +;; random number with standard normal distribution +;; fixed248 nrand(); +int fixed248::nrand() impure inline { + return nrand_f252() ~>> 4; +} + +;; generates a random number approximately distributed according to the standard normal distribution +;; fixed248 nrand_fast(); +int fixed248::nrand_fast() impure inline { + return nrand_fast_f252() ~>> 4; +} diff --git a/crypto/smartcont/multisig-code.fc b/crypto/smartcont/multisig-code.fc index 36e21a7b..243262b6 100644 --- a/crypto/smartcont/multisig-code.fc +++ b/crypto/smartcont/multisig-code.fc @@ -66,6 +66,13 @@ _ unpack_owner_info(slice cs) inline_ref { return (root_i, 0, 0, in_msg); } +(cell, ()) dec_flood(cell owner_infos, int creator_i) { + (slice owner_info, var found?) = owner_infos.udict_get?(8, creator_i); + (int public_key, int flood) = unpack_owner_info(owner_info); + owner_infos~udict_set_builder(8, creator_i, pack_owner_info(public_key, flood - 1)); + return (owner_infos, ()); +} + () try_init() impure inline_ref { ;; first query without signatures is always accepted (int wallet_id, int n, int k, int last_cleaned, cell owner_infos, cell pending_queries) = unpack_state(); @@ -82,10 +89,7 @@ _ unpack_owner_info(slice cs) inline_ref { send_raw_message(msg~load_ref(), mode); } pending_queries~udict_set_builder(64, query_id, begin_cell().store_int(0, 1)); - - (slice owner_info, var found?) = owner_infos.udict_get?(8, creator_i); - (int public_key, int flood) = unpack_owner_info(owner_info); - owner_infos~udict_set_builder(8, creator_i, pack_owner_info(public_key, flood - 1)); + owner_infos~dec_flood(creator_i); } else { pending_queries~udict_set_builder(64, query_id, begin_cell() .store_uint(1, 1) @@ -123,8 +127,8 @@ _ unpack_owner_info(slice cs) inline_ref { last_cleaned -= last_cleaned == 0; (slice owner_info, var found?) = owner_infos.udict_get?(8, root_i); - (int public_key, int flood) = unpack_owner_info(owner_info); throw_unless(31, found?); + (int public_key, int flood) = unpack_owner_info(owner_info); throw_unless(32, check_signature(root_hash, root_signature, public_key)); cell signatures = in_msg~load_dict(); @@ -154,11 +158,12 @@ _ unpack_owner_info(slice cs) inline_ref { cnt_bits |= mask; cnt += 1; + throw_if(41, ~ found? & (cnt < k) & (bound + ((60 * 60) << 32) > query_id)); + set_gas_limit(100000); ifnot (found?) { owner_infos~udict_set_builder(8, root_i, pack_owner_info(public_key, flood)); - throw_if(41, (cnt < k) & (bound + ((60 * 60) << 32) > query_id)); } (pending_queries, owner_infos) = update_pending_queries(pending_queries, owner_infos, msg, query_id, creator_i, cnt, cnt_bits, n, k); @@ -178,12 +183,15 @@ _ unpack_owner_info(slice cs) inline_ref { bound -= (64 << 32); ;; clean up records expired more than 64 seconds ago int old_last_cleaned = last_cleaned; do { - var (pending_queries', i, _, f) = pending_queries.udict_delete_get_min(64); + var (pending_queries', i, query, f) = pending_queries.udict_delete_get_min(64); f~touch(); if (f) { f = (i < bound); } if (f) { + if (query~load_int(1)) { + owner_infos~dec_flood(query~load_uint(8)); + } pending_queries = pending_queries'; last_cleaned = i; need_save = -1; diff --git a/crypto/smartcont/new-auto-dns.fif b/crypto/smartcont/new-auto-dns.fif new file mode 100644 index 00000000..3d4848b4 --- /dev/null +++ b/crypto/smartcont/new-auto-dns.fif @@ -0,0 +1,66 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include +"Asm.fif" include +"GetOpt.fif" include + +{ show-options-help 1 halt } : usage + +Basechain =: wc // create smart contract in basechain +"new-dns-query.boc" =: savefile +0 =: contract-id +variable dns-dict dictnew dns-dict ! + +begin-options + "
[-w] [-r] [-o]" +cr +tab + +"Creates a new automatic dns smart contract with 32-bit identifier controlled from wallet with address
" + +"and saves it into ('" savefile $+ +"' by default)" + disable-digit-options generic-help-setopt + "w" "--workchain" { parse-workchain-id =: wc } short-long-option-arg + "Selects workchain to create smart contract (" wc (.) $+ +" by default)" option-help + "r" "--random-id" { parse-int =: contract-id } short-long-option-arg + "Sets 'random' smart contract identifier (" contract-id (.) $+ +" by default)" option-help + "o" "--output" { =: savefile } short-long-option-arg + "Sets output file for generated initialization message ('" savefile $+ +"' by default)" option-help + "h" "--help" { usage } short-long-option + "Shows a help message" option-help +parse-options + +$# 6 <> ' usage if +6 :$1..n +$1 =: file-base +$2 false parse-load-address drop 2=: ctl-addr +$3 parse-int dup 0 10000000 in-range? ' usage ifnot =: reg-period +$4 $>GR =: reg-price +$5 parse-int dup 0< ' usage if =: ng-pb +$6 parse-int dup 0< ' usage if =: ng-pc +contract-id 32 fits ' usage ifnot +{ contract-id ?dup { (.) $+ } if } : +contractid + +."Creating new automatic DNS smart contract in workchain " wc . +."with random id " contract-id . cr +."Controlling wallet (smart contract) is " ctl-addr 6 .Addr cr +."Subdomain registration period is " reg-period . ."seconds" cr +."Subdomain registration price is " reg-price .GR +."+ " ng-pc . ."per cell + " ng-pb . ."per bit" cr + +// Create new automatic DNS; source code included from `auto/dns-auto-code.fif` +"auto/dns-auto-code.fif" include +// code + ref, // ctl + dns-dict @ dict, dictnew dict, // dom_dict gc + reg-period 30 u, reg-price Gram, ng-pc Gram, ng-pb Gram, // stdper ppc ppb + 0 64 u, // nhk lhk +b> // data +null // no libraries + // create StateInit +dup ."StateInit: " +dup ."External message for initialization is " B dup Bx. cr +savefile tuck B>file +."(Saved dns smart-contract creating query to file " type .")" cr diff --git a/crypto/smartcont/new-manual-dns.fif b/crypto/smartcont/new-manual-dns.fif new file mode 100644 index 00000000..47b57bbe --- /dev/null +++ b/crypto/smartcont/new-manual-dns.fif @@ -0,0 +1,63 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include +"Asm.fif" include +"GetOpt.fif" include + +{ show-options-help 1 halt } : usage + +Basechain =: wc // create smart contract in basechain +65536 =: timeout +"new-dns-query.boc" =: savefile +variable dns-dict dictnew dns-dict ! + +begin-options + " [-w] [-t] [-o]" +cr +tab + +"Creates a new manual dns smart contract with 32-bit identifier managed by private key .pk, " + +"and saves it into ('" savefile $+ +"' by default)" + disable-digit-options generic-help-setopt + "w" "--workchain" { parse-workchain-id =: wc } short-long-option-arg + "Selects workchain to create smart contract (" wc (.) $+ +" by default)" option-help + "t" "--timeout" { parse-int =: timeout } short-long-option-arg + "Sets expiration timeout for the initialization message in seconds (" timeout (.) $+ +" by default)" option-help + "o" "--output" { =: savefile } short-long-option-arg + "Sets output file for generated initialization message ('" savefile $+ +"' by default)" option-help + "h" "--help" { usage } short-long-option + "Shows a help message" option-help +parse-options + +$# 2 <> ' usage if +2 :$1..n +$1 =: file-base +$2 parse-int dup =: contract-id +32 fits ' usage ifnot +{ contract-id (.) $+ } : +contractid + +."Creating new manual DNS smart contract in workchain " wc . +."with contract id " contract-id . cr + +// Create new manual DNS; source code included from `auto/dns-manual-code.fif` +"auto/dns-manual-code.fif" include +// code + // data +null // no libraries + // create StateInit +dup ."StateInit: " +dup ."signing message: " +dup ."External message for initialization is " B dup Bx. cr +savefile tuck B>file +."(Saved dns smart-contract creating query to file " type .")" cr diff --git a/crypto/smartcont/new-pinger.fif b/crypto/smartcont/new-pinger.fif index e7324280..4caf7abc 100755 --- a/crypto/smartcont/new-pinger.fif +++ b/crypto/smartcont/new-pinger.fif @@ -2,20 +2,23 @@ "TonUtil.fif" include "Asm.fif" include -{ ."usage: " @' $0 type ." []" cr +{ ."usage: " $0 type ." []" cr ."Creates a new pinger in specified workchain, with destination address . " cr ."Resulting initialization query is saved into -query.boc ('new-pinger-query.boc' by default)" cr 1 halt } : usage -def? $# { @' $# dup 1 < swap 3 > or ' usage if } if + +$# dup 1 < swap 3 > or ' usage if +3 :$1..n Basechain 256 1<<1- 3 15 */ 2constant dest-addr Basechain constant wc // create a wallet in workchain 0 (basechain) -def? $1 { @' $1 parse-workchain-id =: wc } if // set workchain id from command line argument -def? $2 { @' $2 false parse-load-address drop 2=: dest-addr } if -def? $3 { @' $3 } { "new-pinger" } cond constant file-base +$1 parse-workchain-id =: wc // set workchain id from command line argument +$2 dup null? { drop } { false parse-load-address drop 2=: dest-addr } cond +$3 "new-pinger" replace-if-null constant file-base ."Creating new pinger in workchain " wc . cr +."Address to ping is " dest-addr 2dup .addr ." = " 6 .Addr cr // Create new simple pinger <{ SETCP0 DUP INC 1 RSHIFT# 32 THROWIF // fail unless recv_internal or recv_external diff --git a/crypto/smartcont/new-pow-testgiver.fif b/crypto/smartcont/new-pow-testgiver.fif new file mode 100644 index 00000000..676f1f03 --- /dev/null +++ b/crypto/smartcont/new-pow-testgiver.fif @@ -0,0 +1,59 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include +"Asm.fif" include + +{ ."usage: " $0 type ." []" cr + ."Creates a new proof-of-work testgiver with unique 32-bit identifier designed to deliver every seconds, with SHA256 hash complexity between 2** and 2**, with private key saved to or loaded from .pk" cr + ."('pow-giver.pk' by default)" cr 1 halt +} : usage +$# 7 - -2 and ' usage if + +8 :$1..n +$1 parse-workchain-id =: wc // set workchain id from command line argument +$2 parse-int dup =: subwallet-id + 0= abort"giver-id must be non-zero" +$3 $>GR =: amount +$4 parse-int dup =: interval + dup 24 ufits and 0= abort"invalid interval" +$5 parse-int dup =: min-cpl + 1- 8 ufits not abort"invalid minimal log-complexity (must be 1..256)" +$6 parse-int dup =: init-cpl + 1- 8 ufits not abort"invalid initial log-complexity (must be 1..256)" +$7 parse-int dup =: max-cpl + 1- 8 ufits not abort"invalid maximal log-complexity (must be 1..256)" +$8 "pow-giver" replace-if-null =: file-base + +min-cpl init-cpl > abort"initial complexity cannot be below minimal complexity" +max-cpl init-cpl < abort"initial complexity cannot exceed maximal complexity" +subwallet-id (.) 1 ' $+ does : +subwallet + +."Creating new proof-of-work testgiver in workchain " wc . +."with unique giver id " subwallet-id . cr +."Designed to give " amount .GR ."approximately every " interval . ."seconds" cr +."Complexity (in SHA256 hashes): min=" min-cpl 1<< . ."init=" init-cpl 1<< . ."max=" max-cpl 1<< . cr + +"auto/pow-testgiver-code.fif" include // code +{ 256 swap - 8 u, } : cpl, + ref, +b> // data +null // no libraries + // create StateInit +dup ."StateInit: " +dup ."signing message: " +dup ."External message for initialization is " B dup Bx. cr +file-base +subwallet +"-query.boc" tuck B>file +."(Saved proof-of-work testgiver creating query to file " type .")" cr diff --git a/crypto/smartcont/new-restricted-wallet2.fif b/crypto/smartcont/new-restricted-wallet2.fif index 8f95e4c1..90f54495 100644 --- a/crypto/smartcont/new-restricted-wallet2.fif +++ b/crypto/smartcont/new-restricted-wallet2.fif @@ -29,8 +29,8 @@ def? $3 { @' $3 } { "rwallet" } cond constant file-base } : make-rdict // Create new restricted wallet; code taken from `auto/restricted-wallet2-code.fif` -"auto/restricted-wallet-code.fif" include // code - // data +"auto/restricted-wallet2-code.fif" include // code + // data null // no libraries // create StateInit dup ."StateInit: " [-w][-r][-i][-t] []" +cr +tab + +"Creates a restricted lockup wallet v3 in the masterchain initialized by key loaded from " + +"and controlled by the private key corresponding to " +cr +tab + +"and saves its initialization query into new-.boc and its address into .addr ('rwallet.addr' by default)" + disable-digit-options generic-help-setopt + "r" "--restrict-mode" { parse-int =: restrict-mode } short-long-option-arg + "Selects a standard restriction mode: 1 for 18-month lockup, 2 for 4-year lockup" option-help + "w" "--workchain" { parse-int =: wc } short-long-option-arg + "Selects a workchain (" wc (.) $+ +" by default)" option-help + "i" "--subwallet-id" { parse-int =: subwallet-id } short-long-option-arg + "Sets 32-bit subwallet id (workchain plus " subwallet-base (.) $+ +" by default)" option-help + "x" "--expires-in" { parse-int =: expires-in } short-long-option-arg + "Expiration time of the initialization message (" expires-in (.) $+ + +" seconds by default)" option-help + "t" "--start-at" { dup "now" $= { drop now } { parse-int } cond =: start-at } short-long-option-arg + "Restriction start Unixtime (`now` by default)" option-help + "h" "--help" { usage } short-long-option + "Shows a help message" option-help +parse-options + +$# dup 3 < swap 4 > or ' usage if +4 :$1..n + +$1 =: filename-base +$2 parse-pubkey =: PubKey +$3 $>GR =: amount +$4 "rwallet" replace-if-null =: savefile-base +subwallet-id subwallet-base wc + replace-if-null =: subwallet-id +expires-in now + =: expires-at + +"new-" savefile-base $+ +".boc" =: savefile +savefile-base +".addr" =: savefile-addr + +wc 8 fits not abort"invalid workchain id" +subwallet-id 32 fits not abort"invalid subwallet-id" +expires-at 32 ufits not abort"invalid expiration time" +start-at 32 ufits not abort"invalid restriction start time" +restrict-mode dup 0 < swap 2 > or abort"unknown restriction mode" +filename-base +".pk" load-keypair =: init_pk =: init_pubkey + +."Creating new restricted lockup wallet v3 in workchain " wc . +."with restriction mode " restrict-mode . ."and nominal amount " amount .GR cr +."controlled by public key " PubKey .pubkey ." and initialized by public key " init_pubkey 256 B>u@ .pubkey cr +."(subwallet id is " subwallet-id ._ .")" cr + +// D x t -- D' +{ idict! not abort"cannot add value" +} : rdict-entry +{ 86400 * } : days* +{ 365 * days* } : years* +// balance -- dict +{ dictnew + over 31 -1<< rdict-entry + over 3/4 */ 91 days* rdict-entry + over 1/2 */ 183 days* rdict-entry + swap 1/4 */ 365 days* rdict-entry + 0 548 days* rdict-entry +} : make-rdict1 +{ dictnew + over 31 -1<< rdict-entry + over .9 */ 0 rdict-entry + over .6775 */ 1 years* rdict-entry + over .445 */ 2 years* rdict-entry + swap .2225 */ 3 years* rdict-entry + 0 4 years* 86400 + rdict-entry +} : make-rdict2 +{ dictnew + swap 31 -1<< rdict-entry + 0 0 rdict-entry +} : make-rdict0 + +amount +restrict-mode ?dup { + 1 = { make-rdict1 } { make-rdict2 } cond +} { make-rdict0 } cond +=: rdict + +."Restrictions start at " start-at ._ .": " cr +rdict 32 { swap . ."-> " Gram@ .GR cr true } idictforeach cr + +// Create new restricted wallet v3; code taken from `auto/restricted-wallet3-code.fif` +"auto/restricted-wallet3-code.fif" include // code + // data +null // no libraries + // create StateInit +dup ."StateInit: " +dup ."signing message: " +dup ."External message for initialization is " B dup Bx. cr +savefile tuck B>file +."(Saved wallet creating query to file " type .")" cr diff --git a/crypto/smartcont/new-wallet-v2.fif b/crypto/smartcont/new-wallet-v2.fif index d59c6c52..0bb2bcd1 100755 --- a/crypto/smartcont/new-wallet-v2.fif +++ b/crypto/smartcont/new-wallet-v2.fif @@ -15,8 +15,8 @@ def? $2 { @' $2 } { "new-wallet" } cond constant file-base // Create new advanced wallet; code adapted from `auto/wallet-code.fif` <{ SETCP0 DUP IFNOTRET // return if recv_internal - DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method - DROP c4 PUSHCTR CTOS 32 PLDU // cnt + DUP 85143 INT EQUAL OVER 78748 INT EQUAL OR IFJMP:<{ // "seqno" and "get_public_key" get-methods + 1 INT AND c4 PUSHCTR CTOS 32 LDU 256 PLDU CONDSEL // cnt or pubk }> INC 32 THROWIF // fail unless recv_external 9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU // signature in_msg msg_seqno valid_until cs diff --git a/crypto/smartcont/new-wallet-v3.fif b/crypto/smartcont/new-wallet-v3.fif index 658df598..3b31b35c 100644 --- a/crypto/smartcont/new-wallet-v3.fif +++ b/crypto/smartcont/new-wallet-v3.fif @@ -17,29 +17,8 @@ $3 "new-wallet" replace-if-null =: file-base ."with unique wallet id " subwallet-id . cr // Create new advanced wallet; code adapted from `auto/wallet3-code.fif` -<{ SETCP0 DUP IFNOTRET // return if recv_internal - DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method - DROP c4 PUSHCTR CTOS 32 PLDU // cnt - }> - INC 32 THROWIF // fail unless recv_external - 9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU 32 LDU // signature in_msg subwallet_id valid_until msg_seqno cs - NOW s1 s3 XCHG LEQ 35 THROWIF // signature in_msg subwallet_id cs msg_seqno - c4 PUSH CTOS 32 LDU 32 LDU 256 LDU ENDS // signature in_msg subwallet_id cs msg_seqno stored_seqno stored_subwallet public_key - s3 s2 XCPU EQUAL 33 THROWIFNOT // signature in_msg subwallet_id cs public_key stored_seqno stored_subwallet - s4 s4 XCPU EQUAL 34 THROWIFNOT // signature in_msg stored_subwallet cs public_key stored_seqno - s0 s4 XCHG HASHSU // signature stored_seqno stored_subwallet cs public_key msg_hash - s0 s5 s5 XC2PU // public_key stored_seqno stored_subwallet cs msg_hash signature public_key - CHKSIGNU 35 THROWIFNOT // public_key stored_seqno stored_subwallet cs - ACCEPT - WHILE:<{ - DUP SREFS // public_key stored_seqno stored_subwallet cs _51 - }>DO<{ // public_key stored_seqno stored_subwallet cs - 8 LDU LDREF s0 s2 XCHG // public_key stored_seqno stored_subwallet cs _56 mode - SENDRAWMSG - }> // public_key stored_seqno stored_subwallet cs - ENDS SWAP INC // public_key stored_subwallet seqno' - NEWC 32 STU 32 STU 256 STU ENDC c4 POP -}>c // >libref +"wallet-v3-code.fif" include +// >libref // code INC 32 THROWIF // fail unless recv_external 512 INT LDSLICEX DUP 32 PLDU // sign cs cnt diff --git a/crypto/smartcont/payment-channel-code.fc b/crypto/smartcont/payment-channel-code.fc new file mode 100644 index 00000000..b4b14d5c --- /dev/null +++ b/crypto/smartcont/payment-channel-code.fc @@ -0,0 +1,357 @@ +;; WARINIG: NOT READY FOR A PRODUCTION! + +int err:wrong_a_signature() asm "31 PUSHINT"; +int err:wrong_b_signature() asm "32 PUSHINT"; +int err:msg_value_too_small() asm "33 PUSHINT"; +int err:replay_protection() asm "34 PUSHINT"; +int err:no_timeout() asm "35 PUSHINT"; +int err:expected_init() asm "36 PUSHINT"; +int err:expected_close() asm "37 PUSHINT"; +int err:expected_payout() asm "37 PUSHINT"; +int err:no_promise_signature() asm "38 PUSHINT"; +int err:wrong_channel_id() asm "39 PUSHINT"; +int err:unknown_op() asm "40 PUSHINT"; +int err:not_enough_fee() asm "41 PUSHINT"; + +int op:pchan_cmd() asm "0x912838d1 PUSHINT"; + +int msg:init() asm "0x27317822 PUSHINT"; +int msg:close() asm "0xf28ae183 PUSHINT"; +int msg:timeout() asm "0x43278a28 PUSHINT"; +int msg:payout() asm "0x37fe7810 PUSHINT"; + +int state:init() asm "0 PUSHINT"; +int state:close() asm "1 PUSHINT"; +int state:payout() asm "2 PUSHINT"; + +int min_fee() asm "1000000000 PUSHINT"; + + +;; A - initial balance of Alice, +;; B - initial balance of B +;; +;; To determine balance we track nondecreasing list of promises +;; promise_A ;; promised by Alice to Bob +;; promise_B ;; promised by Bob to Alice +;; +;; diff - balance between Alice and Bob. 0 in the beginning +;; diff = promise_B - promise_A; +;; diff = clamp(diff, -A, +B); +;; +;; final_A = A + diff; +;; final_B = B + diff; + +;; Data pack/unpack +;; +_ unpack_data() inline_ref { + var cs = get_data().begin_parse(); + var res = (cs~load_ref(), cs~load_ref()); + cs.end_parse(); + return res; +} + +_ pack_data(cell config, cell state) impure inline_ref { + set_data(begin_cell().store_ref(config).store_ref(state).end_cell()); +} + +;; Config pack/unpack +;; +;; config$_ initTimeout:int exitTimeout:int a_key:int256 b_key:int256 a_addr b_addr channel_id:uint64 = Config; +;; +_ unpack_config(cell config) { + var cs = config.begin_parse(); + var res = ( + cs~load_uint(32), + cs~load_uint(32), + cs~load_uint(256), + cs~load_uint(256), + cs~load_ref().begin_parse(), + cs~load_ref().begin_parse(), + cs~load_uint(64), + cs~load_grams()); + cs.end_parse(); + return res; +} + +;; takes +;; signedMesage$_ a_sig:Maybe b_sig:Maybe msg:Message = SignedMessage; +;; checks signatures and unwap message. +(slice, (int, int)) unwrap_signatures(slice cs, int a_key, int b_key) { + int a? = cs~load_int(1); + slice a_sig = cs; + if (a?) { + a_sig = cs~load_ref().begin_parse().preload_bits(512); + } + var b? = cs~load_int(1); + slice b_sig = cs; + if (b?) { + b_sig = cs~load_ref().begin_parse().preload_bits(512); + } + int hash = cs.slice_hash(); + if (a?) { + throw_unless(err:wrong_a_signature(), check_signature(hash, a_sig, a_key)); + } + if (b?) { + throw_unless(err:wrong_b_signature(), check_signature(hash, b_sig, b_key)); + } + return (cs, (a?, b?)); +} + +;; process message, give state is stateInit +;; +;; stateInit signed_A?:Bool signed_B?:Bool min_A:Grams min_B:Grams expire_at:uint32 A:Grams B:Grams = State; +_ unpack_state_init(slice state) { + return ( + state~load_int(1), + state~load_int(1), + state~load_grams(), + state~load_grams(), + state~load_uint(32), + state~load_grams(), + state~load_grams()); + +} +_ pack_state_init(int signed_A?, int signed_B?, int min_A, int min_B, int expire_at, int A, int B) { + return begin_cell() + .store_int(state:init(), 3) + .store_int(signed_A?, 1) + .store_int(signed_B?, 1) + .store_grams(min_A) + .store_grams(min_B) + .store_uint(expire_at, 32) + .store_grams(A) + .store_grams(B).end_cell(); +} + +;; stateClosing$10 signed_A?:bool signed_B?:Bool promise_A:Grams promise_B:Grams exipire_at:uint32 A:Grams B:Grams = State; +_ unpack_state_close(slice state) { + return ( + state~load_int(1), + state~load_int(1), + state~load_grams(), + state~load_grams(), + state~load_uint(32), + state~load_grams(), + state~load_grams()); +} + +_ pack_state_close(int signed_A?, int signed_B?, int promise_A, int promise_B, int expire_at, int A, int B) { + return begin_cell() + .store_int(state:close(), 3) + .store_int(signed_A?, 1) + .store_int(signed_B?, 1) + .store_grams(promise_A) + .store_grams(promise_B) + .store_uint(expire_at, 32) + .store_grams(A) + .store_grams(B).end_cell(); +} + +_ send_payout(slice s_addr, int amount, int channel_id, int flags) impure { + send_raw_message(begin_cell() + .store_uint(0x10, 6) + .store_slice(s_addr) + .store_grams(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(msg:payout(), 32) + .store_uint(channel_id, 64) + .end_cell(), flags); +} + + +cell do_payout(int promise_A, int promise_B, int A, int B, slice a_addr, slice b_addr, int channel_id) impure { + accept_message(); + + int diff = promise_B - promise_A; + if (diff < - A) { + diff = - A; + } + if (diff > B) { + diff = B; + } + A += diff; + B -= diff; + + send_payout(b_addr, B, channel_id, 3); + send_payout(a_addr, A, channel_id, 3 + 128); + + return begin_cell() + .store_int(state:payout(), 3) + .store_grams(A) + .store_grams(B) + .end_cell(); +} + + +;; +;; init$000 inc_A:Grams inc_B:Grams min_A:Grams min_B:Grams = Message; +;; +cell with_init(slice state, int msg_value, slice msg, int msg_signed_A?, int msg_signed_B?, + slice a_addr, slice b_addr, int init_timeout, int channel_id, int min_A_extra) { + ;; parse state + (int signed_A?, int signed_B?, int min_A, int min_B, int expire_at, int A, int B) = unpack_state_init(state); + + if (expire_at == 0) { + expire_at = now() + init_timeout; + } + + int op = msg~load_uint(32); + if (op == msg:timeout()) { + throw_unless(err:no_timeout(), expire_at < now()); + return do_payout(0, 0, A, B, a_addr, b_addr, channel_id); + } + throw_unless(err:expected_init(), op == msg:init()); + + ;; unpack init message + (int inc_A, int inc_B, int upd_min_A, int upd_min_B, int got_channel_id) = + (msg~load_grams(), msg~load_grams(), msg~load_grams(), msg~load_grams(), msg~load_uint(64)); + throw_unless(err:wrong_channel_id(), got_channel_id == channel_id); + + ;; TODO: we should reserve some part of the value for comission + throw_if(err:msg_value_too_small(), msg_value < inc_A + inc_B); + throw_unless(err:replay_protection(), (msg_signed_A? < signed_A?) | (msg_signed_B? < signed_B?)); + + A += inc_A; + B += inc_B; + + signed_A? |= msg_signed_A?; + if (min_A < upd_min_A) { + min_A = upd_min_A; + } + + signed_B? |= msg_signed_B?; + if (min_B < upd_min_B) { + min_B = upd_min_B; + } + + if (signed_A? & signed_B?) { + A -= min_A_extra; + if ((min_A > A) | (min_B > B)) { + return do_payout(0, 0, A, B, a_addr, b_addr, channel_id); + } + + return pack_state_close(0, 0, 0, 0, 0, A, B); + } + + return pack_state_init(signed_A?, signed_B?, min_A, min_B, expire_at, A, B); +} + +;; close$001 extra_A:Grams extra_B:Grams sig:Maybe promise_A:Grams promise_B:Grams = Message; + +cell with_close(slice cs, slice msg, int msg_signed_A?, int msg_signed_B?, int a_key, int b_key, + slice a_addr, slice b_addr, int expire_timeout, int channel_id) { + ;; parse state + (int signed_A?, int signed_B?, int promise_A, int promise_B, int expire_at, int A, int B) = unpack_state_close(cs); + + if (expire_at == 0) { + expire_at = now() + expire_timeout; + } + + int op = msg~load_uint(32); + if (op == msg:timeout()) { + throw_unless(err:no_timeout(), expire_at < now()); + return do_payout(promise_A, promise_B, A, B, a_addr, b_addr, channel_id); + } + throw_unless(err:expected_close(), op == msg:close()); + + ;; also ensures that (msg_signed_A? | msg_signed_B?) is true + throw_unless(err:replay_protection(), (msg_signed_A? < signed_A?) | (msg_signed_B? < signed_B?)); + signed_A? |= msg_signed_A?; + signed_B? |= msg_signed_B?; + + ;; unpack close message + (int extra_A, int extra_B) = (msg~load_grams(), msg~load_grams()); + int has_sig = msg~load_int(1); + if (has_sig) { + slice sig = msg~load_ref().begin_parse().preload_bits(512); + int hash = msg.slice_hash(); + ifnot (msg_signed_A?) { + throw_unless(err:wrong_a_signature(), check_signature(hash, sig, a_key)); + extra_A = 0; + } + ifnot (msg_signed_B?) { + throw_unless(err:wrong_b_signature(), check_signature(hash, sig, b_key)); + extra_B = 0; + } + } else { + throw_unless(err:no_promise_signature(), msg_signed_A? & msg_signed_B?); + extra_A = 0; + extra_B = 0; + } + (int got_channel_id, int update_promise_A, int update_promise_B) = (msg~load_uint(64), msg~load_grams(), msg~load_grams()); + throw_unless(err:wrong_channel_id(), got_channel_id == channel_id); + + + accept_message(); + update_promise_A += extra_A; + if (promise_A < update_promise_A) { + promise_A = update_promise_A; + } + update_promise_B += extra_B; + if (promise_B < update_promise_B) { + promise_B = update_promise_B; + } + + if (signed_A? & signed_B?) { + return do_payout(promise_A, promise_B, A, B, a_addr, b_addr, channel_id); + } + return pack_state_close(signed_A?, signed_B?, promise_A, promise_B, expire_at, A, B); +} + +() with_payout(slice cs, slice msg, slice a_addr, slice b_addr, int channel_id) impure { + int op = msg~load_uint(32); + throw_unless(err:expected_payout(), op == msg:payout()); + (int A, int B) = (cs~load_grams(), cs~load_grams()); + throw_unless(err:not_enough_fee(), A + B + 1000000000 < get_balance().pair_first()); + accept_message(); + send_payout(b_addr, B, channel_id, 3); + send_payout(a_addr, A, channel_id, 3 + 128); +} + +() recv_any(int msg_value, slice msg) impure { + if (msg.slice_empty?()) { + return(); + } + ;; op is not signed, but we don't need it to be signed. + int op = msg~load_uint(32); + if (op <= 1) { + ;; simple transfer with comment, return + ;; external message will be aborted + ;; internal message will be accepted + return (); + } + throw_unless(err:unknown_op(), op == op:pchan_cmd()); + + (cell config, cell state) = unpack_data(); + (int init_timeout, int close_timeout, int a_key, int b_key, + slice a_addr, slice b_addr, int channel_id, int min_A_extra) = config.unpack_config(); + (int msg_signed_A?, int msg_signed_B?) = msg~unwrap_signatures(a_key, b_key); + + slice cs = state.begin_parse(); + int state_type = cs~load_uint(3); + + if (state_type == state:init()) { ;; init + state = with_init(cs, msg_value, msg, msg_signed_A?, msg_signed_B?, a_addr, b_addr, init_timeout, channel_id, min_A_extra); + } if (state_type == state:close()) { + state = with_close(cs, msg, msg_signed_A?, msg_signed_B?, a_key, b_key, a_addr, b_addr, close_timeout, channel_id); + } if (state_type == state:payout()) { + with_payout(cs, msg, a_addr, b_addr, channel_id); + } + + pack_data(config, state); +} + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + ;; TODO: uncomment when supported in tests + ;; var cs = in_msg_cell.begin_parse(); + ;; var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + ;; if (flags & 1) { + ;; ;; ignore all bounced messages + ;; return (); + ;; } + recv_any(msg_value, in_msg); +} + +() recv_external(slice in_msg) impure { + recv_any(0, in_msg); +} diff --git a/crypto/smartcont/pow-testgiver-code.fc b/crypto/smartcont/pow-testgiver-code.fc new file mode 100644 index 00000000..6361a461 --- /dev/null +++ b/crypto/smartcont/pow-testgiver-code.fc @@ -0,0 +1,158 @@ +;; Advanced TestGiver smart contract with Proof-of-Work verification + +int ufits(int x, int bits) impure asm "UFITSX"; + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +() check_proof_of_work(slice cs) impure inline_ref { + var hash = slice_hash(cs); + var ds = get_data().begin_parse(); + var (stored_seqno_sw, public_key, seed, pow_complexity) = (ds~load_uint(64), ds~load_uint(256), ds~load_uint(128), ds~load_uint(256)); + throw_unless(24, hash < pow_complexity); ;; hash problem NOT solved + var (op, flags, expire, whom, rdata1, rseed, rdata2) = (cs~load_uint(32), cs~load_int(8), cs~load_uint(32), cs~load_uint(256), cs~load_uint(256), cs~load_uint(128), cs~load_uint(256)); + cs.end_parse(); + ufits(expire - now(), 10); + throw_unless(25, (rseed == seed) & (rdata1 == rdata2)); + ;; Proof of Work correct + accept_message(); + randomize_lt(); + randomize(rdata1); + var (last_success, xdata) = (ds~load_uint(32), ds~load_ref()); + ds.end_parse(); + ds = xdata.begin_parse(); + var (amount, target_delta, min_cpl, max_cpl) = (ds~load_grams(), ds~load_uint(32), ds~load_uint(8), ds~load_uint(8)); + ds.end_parse(); + ;; recompute complexity + int delta = now() - last_success; + if (delta > 0) { + int factor = muldivr(delta, 1 << 128, target_delta); + factor = min(max(factor, 7 << 125), 9 << 125); ;; factor must be in range 7/8 .. 9/8 + pow_complexity = muldivr(pow_complexity, factor, 1 << 128); ;; rescale complexity + pow_complexity = min(max(pow_complexity, 1 << min_cpl), 1 << max_cpl); + } + ;; update data + set_data(begin_cell() + .store_uint(stored_seqno_sw, 64) + .store_uint(public_key, 256) + .store_uint(random() >> 128, 128) ;; new seed + .store_uint(pow_complexity, 256) + .store_uint(now(), 32) ;; new last_success + .store_ref(xdata) + .end_cell()); + commit(); + ;; create outbound message + send_raw_message(begin_cell() + .store_uint(((flags & 1) << 6) | 0x84, 9) + .store_int(flags >> 2, 8) + .store_uint(whom, 256) + .store_grams(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .end_cell(), 3); +} + +() rescale_complexity(slice cs) impure inline_ref { + var (op, expire) = (cs~load_uint(32), cs~load_uint(32)); + cs.end_parse(); + int time = now(); + throw_unless(28, time > expire); + var ds = get_data().begin_parse(); + var (skipped_data, pow_complexity, last_success, xdata) = (ds~load_bits(64 + 256 + 128), ds~load_uint(256), ds~load_uint(32), ds~load_ref()); + ds.end_parse(); + throw_unless(29, expire > last_success); + ds = xdata.begin_parse(); + var (amount, target_delta) = (ds~load_grams(), ds~load_uint(32)); + int delta = time - last_success; + throw_unless(30, delta >= target_delta * 16); + accept_message(); + var (min_cpl, max_cpl) = (ds~load_uint(8), ds~load_uint(8)); + ds.end_parse(); + int factor = muldivr(delta, 1 << 128, target_delta); + int max_complexity = (1 << max_cpl); + int max_factor = muldiv(max_complexity, 1 << 128, pow_complexity); + pow_complexity = (max_factor < factor ? max_complexity : muldivr(pow_complexity, factor, 1 << 128)); + last_success = time - target_delta; + set_data(begin_cell() + .store_slice(skipped_data) + .store_uint(pow_complexity, 256) + .store_uint(last_success, 32) ;; new last_success + .store_ref(xdata) + .end_cell()); +} + +(slice, ()) ~update_params(slice ds, cell pref) inline_ref { + var cs = pref.begin_parse(); + var reset_cpl = cs~load_uint(8); + var (seed, pow_complexity, last_success) = (ds~load_uint(128), ds~load_uint(256), ds~load_uint(32)); + if (reset_cpl) { + randomize(seed); + pow_complexity = (1 << reset_cpl); + seed = (random() >> 128); + } + var c = begin_cell() + .store_uint(seed, 128) + .store_uint(pow_complexity, 256) + .store_uint(now(), 32) + .store_ref(begin_cell().store_slice(cs).end_cell()) + .end_cell(); + return (begin_parse(c), ()); +} + +() recv_external(slice in_msg) impure { + var op = in_msg.preload_uint(32); + if (op == 0x4d696e65) { + ;; Mine = Obtain test grams by presenting valid proof of work + return check_proof_of_work(in_msg); + } + if (op == 0x5253636c) { + ;; RScl = Rescale complexity if no success for long time + return rescale_complexity(in_msg); + } + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + throw_if(35, valid_until <= now()); + var ds = get_data().begin_parse(); + var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256)); + throw_unless(33, msg_seqno == stored_seqno); + throw_unless(34, (subwallet_id == stored_subwallet) | (subwallet_id == 0)); + throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + cs~touch(); + while (cs.slice_refs()) { + var ref = cs~load_ref(); + var mode = cs~load_uint(8); + if (mode < 0xff) { + send_raw_message(ref, mode); + } else { + ds~update_params(ref); + } + } + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .store_slice(ds) + .end_cell()); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +;; gets (seed, pow_complexity, amount, interval) +(int, int, int, int) get_pow_params() method_id { + var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); + var (seed, pow_complexity, xdata) = (ds~load_uint(128), ds~load_uint(256), ds.preload_ref()); + ds = xdata.begin_parse(); + return (seed, pow_complexity, ds~load_grams(), ds.preload_uint(32)); +} + +int get_public_key() method_id { + var ds = get_data().begin_parse(); + ds~load_uint(32 + 32); + return ds.preload_uint(256); +} diff --git a/crypto/smartcont/restricted-wallet-code.fc b/crypto/smartcont/restricted-wallet-code.fc index 83639dfd..a3168340 100644 --- a/crypto/smartcont/restricted-wallet-code.fc +++ b/crypto/smartcont/restricted-wallet-code.fc @@ -62,6 +62,12 @@ int seqno() method_id { return get_data().begin_parse().preload_uint(32); } -int balance() method_id { - return restricted?() ? 0 : get_balance().first(); +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(32); + return cs.preload_uint(256); +} + +int balance() method_id { + return restricted?() ? 0 : get_balance().pair_first(); } diff --git a/crypto/smartcont/restricted-wallet2-code.fc b/crypto/smartcont/restricted-wallet2-code.fc index fa57e77d..fbad6c09 100644 --- a/crypto/smartcont/restricted-wallet2-code.fc +++ b/crypto/smartcont/restricted-wallet2-code.fc @@ -1,13 +1,16 @@ ;; Restricted wallet (a variant of wallet-code.fc) -;; until configuration parameter -13 is set, accepts messages only to elector smc +;; restricts access to parts of balance until certain dates () recv_internal(slice in_msg) impure { ;; do nothing for internal messages } -_ days_passed() inline { - var p = config_param(-13); - return null?(p) ? -1 : (now() - begin_parse(p).preload_uint(32)) / 86400; +_ seconds_passed(int start_at, int utime) inline_ref { + ifnot (start_at) { + var p = config_param(-13); + start_at = null?(p) ? 0 : begin_parse(p).preload_uint(32); + } + return start_at ? utime - start_at : -1; } () recv_external(slice in_msg) impure { @@ -16,7 +19,7 @@ _ days_passed() inline { var (msg_seqno, valid_until) = (cs~load_uint(32), cs~load_uint(32)); throw_if(35, valid_until <= now()); var ds = get_data().begin_parse(); - var (stored_seqno, public_key, rdict) = (ds~load_uint(32), ds~load_uint(256), ds~load_dict()); + var (stored_seqno, public_key, start_at, rdict) = (ds~load_uint(32), ds~load_uint(256), ds~load_uint(32), ds~load_dict()); ds.end_parse(); throw_unless(33, msg_seqno == stored_seqno); ifnot (msg_seqno) { @@ -24,14 +27,15 @@ _ days_passed() inline { set_data(begin_cell() .store_uint(stored_seqno + 1, 32) .store_uint(public_key, 256) + .store_uint(start_at, 32) .store_dict(rdict) .end_cell()); return (); } throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key)); accept_message(); - var ts = days_passed(); - var (_, value, found) = rdict.idict_get_preveq?(16, ts); + var ts = seconds_passed(start_at, now()); + var (_, value, found) = rdict.idict_get_preveq?(32, ts); if (found) { raw_reserve(value~load_grams(), 2); } @@ -45,6 +49,7 @@ _ days_passed() inline { set_data(begin_cell() .store_uint(stored_seqno + 1, 32) .store_uint(public_key, 256) + .store_uint(start_at, 32) .store_dict(rdict) .end_cell()); } @@ -55,15 +60,29 @@ int seqno() method_id { return get_data().begin_parse().preload_uint(32); } -int balance() method_id { +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(32); + return cs.preload_uint(256); +} + +int compute_balance_at(int utime) inline_ref { var ds = get_data().begin_parse().skip_bits(32 + 256); - var rdict = ds~load_dict(); + var (start_at, rdict) = (ds~load_uint(32), ds~load_dict()); ds.end_parse(); - var ts = days_passed(); - var balance = get_balance().first(); - var (_, value, found) = rdict.idict_get_preveq?(16, ts); + var ts = seconds_passed(start_at, utime); + var balance = get_balance().pair_first(); + var (_, value, found) = rdict.idict_get_preveq?(32, ts); if (found) { balance = max(balance - value~load_grams(), 0); } return balance; } + +int balance_at(int utime) method_id { + return compute_balance_at(utime); +} + +int balance() method_id { + return compute_balance_at(now()); +} diff --git a/crypto/smartcont/restricted-wallet3-code.fc b/crypto/smartcont/restricted-wallet3-code.fc new file mode 100644 index 00000000..aab86065 --- /dev/null +++ b/crypto/smartcont/restricted-wallet3-code.fc @@ -0,0 +1,103 @@ +;; Restricted wallet initialized by a third party (a variant of restricted-wallet2-code.fc) +;; restricts access to parts of balance until certain dates + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +_ seconds_passed(int start_at, int utime) inline_ref { + ifnot (start_at) { + var p = config_param(-13); + start_at = null?(p) ? 0 : begin_parse(p).preload_uint(32); + } + return start_at ? utime - start_at : -1; +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + throw_if(35, valid_until <= now()); + var ds = get_data().begin_parse(); + var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256)); + throw_unless(33, msg_seqno == stored_seqno); + throw_unless(34, subwallet_id == stored_subwallet); + throw_unless(36, check_signature(slice_hash(in_msg), signature, public_key)); + ifnot (msg_seqno) { + public_key = ds~load_uint(256); ;; load "final" public key + ds.end_parse(); + cs~touch(); + var (start_at, rdict) = (cs~load_uint(32), cs~load_dict()); + cs.end_parse(); + accept_message(); + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .store_uint(start_at, 32) + .store_dict(rdict) + .end_cell()); + return (); + } + var (start_at, rdict) = (ds~load_uint(32), ds~load_dict()); + ds.end_parse(); + accept_message(); + var ts = seconds_passed(start_at, now()); + var (_, value, found) = rdict.idict_get_preveq?(32, ts); + if (found) { + raw_reserve(value~load_grams(), 2); + } + cs~touch(); + while (cs.slice_refs()) { + var mode = cs~load_uint(8); + var msg = cs~load_ref(); + send_raw_message(msg, mode); + } + cs.end_parse(); + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .store_uint(start_at, 32) + .store_dict(rdict) + .end_cell()); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int wallet_id() method_id { + var ds = get_data().begin_parse(); + ds~load_uint(32); + return ds.preload_uint(32); +} + +int get_public_key() method_id { + var ds = get_data().begin_parse(); + ds~load_uint(32 + 32); + return ds.preload_uint(256); +} + +int compute_balance_at(int utime) inline_ref { + var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); + var (start_at, rdict) = (ds~load_uint(32), ds~load_dict()); + ds.end_parse(); + var ts = seconds_passed(start_at, utime); + var balance = get_balance().pair_first(); + var (_, value, found) = rdict.idict_get_preveq?(32, ts); + if (found) { + balance = max(balance - value~load_grams(), 0); + } + return balance; +} + +int balance_at(int utime) method_id { + return compute_balance_at(utime); +} + +int balance() method_id { + return compute_balance_at(now()); +} diff --git a/crypto/smartcont/show-addr.fif b/crypto/smartcont/show-addr.fif index 506aa492..c421a879 100755 --- a/crypto/smartcont/show-addr.fif +++ b/crypto/smartcont/show-addr.fif @@ -12,11 +12,13 @@ $1 "new-wallet" replace-if-null =: file-base file-base +".addr" dup ."Loading wallet address from " type cr file>B 32 B| dup Blen { 32 B>i@ } { drop Basechain } cond constant wallet_wc 256 B>u@ dup constant wallet_addr -."Source wallet address = " wallet_wc ._ .":" x. cr -wallet_wc wallet_addr 2dup 7 smca>$ ."Non-bounceable address (for init only): " type cr -6 smca>$ ."Bounceable address (for later access): " type cr +."Source wallet address = " wallet_wc swap 2dup .addr cr +."Non-bounceable address (for init only): " 2dup 7 .Addr cr +."Bounceable address (for later access): " 6 .Addr cr file-base +".pk" dup file-exists? { dup file>B dup Blen 32 <> abort"Private key must be exactly 32 bytes long" - =: wallet_pk ."Private key available in file " type cr + tuck =: wallet_pk ."Private key available in file " type cr + priv>pub 256 B>u@ + dup ."Corresponding public key is " .pubkey ." = " 64X. cr } { ."Private key file " type ." not found" cr } cond diff --git a/crypto/smartcont/simple-wallet-ext-code.fc b/crypto/smartcont/simple-wallet-ext-code.fc index 52c4619f..fb43e833 100644 --- a/crypto/smartcont/simple-wallet-ext-code.fc +++ b/crypto/smartcont/simple-wallet-ext-code.fc @@ -45,6 +45,11 @@ int seqno() method_id { return get_data().begin_parse().preload_uint(32); } +int get_public_key() method_id { + var (seqno, public_key) = load_state(); + return public_key; +} + cell create_init_state(int public_key) method_id { return create_state(0, public_key); } diff --git a/crypto/smartcont/stdlib.fc b/crypto/smartcont/stdlib.fc index 9041bafe..978b9473 100644 --- a/crypto/smartcont/stdlib.fc +++ b/crypto/smartcont/stdlib.fc @@ -1,100 +1,540 @@ ;; Standard library for funC ;; +{- + This file is part of TON FunC Standard Library. + + FunC Standard Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + FunC Standard Library 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 Lesser General Public License for more details. + +-} + +{- + # Tuple manipulation primitives + The names and the types are mostly self-explaining. + See [polymorhism with forall](https://ton.org/docs/#/func/functions?id=polymorphism-with-forall) + for more info on the polymorphic functions. + + Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) + and vise versa. +-} + +{- + # Lisp-style lists + + Lists can be represented as nested 2-elements tuples. + Empty list is conventionally represented as TVM `null` value (it can be obtained by calling [null()]). + For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types. +-} + +;;; Adds an element to the beginning of lisp-style list. forall X -> tuple cons(X head, tuple tail) asm "CONS"; + +;;; Extracts the head and the tail of lisp-style list. forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; + +;;; Extracts the tail and the head of lisp-style list. forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; + +;;; Returns the head of lisp-style list. forall X -> X car(tuple list) asm "CAR"; + +;;; Returns the tail of lisp-style list. tuple cdr(tuple list) asm "CDR"; -forall X, Y -> tuple pair(X x, Y y) asm "PAIR"; -forall X, Y -> (X, Y) unpair(tuple t) asm "UNPAIR"; -forall X, Y, Z -> tuple triple(X x, Y y, Z z) asm "TRIPLE"; -forall X, Y, Z -> (X, Y, Z) untriple(tuple t) asm "UNTRIPLE"; -forall X, Y, Z, W -> tuple tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; -forall X, Y, Z, W -> (X, Y, Z, W) untuple4(tuple t) asm "4 UNTUPLE"; + +;;; Creates tuple with zero elements. +tuple empty_tuple() asm "NIL"; + +;;; Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` +;;; is of length at most 255. Otherwise throws a type check exception. +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; + +;;; Creates a tuple of length one with given argument as element. +forall X -> [X] single(X x) asm "SINGLE"; + +;;; Unpacks a tuple of length one +forall X -> X unsingle([X] t) asm "UNSINGLE"; + +;;; Creates a tuple of length two with given arguments as elements. +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; + +;;; Unpacks a tuple of length two +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; + +;;; Creates a tuple of length three with given arguments as elements. +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; + +;;; Unpacks a tuple of length three +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; + +;;; Creates a tuple of length four with given arguments as elements. +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; + +;;; Unpacks a tuple of length four +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; + +;;; Returns the first element of a tuple (with unknown element types). forall X -> X first(tuple t) asm "FIRST"; + +;;; Returns the second element of a tuple (with unknown element types). forall X -> X second(tuple t) asm "SECOND"; + +;;; Returns the third element of a tuple (with unknown element types). forall X -> X third(tuple t) asm "THIRD"; + +;;; Returns the fourth element of a tuple (with unknown element types). forall X -> X fourth(tuple t) asm "3 INDEX"; + +;;; Returns the first element of a pair tuple. +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; + +;;; Returns the second element of a pair tuple. +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; + +;;; Returns the first element of a triple tuple. +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; + +;;; Returns the second element of a triple tuple. +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; + +;;; Returns the third element of a triple tuple. +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; + + +;;; Push null element (casted to given type) +;;; By the TVM type `Null` FunC represents absence of a value of some atomic type. +;;; So `null` can actually have any atomic type. forall X -> X null() asm "PUSHNULL"; +;;; Moves a variable [x] to the top of the stack +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + + + +;;; Returns the current Unix time as an Integer int now() asm "NOW"; + +;;; Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. +;;; If necessary, it can be parsed further using primitives such as [parse_std_addr]. slice my_address() asm "MYADDR"; -tuple get_balance() asm "BALANCE"; + +;;; Returns the balance of the smart contract as a tuple consisting of an int +;;; (balance in nanotoncoins) and a `cell` +;;; (a dictionary with 32-bit keys representing the balance of "extra currencies") +;;; at the start of Computation Phase. +;;; Note that RAW primitives such as [send_raw_message] do not update this field. +[int, cell] get_balance() asm "BALANCE"; + +;;; Returns the logical time of the current transaction. int cur_lt() asm "LTIME"; + +;;; Returns the starting logical time of the current block. int block_lt() asm "BLOCKLT"; +;;; Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. +;;; Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. int cell_hash(cell c) asm "HASHCU"; + +;;; Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. +;;; The result is the same as if an ordinary cell containing only data and references from `s` had been created +;;; and its hash computed by [cell_hash]. int slice_hash(slice s) asm "HASHSU"; + +;;; Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, +;;; throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. int string_hash(slice s) asm "SHA256U"; +{- + # Signature checks +-} + +;;; Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) +;;; using [public_key] (also represented by a 256-bit unsigned integer). +;;; The signature must contain at least 512 data bits; only the first 512 bits are used. +;;; The result is `−1` if the signature is valid, `0` otherwise. +;;; Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. +;;; That is, if [hash] is computed as the hash of some data, these data are hashed twice, +;;; the second hashing occurring inside `CHKSIGNS`. int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; + +;;; Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, +;;; similarly to [check_signature]. +;;; If the bit length of [data] is not divisible by eight, throws a cell underflow exception. +;;; The verification of Ed25519 signatures is the standard one, +;;; with sha256 used to reduce [data] to the 256-bit number that is actually signed. int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; +{--- + # Computation of boc size + The primitives below may be useful for computing storage fees of user-provided data. +-} + +;;; Returns `(x, y, z, -1)` or `(null, null, null, 0)`. +;;; Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` +;;; in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account +;;; the identification of equal cells. +;;; The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, +;;; with a hash table of visited cell hashes used to prevent visits of already-visited cells. +;;; The total count of visited cells `x` cannot exceed non-negative [max_cells]; +;;; otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and +;;; a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; + +;;; Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. +;;; The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; +;;; however, the data bits and the cell references of [s] are accounted for in `y` and `z`. +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; + +;;; A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (8) on failure. +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; Throws an exception with exit_code excno if cond is not 0 (commented since implemented in compilator) ;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; +{-- + # Debug primitives + Only works for local TVM execution with debug level verbosity +-} +;;; Dumps the stack (at most the top 255 values) and shows the total stack depth. () dump_stack() impure asm "DUMPSTK"; +{- + # Persistent storage save and load +-} + +;;; Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. cell get_data() asm "c4 PUSH"; + +;;; Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. () set_data(cell c) impure asm "c4 POP"; + +{- + # Continuation primitives +-} +;;; Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. +;;; The primitive returns the current value of `c3`. cont get_c3() impure asm "c3 PUSH"; + +;;; Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. +;;; Note that after execution of this primitive the current code +;;; (and the stack of recursive function calls) won't change, +;;; but any other function call will use a function from the new code. () set_c3(cont c) impure asm "c3 POP"; + +;;; Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. cont bless(slice s) impure asm "BLESS"; +{--- + # Gas related primitives +-} + +;;; Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, +;;; decreasing the value of `gr` by `gc` in the process. +;;; In other words, the current smart contract agrees to buy some gas to finish the current transaction. +;;; This action is required to process external messages, which bring no value (hence no gas) with themselves. +;;; +;;; For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). () accept_message() impure asm "ACCEPT"; + +;;; Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. +;;; If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, +;;; an (unhandled) out of gas exception is thrown before setting new gas limits. +;;; Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. () set_gas_limit(int limit) impure asm "SETGASLIMIT"; + +;;; Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) +;;; so that the current execution is considered “successful” with the saved values even if an exception +;;; in Computation Phase is thrown later. () commit() impure asm "COMMIT"; +;;; Not implemented +;;() buy_gas(int gram) impure asm "BUYGAS"; + +;;; Computes the amount of gas that can be bought for `amount` nanoTONs, +;;; and sets `gl` accordingly in the same way as [set_gas_limit]. +() buy_gas(int amount) impure asm "BUYGAS"; + +;;; Computes the minimum of two integers [x] and [y]. int min(int x, int y) asm "MIN"; + +;;; Computes the maximum of two integers [x] and [y]. int max(int x, int y) asm "MAX"; +;;; Sorts two integers. +(int, int) minmax(int x, int y) asm "MINMAX"; + +;;; Computes the absolute value of an integer [x]. +int abs(int x) asm "ABS"; + +{- + # Slice primitives + + It is said that a primitive _loads_ some data, + if it returns the data and the remainder of the slice + (so it can also be used as [modifying method](https://ton.org/docs/#/func/statements?id=modifying-methods)). + + It is said that a primitive _preloads_ some data, if it returns only the data + (it can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods)). + + Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice. +-} + + +;;; Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, +;;; or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) +;;; which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. slice begin_parse(cell c) asm "CTOS"; + +;;; Checks if [s] is empty. If not, throws an exception. () end_parse(slice s) impure asm "ENDS"; + +;;; Loads the first reference from the slice. (slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; + +;;; Preloads the first reference from the slice. cell preload_ref(slice s) asm "PLDREF"; + + {- Functions below are commented because are implemented on compilator level for optimisation -} + +;;; Loads a signed [len]-bit integer from a slice [s]. ;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; + +;;; Loads an unsigned [len]-bit integer from a slice [s]. ;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; + +;;; Preloads a signed [len]-bit integer from a slice [s]. ;; int preload_int(slice s, int len) asm "PLDIX"; + +;;; Preloads an unsigned [len]-bit integer from a slice [s]. ;; int preload_uint(slice s, int len) asm "PLDUX"; + +;;; Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. ;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; + +;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. ;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; + +;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^120 - 1`). (slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; + +;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; (slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; + +;;; Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. slice first_bits(slice s, int len) asm "SDCUTFIRST"; + +;;; Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; (slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; + +;;; Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice slice_last(slice s, int len) asm "SDCUTLAST"; + +;;; Loads a dictionary `D` (HashMapE) from `slice` [s]. +;;; (returns `null` if `nothing` constructor is used). (slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; + +;;; Preloads a dictionary `D` from `slice` [s]. cell preload_dict(slice s) asm "PLDDICT"; + +;;; Loads a dictionary as [load_dict], but returns only the remainder of the slice. slice skip_dict(slice s) asm "SKIPDICT"; +;;; Loads (Maybe ^Cell) from `slice` [s]. +;;; In other words loads 1 bit and if it is true +;;; loads first ref and return it with slice remainder +;;; otherwise returns `null` and slice remainder +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; + +;;; Preloads (Maybe ^Cell) from `slice` [s]. +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; + + +;;; Returns the depth of `cell` [c]. +;;; If [c] has no references, then return `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. +;;; If [c] is a `null` instead of a cell, returns zero. +int cell_depth(cell c) asm "CDEPTH"; + + +{- + # Slice size primitives +-} + +;;; Returns the number of references in `slice` [s]. int slice_refs(slice s) asm "SREFS"; + +;;; Returns the number of data bits in `slice` [s]. int slice_bits(slice s) asm "SBITS"; + +;;; Returns both the number of data bits and the number of references in `slice` [s]. (int, int) slice_bits_refs(slice s) asm "SBITREFS"; + +;;; Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). int slice_empty?(slice s) asm "SEMPTY"; + +;;; Checks whether `slice` [s] has no bits of data. int slice_data_empty?(slice s) asm "SDEMPTY"; + +;;; Checks whether `slice` [s] has no references. int slice_refs_empty?(slice s) asm "SREMPTY"; +;;; Returns the depth of `slice` [s]. +;;; If [s] has no references, then returns `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. +int slice_depth(slice s) asm "SDEPTH"; + +{- + # Builder size primitives +-} + +;;; Returns the number of cell references already stored in `builder` [b] +int builder_refs(builder b) asm "BREFS"; + +;;; Returns the number of data bits already stored in `builder` [b]. +int builder_bits(builder b) asm "BBITS"; + +;;; Returns the depth of `builder` [b]. +;;; If no cell references are stored in [b], then returns 0; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. +int builder_depth(builder b) asm "BDEPTH"; + +{- + # Builder primitives + It is said that a primitive _stores_ a value `x` into a builder `b` + if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. + It can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods). + + All the primitives below first check whether there is enough space in the `builder`, + and only then check the range of the value being serialized. +-} + +;;; Creates a new empty `builder`. builder begin_cell() asm "NEWC"; + +;;; Converts a `builder` into an ordinary `cell`. cell end_cell(builder b) asm "ENDC"; + +;;; Stores a reference to `cell` [c] into `builder` [b]. builder store_ref(builder b, cell c) asm(c b) "STREF"; + +;;; Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. ;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; + +;;; Stores a signed [len]-bit integer `x` into `b` for` 0 ≤ len ≤ 257`. ;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; + + +;;; Stores `slice` [s] into `builder` [b] builder store_slice(builder b, slice s) asm "STSLICER"; + +;;; Stores (serializes) an integer [x] in the range `0..2^120 − 1` into `builder` [b]. +;;; The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, +;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, +;;; followed by an `8l`-bit unsigned big-endian representation of [x]. +;;; If [x] does not belong to the supported range, a range check exception is thrown. +;;; +;;; Store amounts of TonCoins to the builder as VarUInteger 16 builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_coins(builder b, int x) asm "STGRAMS"; + +;;; Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. +;;; In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. builder store_dict(builder b, cell c) asm(c b) "STDICT"; +;;; Stores (Maybe ^Cell) to builder: +;;; if cell is null store 1 zero bit +;;; otherwise store 1 true bit and ref to cell +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + + +{- + # Address manipulation primitives + The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: + ```TL-B + addr_none$00 = MsgAddressExt; + addr_extern$01 len:(## 8) external_address:(bits len) + = MsgAddressExt; + anycast_info$_ depth:(#<= 30) { depth >= 1 } + rewrite_pfx:(bits depth) = Anycast; + addr_std$10 anycast:(Maybe Anycast) + workchain_id:int8 address:bits256 = MsgAddressInt; + addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) + workchain_id:int32 address:(bits addr_len) = MsgAddressInt; + _ _:MsgAddressInt = MsgAddress; + _ _:MsgAddressExt = MsgAddress; + + int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddress dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ``` + A deserialized `MsgAddress` is represented by a tuple `t` as follows: + + - `addr_none` is represented by `t = (0)`, + i.e., a tuple containing exactly one integer equal to zero. + - `addr_extern` is represented by `t = (1, s)`, + where slice `s` contains the field `external_address`. In other words, ` + t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`. + - `addr_std` is represented by `t = (2, u, x, s)`, + where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present). + Next, integer `x` is the `workchain_id`, and slice `s` contains the address. + - `addr_var` is represented by `t = (3, u, x, s)`, + where `u`, `x`, and `s` have the same meaning as for `addr_std`. +-} + +;;; Loads from slice [s] the only prefix that is a valid `MsgAddress`, +;;; and returns both this prefix `s'` and the remainder `s''` of [s] as slices. (slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; + +;;; Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. +;;; If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. tuple parse_addr(slice s) asm "PARSEMSGADDR"; + +;;; Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), +;;; applies rewriting from the anycast (if present) to the same-length prefix of the address, +;;; and returns both the workchain and the 256-bit address as integers. +;;; If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, +;;; throws a cell deserialization exception. (int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; + +;;; A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s], +;;; even if it is not exactly 256 bit long (represented by a `msg_addr_var`). (int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; +{- + # Dictionary primitives +-} + + +;;; Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; (cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; + +;;; Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; (cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; + cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; -cell udict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; (cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; (cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; (cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; @@ -125,40 +565,75 @@ cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(va (cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; (cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; (cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; -(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; -(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; + +;;; Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL cell new_dict() asm "NEWDICT"; +;;; Checks whether a dictionary is empty. Equivalent to cell_null?. int dict_empty?(cell c) asm "DICTEMPTY"; + +{- Prefix dictionary primitives -} +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +;;; Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. cell config_param(int x) asm "CONFIGOPTPARAM"; +;;; Checks whether c is a null. Note, that FunC also has polymorphic null? built-in. int cell_null?(cell c) asm "ISNULL"; +;;; Creates an output action which would reserve exactly amount nanotoncoins (if mode = 0), at most amount nanotoncoins (if mode = 2), or all but amount nanotoncoins (if mode = 1 or mode = 3), from the remaining balance of the account. It is roughly equivalent to creating an outbound message carrying amount nanotoncoins (or b − amount nanotoncoins, where b is the remaining balance) to oneself, so that the subsequent output actions would not be able to spend more money than the remainder. Bit +2 in mode means that the external action does not fail if the specified amount cannot be reserved; instead, all remaining balance is reserved. Bit +8 in mode means `amount <- -amount` before performing any further actions. Bit +4 in mode means that amount is increased by the original balance of the current account (before the compute phase), including all extra currencies, before performing any other checks and actions. Currently, amount must be a non-negative integer, and mode must be in the range 0..15. () raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; -() raw_reserve_extra(slice currencies, int mode) impure asm "RAWRESERVEX"; +;;; Similar to raw_reserve, but also accepts a dictionary extra_amount (represented by a cell or null) with extra currencies. In this way currencies other than TonCoin can be reserved. +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +;;; Sends a raw message contained in msg, which should contain a correctly serialized object Message X, with the only exception that the source address is allowed to have dummy value addr_none (to be automatically replaced with the current smart contract address), and ihr_fee, fwd_fee, created_lt and created_at fields can have arbitrary values (to be rewritten with correct values during the action phase of the current transaction). Integer parameter mode contains the flags. Currently mode = 0 is used for ordinary messages; mode = 128 is used for messages that are to carry all the remaining balance of the current smart contract (instead of the value originally indicated in the message); mode = 64 is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message (if bit 0 is not set, the gas fees are deducted from this amount); mode' = mode + 1 means that the sender wants to pay transfer fees separately; mode' = mode + 2 means that any errors arising while processing this message during the action phase should be ignored. Finally, mode' = mode + 32 means that the current account must be destroyed if its resulting balance is zero. This flag is usually employed together with +128. () send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +;;; Creates an output action that would change this smart contract code to that given by cell new_code. Notice that this change will take effect only after the successful termination of the current run of the smart contract () set_code(cell new_code) impure asm "SETCODE"; +;;; Generates a new pseudo-random unsigned 256-bit integer x. The algorithm is as follows: if r is the old value of the random seed, considered as a 32-byte array (by constructing the big-endian representation of an unsigned 256-bit integer), then its sha512(r) is computed; the first 32 bytes of this hash are stored as the new value r' of the random seed, and the remaining 32 bytes are returned as the next random value x. int random() impure asm "RANDU256"; +;;; Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. int rand(int range) impure asm "RAND"; +;;; Returns the current random seed as an unsigned 256-bit Integer. int get_seed() impure asm "RANDSEED"; -int set_seed() impure asm "SETRAND"; +;;; Sets the random seed to unsigned 256-bit seed. +() set_seed(int) impure asm "SETRAND"; +;;; Mixes unsigned 256-bit integer x into the random seed r by setting the random seed to sha256 of the concatenation of two 32-byte strings: the first with the big-endian representation of the old seed r, and the second with the big-endian representation of x. () randomize(int x) impure asm "ADDRAND"; +;;; Equivalent to randomize(cur_lt());. () randomize_lt() impure asm "LTIME" "ADDRAND"; + +;;; Checks whether the data parts of two slices coinside +int equal_slice_bits (slice a, slice b) asm "SDEQ"; + +;;; Concatenates two builders +builder store_builder(builder to, builder from) asm "STBR"; diff --git a/crypto/smartcont/update-config.fif b/crypto/smartcont/update-config.fif index fcb8cf16..ed13271f 100755 --- a/crypto/smartcont/update-config.fif +++ b/crypto/smartcont/update-config.fif @@ -30,8 +30,6 @@ file-base +".pk" load-keypair nip constant config_pk boc-filename file>B B>boc dup B B+ max_factor 32 u>B B+ src_addr 256 u>B B+ adnl_addr 256 u>B B+ dup Bx. cr diff --git a/crypto/smartcont/validator-elect-signed.fif b/crypto/smartcont/validator-elect-signed.fif index 55e3d9dd..4e681f07 100755 --- a/crypto/smartcont/validator-elect-signed.fif +++ b/crypto/smartcont/validator-elect-signed.fif @@ -27,7 +27,7 @@ def? $7 { @' $7 } { "validator-query.boc" } cond constant output_fname ."Creating a request to participate in validator elections at time " elect_time . ."from smart contract " -1 src_addr 2dup 1 .Addr ." = " .addr ." with maximal stake factor with respect to the minimal stake " max_factor ._ -."/65536 and validator ADNL address " adnl_addr x. cr +."/65536 and validator ADNL address " adnl_addr 64x. cr B{654c5074} elect_time 32 u>B B+ max_factor 32 u>B B+ src_addr 256 u>B B+ adnl_addr 256 u>B B+ ."String to sign is: " dup Bx. cr constant to_sign diff --git a/crypto/smartcont/wallet-code.fc b/crypto/smartcont/wallet-code.fc index 3ce2fac1..9d4cddb7 100644 --- a/crypto/smartcont/wallet-code.fc +++ b/crypto/smartcont/wallet-code.fc @@ -29,3 +29,9 @@ int seqno() method_id { return get_data().begin_parse().preload_uint(32); } + +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(32); + return cs.preload_uint(256); +} diff --git a/crypto/smartcont/wallet-v2.fif b/crypto/smartcont/wallet-v2.fif index 6bf12103..fd5f6ae7 100755 --- a/crypto/smartcont/wallet-v2.fif +++ b/crypto/smartcont/wallet-v2.fif @@ -9,9 +9,11 @@ true =: allow-bounce false =: force-bounce 3 =: send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors 60 =: timeout // external message expires in 60 seconds +variable extra-currencies +{ extra-currencies @ cc+ extra-currencies ! } : extra-cc+! begin-options - " [-n|-b] [-t] [-B ] [-C ] []" +cr +tab + " [-x *] [-n|-b] [-t] [-B ] [-C ] []" +cr +tab +"Creates a request to advanced wallet created by new-wallet-v2.fif, with private key loaded from file .pk " +"and address from .addr, and saves it into .boc ('wallet-query.boc' by default)" disable-digit-options generic-help-setopt @@ -19,6 +21,8 @@ begin-options "Clears bounce flag" option-help "b" "--force-bounce" { true =: force-bounce } short-long-option "Forces bounce flag" option-help + "x" "--extra" { $>xcc extra-cc+! } short-long-option-arg + "Indicates the amount of extra currencies to be transfered" option-help "t" "--timeout" { parse-int =: timeout } short-long-option-arg "Sets expiration timeout in seconds (" timeout (.) $+ +" by default)" option-help "B" "--body" { =: body-boc-file } short-long-option-arg @@ -38,7 +42,7 @@ true constant bounce $1 =: file-base $2 bounce parse-load-address force-bounce or allow-bounce and =: bounce 2=: dest_addr $3 parse-int =: seqno -$4 $>GR =: amount +$4 $>cc extra-cc+! extra-currencies @ 2=: amount $5 "wallet-query" replace-if-null =: savefile file-base +".addr" load-address @@ -49,14 +53,14 @@ file-base +".pk" load-keypair nip constant wallet_pk def? body-boc-file { @' body-boc-file file>B B>boc } { comment simple-transfer-body } cond constant body-cell -."Transferring " amount .GR ."to account " +."Transferring " amount .GR+cc ."to account " dest_addr 2dup bounce 7 + .Addr ." = " .addr ."seqno=0x" seqno x. ."bounce=" bounce . cr ."Body of transfer message is " body-cell dup ."signing message: " + INC 32 THROWIF // fail unless recv_external + 9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU 32 LDU // signature in_msg subwallet_id valid_until msg_seqno cs + NOW s1 s3 XCHG LEQ 35 THROWIF // signature in_msg subwallet_id cs msg_seqno + c4 PUSH CTOS 32 LDU 32 LDU 256 LDU ENDS // signature in_msg subwallet_id cs msg_seqno stored_seqno stored_subwallet public_key + s3 s2 XCPU EQUAL 33 THROWIFNOT // signature in_msg subwallet_id cs public_key stored_seqno stored_subwallet + s4 s4 XCPU EQUAL 34 THROWIFNOT // signature in_msg stored_subwallet cs public_key stored_seqno + s0 s4 XCHG HASHSU // signature stored_seqno stored_subwallet cs public_key msg_hash + s0 s5 s5 XC2PU // public_key stored_seqno stored_subwallet cs msg_hash signature public_key + CHKSIGNU 35 THROWIFNOT // public_key stored_seqno stored_subwallet cs + ACCEPT + WHILE:<{ + DUP SREFS // public_key stored_seqno stored_subwallet cs _51 + }>DO<{ // public_key stored_seqno stored_subwallet cs + 8 LDU LDREF s0 s2 XCHG // public_key stored_seqno stored_subwallet cs _56 mode + SENDRAWMSG + }> // public_key stored_seqno stored_subwallet cs + ENDS SWAP INC // public_key stored_subwallet seqno' + NEWC 32 STU 32 STU 256 STU ENDC c4 POP +}>c diff --git a/crypto/smartcont/wallet-v3.fif b/crypto/smartcont/wallet-v3.fif index 8804acbb..c090dc09 100644 --- a/crypto/smartcont/wallet-v3.fif +++ b/crypto/smartcont/wallet-v3.fif @@ -9,9 +9,11 @@ true =: allow-bounce false =: force-bounce 3 =: send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors 60 =: timeout // external message expires in 60 seconds +variable extra-currencies +{ extra-currencies @ cc+ extra-currencies ! } : extra-cc+! begin-options - " [-n|-b] [-t] [-B ] [-C ] []" +cr +tab + " [-x *] [-n|-b] [-t] [-B ] [-C ] []" +cr +tab +"Creates a request to advanced wallet created by new-wallet-v3.fif, with private key loaded from file .pk " +"and address from .addr, and saves it into .boc ('wallet-query.boc' by default)" disable-digit-options generic-help-setopt @@ -19,6 +21,8 @@ begin-options "Clears bounce flag" option-help "b" "--force-bounce" { true =: force-bounce } short-long-option "Forces bounce flag" option-help + "x" "--extra" { $>xcc extra-cc+! } short-long-option-arg + "Indicates the amount of extra currencies to be transfered" option-help "t" "--timeout" { parse-int =: timeout } short-long-option-arg "Sets expiration timeout in seconds (" timeout (.) $+ +" by default)" option-help "B" "--body" { =: body-boc-file } short-long-option-arg @@ -40,10 +44,12 @@ $1 =: file-base $2 bounce parse-load-address force-bounce or allow-bounce and =: bounce 2=: dest_addr $3 parse-int =: subwallet_id $4 parse-int =: seqno -$5 $>GR =: amount +$5 $>cc extra-cc+! extra-currencies @ 2=: amount $6 "wallet-query" replace-if-null =: savefile +subwallet_id (.) 1 ' $+ does : +subwallet -file-base +".addr" load-address +file-base +subwallet +".addr" dup file-exists? { drop file-base +".addr" } ifnot +load-address 2dup 2constant wallet_addr ."Source wallet address = " 2dup .addr cr 6 .Addr cr file-base +".pk" load-keypair nip constant wallet_pk @@ -51,15 +57,15 @@ file-base +".pk" load-keypair nip constant wallet_pk def? body-boc-file { @' body-boc-file file>B B>boc } { comment simple-transfer-body } cond constant body-cell -."Transferring " amount .GR ."to account " +."Transferring " amount .GR+cc ."to account " dest_addr 2dup bounce 7 + .Addr ." = " .addr ."subwallet_id=0x" subwallet_id x. ."seqno=0x" seqno x. ."bounce=" bounce . cr ."Body of transfer message is " body-cell dup ."signing message: " [-n|-b] [-B ] [-C ] []" +cr +tab + " [-x *] [-n|-b] [-B ] [-C ] []" +cr +tab +"Creates a request to simple wallet created by new-wallet.fif, with private key loaded from file .pk " +"and address from .addr, and saves it into .boc ('wallet-query.boc' by default)" disable-digit-options generic-help-setopt @@ -18,10 +20,14 @@ begin-options "Clears bounce flag" option-help "b" "--force-bounce" { true =: force-bounce } short-long-option "Forces bounce flag" option-help + "x" "--extra" { $>xcc extra-cc+! } short-long-option-arg + "Indicates the amount of extra currencies to be transfered" option-help "B" "--body" { =: body-boc-file } short-long-option-arg "Sets the payload of the transfer message" option-help "C" "--comment" { =: comment } short-long-option-arg "Sets the comment to be sent in the transfer message" option-help + "I" "--with-init" { =: init-file } short-long-option-arg + "Indicates filename with BoC containing StateInit for internal message" option-help "m" "--mode" { parse-int =: send-mode } short-long-option-arg "Sets transfer mode (0..255) for SENDRAWMSG (" send-mode (.) $+ +" by default)" option-help @@ -35,7 +41,7 @@ true =: bounce $1 =: file-base $2 bounce parse-load-address allow-bounce and force-bounce or =: bounce 2=: dest_addr $3 parse-int =: seqno -$4 $>GR =: amount +$4 $>cc extra-cc+! extra-currencies @ 2=: amount $5 "wallet-query" replace-if-null =: savefile allow-bounce not force-bounce and abort"cannot have bounce flag both set and cleared" // "" 1 { 69091 * 1+ 65535 and tuck 2521 / 65 + hold swap } 1000 times drop =: comment @@ -48,14 +54,18 @@ file-base +".pk" load-keypair nip constant wallet_pk def? body-boc-file { @' body-boc-file file>B B>boc } { comment simple-transfer-body } cond constant body-cell -."Transferring " amount .GR ."to account " +def? init-file { @' init-file file>B B>boc dup ."signing message: " . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "GenericAccount.h" #include "block/block-auto.h" #include "block/block-parse.h" namespace ton { -td::Ref GenericAccount::get_init_state(td::Ref code, td::Ref data) noexcept { + +namespace smc { +td::Ref pack_grams(td::uint64 amount) { + vm::CellBuilder cb; + block::tlb::t_Grams.store_integer_value(cb, td::BigInt256(amount)); + return vm::load_cell_slice_ref(cb.finalize()); +} + +bool unpack_grams(td::Ref cs, td::uint64& amount) { + td::RefInt256 got; + if (!block::tlb::t_Grams.as_integer_to(cs, got)) { + return false; + } + if (!got->unsigned_fits_bits(63)) { + return false; + } + auto x = got->to_long(); + if (x < 0) { + return false; + } + amount = x; + return true; +} +} // namespace smc + +td::Ref GenericAccount::get_init_state(const td::Ref& code, + const td::Ref& data) noexcept { return vm::CellBuilder() .store_zeroes(2) .store_ones(2) @@ -47,7 +73,7 @@ void GenericAccount::store_int_message(vm::CellBuilder& cb, const block::StdAddr .store_long(dest_address.workchain, 8) .store_int256(dest_addr, 256); block::tlb::t_Grams.store_integer_value(cb, td::BigInt256(gramms)); - cb.store_zeroes(9 + 64 + 32 + 1 + 1); + cb.store_zeroes(9 + 64 + 32); } td::Ref GenericAccount::create_ext_message(const block::StdAddress& address, td::Ref new_state, @@ -92,8 +118,46 @@ td::Ref GenericAccount::create_ext_message(const block::StdAddress& ad td::Ref res; tlb::type_pack_cell(res, block::gen::t_Message_Any, message); + if (res.is_null()) { + /* body */ { message.body = vm::CellBuilder().store_ones(1).store_ref(std::move(body)).as_cellslice_ref(); } + tlb::type_pack_cell(res, block::gen::t_Message_Any, message); + } CHECK(res.not_null()); return res; } +td::Result GenericAccount::get_public_key(const SmartContract& sc) { + auto answer = sc.run_get_method("get_public_key"); + if (!answer.success) { + return td::Status::Error("get_public_key failed"); + } + auto do_get_public_key = [&]() -> td::Result { + auto key = answer.stack.write().pop_int_finite(); + td::SecureString bytes(32); + if (!key->export_bytes(bytes.as_mutable_slice().ubegin(), bytes.size(), false)) { + return td::Status::Error("get_public_key failed"); + } + return td::Ed25519::PublicKey(std::move(bytes)); + }; + return TRY_VM(do_get_public_key()); +} + +td::Result GenericAccount::get_seqno(const SmartContract& sc) { + return TRY_VM([&]() -> td::Result { + auto answer = sc.run_get_method("seqno"); + if (!answer.success) { + return td::Status::Error("seqno get method failed"); + } + return static_cast(answer.stack.write().pop_long_range(std::numeric_limits::max())); + }()); +} +td::Result GenericAccount::get_wallet_id(const SmartContract& sc) { + return TRY_VM([&]() -> td::Result { + auto answer = sc.run_get_method("wallet_id"); + if (!answer.success) { + return td::Status::Error("wallet_id get method failed"); + } + return static_cast(answer.stack.write().pop_long_range(std::numeric_limits::max())); + }()); +} } // namespace ton diff --git a/crypto/smc-envelope/GenericAccount.h b/crypto/smc-envelope/GenericAccount.h index 003b8e1b..285553c7 100644 --- a/crypto/smc-envelope/GenericAccount.h +++ b/crypto/smc-envelope/GenericAccount.h @@ -14,18 +14,32 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells.h" #include "block/block.h" +#include "Ed25519.h" +#include "SmartContract.h" + namespace ton { +namespace smc { +td::Ref pack_grams(td::uint64 amount); +bool unpack_grams(td::Ref cs, td::uint64& amount); +} // namespace smc class GenericAccount { public: - static td::Ref get_init_state(td::Ref code, td::Ref data) noexcept; + static td::Ref get_init_state(const td::Ref& code, const td::Ref& data) noexcept; + static td::Ref get_init_state(const SmartContract::State& state) noexcept { + return get_init_state(state.code, state.data); + } static block::StdAddress get_address(ton::WorkchainId workchain_id, const td::Ref& init_state) noexcept; static td::Ref create_ext_message(const block::StdAddress& address, td::Ref new_state, td::Ref body) noexcept; static void store_int_message(vm::CellBuilder& cb, const block::StdAddress& dest_address, td::int64 gramms); + + static td::Result get_public_key(const SmartContract& sc); + static td::Result get_seqno(const SmartContract& sc); + static td::Result get_wallet_id(const SmartContract& sc); }; } // namespace ton diff --git a/crypto/smc-envelope/HighloadWallet.cpp b/crypto/smc-envelope/HighloadWallet.cpp index 5f540780..69843fcf 100644 --- a/crypto/smc-envelope/HighloadWallet.cpp +++ b/crypto/smc-envelope/HighloadWallet.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "HighloadWallet.h" #include "GenericAccount.h" @@ -27,46 +27,21 @@ #include namespace ton { -td::Ref HighloadWallet::get_init_state(const td::Ed25519::PublicKey& public_key, - td::uint32 wallet_id) noexcept { - auto code = get_init_code(); - auto data = get_init_data(public_key, wallet_id); - return GenericAccount::get_init_state(std::move(code), std::move(data)); -} -td::Ref HighloadWallet::get_init_message(const td::Ed25519::PrivateKey& private_key, - td::uint32 wallet_id) noexcept { - td::uint32 seqno = 0; - td::uint32 valid_until = std::numeric_limits::max(); - auto append_message = [&](auto&& cb) -> vm::CellBuilder& { - cb.store_long(wallet_id, 32).store_long(valid_until, 32).store_long(seqno, 32); - CHECK(cb.store_maybe_ref({})); - return cb; - }; - auto signature = private_key.sign(append_message(vm::CellBuilder()).finalize()->get_hash().as_slice()).move_as_ok(); - - return append_message(vm::CellBuilder().store_bytes(signature)).finalize(); -} - -td::Ref HighloadWallet::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, - td::uint32 seqno, td::uint32 valid_until, - td::Span gifts) noexcept { - CHECK(gifts.size() <= 254); +td::Result> HighloadWallet::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, + td::uint32 valid_until, td::Span gifts) const { + TRY_RESULT(wallet_id, get_wallet_id()); + TRY_RESULT(seqno, get_seqno()); + CHECK(gifts.size() <= get_max_gifts_size()); vm::Dictionary messages(16); for (size_t i = 0; i < gifts.size(); i++) { auto& gift = gifts[i]; td::int32 send_mode = 3; - auto gramms = gift.gramms; - if (gramms == -1) { - gramms = 0; + if (gift.gramms == -1) { send_mode += 128; } + auto message_inner = create_int_message(gift); vm::CellBuilder cb; - GenericAccount::store_int_message(cb, gift.destination, gramms); - cb.store_bytes("\0\0\0\0", 4); - //vm::CellString::store(cb, gift.message, 35 * 8).ensure(); - auto message_inner = cb.finalize(); - cb = {}; cb.store_long(send_mode, 8).store_ref(message_inner); auto key = messages.integer_key(td::make_refint(i), 16, false); messages.set_builder(key.bits(), 16, cb); @@ -80,47 +55,36 @@ td::Ref HighloadWallet::make_a_gift_message(const td::Ed25519::Private return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize(); } -td::Ref HighloadWallet::get_init_code() noexcept { - return SmartContractCode::highload_wallet(); -} - -vm::CellHash HighloadWallet::get_init_code_hash() noexcept { - return get_init_code()->get_hash(); -} - -td::Ref HighloadWallet::get_init_data(const td::Ed25519::PublicKey& public_key, - td::uint32 wallet_id) noexcept { +td::Ref HighloadWallet::get_init_data(const InitData& init_data) noexcept { return vm::CellBuilder() - .store_long(0, 32) - .store_long(wallet_id, 32) - .store_bytes(public_key.as_octet_string()) + .store_long(init_data.seqno, 32) + .store_long(init_data.wallet_id, 32) + .store_bytes(init_data.public_key) .finalize(); } -td::Result HighloadWallet::get_seqno() const { - return TRY_VM(get_seqno_or_throw()); -} - -td::Result HighloadWallet::get_seqno_or_throw() const { - if (state_.data.is_null()) { - return 0; - } - //FIXME use get method - return static_cast(vm::load_cell_slice(state_.data).fetch_ulong(32)); -} - td::Result HighloadWallet::get_wallet_id() const { - return TRY_VM(get_wallet_id_or_throw()); + return TRY_VM([&]() -> td::Result { + if (state_.data.is_null()) { + return 0; + } + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(32); + return static_cast(cs.fetch_ulong(32)); + }()); } -td::Result HighloadWallet::get_wallet_id_or_throw() const { - if (state_.data.is_null()) { - return 0; - } - //FIXME use get method - auto cs = vm::load_cell_slice(state_.data); - cs.skip_first(32); - return static_cast(cs.fetch_ulong(32)); +td::Result HighloadWallet::get_public_key() const { + return TRY_VM([&]() -> td::Result { + if (state_.data.is_null()) { + return td::Status::Error("data is null"); + } + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(64); + td::SecureString res(td::Ed25519::PublicKey::LENGTH); + cs.fetch_bytes(res.as_mutable_slice().ubegin(), td::narrow_cast(res.size())); + return td::Ed25519::PublicKey(std::move(res)); + }()); } } // namespace ton diff --git a/crypto/smc-envelope/HighloadWallet.h b/crypto/smc-envelope/HighloadWallet.h index aa1e3a4b..db36caae 100644 --- a/crypto/smc-envelope/HighloadWallet.h +++ b/crypto/smc-envelope/HighloadWallet.h @@ -14,41 +14,36 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "vm/cells.h" #include "Ed25519.h" #include "block/block.h" #include "vm/cells/CellString.h" namespace ton { -class HighloadWallet : ton::SmartContract { - public: - explicit HighloadWallet(State state) : ton::SmartContract(std::move(state)) { - } +struct HighloadWalletTraits { + using InitData = WalletInterface::DefaultInitData; + static constexpr unsigned max_message_size = vm::CellString::max_bytes; - static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept; - static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id) noexcept; - struct Gift { - block::StdAddress destination; - td::int64 gramms; - std::string message; - }; - static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, - td::uint32 seqno, td::uint32 valid_until, td::Span gifts) noexcept; + static constexpr unsigned max_gifts_size = 254; + static constexpr auto code_type = SmartContractCode::HighloadWalletV1; +}; - static td::Ref get_init_code() noexcept; - static vm::CellHash get_init_code_hash() noexcept; - static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept; +class HighloadWallet : public WalletBase { + public: + explicit HighloadWallet(State state) : WalletBase(std::move(state)) { + } + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override; + static td::Ref get_init_data(const InitData& init_data) noexcept; - td::Result get_seqno() const; - td::Result get_wallet_id() const; - - private: - td::Result get_seqno_or_throw() const; - td::Result get_wallet_id_or_throw() const; + // can't use get methods for compatibility with old revisions + td::Result get_wallet_id() const override; + td::Result get_public_key() const override; }; } // namespace ton diff --git a/crypto/smc-envelope/HighloadWalletV2.cpp b/crypto/smc-envelope/HighloadWalletV2.cpp new file mode 100644 index 00000000..e0454233 --- /dev/null +++ b/crypto/smc-envelope/HighloadWalletV2.cpp @@ -0,0 +1,107 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ +#include "HighloadWalletV2.h" +#include "GenericAccount.h" +#include "SmartContractCode.h" + +#include "vm/boc.h" +#include "vm/cells/CellString.h" +#include "td/utils/base64.h" + +#include + +namespace ton { + +td::Result> HighloadWalletV2::get_init_message(const td::Ed25519::PrivateKey& private_key, + td::uint32 valid_until) const noexcept { + TRY_RESULT(wallet_id, get_wallet_id()); + td::uint32 id = -1; + auto append_message = [&](auto&& cb) -> vm::CellBuilder& { + cb.store_long(wallet_id, 32).store_long(valid_until, 32).store_long(id, 32); + CHECK(cb.store_maybe_ref({})); + return cb; + }; + auto signature = private_key.sign(append_message(vm::CellBuilder()).finalize()->get_hash().as_slice()).move_as_ok(); + + return append_message(vm::CellBuilder().store_bytes(signature)).finalize(); +} + +td::Result> HighloadWalletV2::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, + td::uint32 valid_until, + td::Span gifts) const { + TRY_RESULT(wallet_id, get_wallet_id()); + CHECK(gifts.size() <= get_max_gifts_size()); + vm::Dictionary messages(16); + for (size_t i = 0; i < gifts.size(); i++) { + auto& gift = gifts[i]; + td::int32 send_mode = 3; + if (gift.gramms == -1) { + send_mode += 128; + } + vm::CellBuilder cb; + cb.store_long(send_mode, 8).store_ref(create_int_message(gift)); + auto key = messages.integer_key(td::make_refint(i), 16, false); + messages.set_builder(key.bits(), 16, cb); + } + std::string hash; + { + vm::CellBuilder cb; + CHECK(cb.store_maybe_ref(messages.get_root_cell())); + hash = cb.finalize()->get_hash().as_slice().substr(28, 4).str(); + } + + vm::CellBuilder cb; + cb.store_long(wallet_id, 32).store_long(valid_until, 32).store_bytes(hash); + CHECK(cb.store_maybe_ref(messages.get_root_cell())); + auto message_outer = cb.finalize(); + auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok(); + return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize(); +} + +td::Ref HighloadWalletV2::get_init_data(const InitData& init_data) noexcept { + vm::CellBuilder cb; + cb.store_long(init_data.wallet_id, 32).store_long(init_data.seqno, 64).store_bytes(init_data.public_key); + CHECK(cb.store_maybe_ref({})); + return cb.finalize(); +} + +td::Result HighloadWalletV2::get_wallet_id() const { + return TRY_VM([&]() -> td::Result { + if (state_.data.is_null()) { + return 0; + } + auto cs = vm::load_cell_slice(state_.data); + return static_cast(cs.fetch_ulong(32)); + }()); +} + +td::Result HighloadWalletV2::get_public_key() const { + return TRY_VM([&]() -> td::Result { + if (state_.data.is_null()) { + return td::Status::Error("data is null"); + } + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(96); + td::SecureString res(td::Ed25519::PublicKey::LENGTH); + cs.fetch_bytes(res.as_mutable_slice().ubegin(), td::narrow_cast(res.size())); + return td::Ed25519::PublicKey(std::move(res)); + }()); +} + +} // namespace ton diff --git a/crypto/smc-envelope/HighloadWalletV2.h b/crypto/smc-envelope/HighloadWalletV2.h new file mode 100644 index 00000000..6b5e6dba --- /dev/null +++ b/crypto/smc-envelope/HighloadWalletV2.h @@ -0,0 +1,51 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ +#pragma once + +#include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" +#include "vm/cells.h" +#include "Ed25519.h" +#include "block/block.h" +#include "vm/cells/CellString.h" + +namespace ton { +struct HighloadWalletV2Traits { + using InitData = WalletInterface::DefaultInitData; + + static constexpr unsigned max_message_size = vm::CellString::max_bytes; + static constexpr unsigned max_gifts_size = 254; + static constexpr auto code_type = SmartContractCode::HighloadWalletV2; +}; +class HighloadWalletV2 : public WalletBase { + public: + explicit HighloadWalletV2(State state) : WalletBase(std::move(state)) { + } + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override; + static td::Ref get_init_data(const InitData& init_data) noexcept; + td::Result> get_init_message(const td::Ed25519::PrivateKey& private_key, + td::uint32 valid_until = std::numeric_limits::max()) const + noexcept; + + // can't use get methods for compatibility with old revisions + td::Result get_wallet_id() const override; + td::Result get_public_key() const override; +}; +} // namespace ton diff --git a/crypto/smc-envelope/ManualDns.cpp b/crypto/smc-envelope/ManualDns.cpp new file mode 100644 index 00000000..40ce3bac --- /dev/null +++ b/crypto/smc-envelope/ManualDns.cpp @@ -0,0 +1,634 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2019-2020 Telegram Systems LLP +*/ +#include "ManualDns.h" + +#include "smc-envelope/SmartContractCode.h" + +#include "vm/dict.h" + +#include "td/utils/format.h" +#include "td/utils/overloaded.h" +#include "td/utils/Parser.h" +#include "td/utils/Random.h" + +#include "block/block-auto.h" +#include "block/block-parse.h" + +#include "common/util.h" + +namespace ton { +td::StringBuilder& operator<<(td::StringBuilder& sb, const ManualDns::EntryData& data) { + switch (data.type) { + case ManualDns::EntryData::Type::Empty: + return sb << "DELETED"; + case ManualDns::EntryData::Type::Text: + return sb << "TEXT:" << data.data.get().text; + case ManualDns::EntryData::Type::NextResolver: + return sb << "NEXT:" << data.data.get().resolver.rserialize(); + case ManualDns::EntryData::Type::AdnlAddress: + return sb << "ADNL:" + << td::adnl_id_encode(data.data.get().adnl_address.as_slice()) + .move_as_ok(); + case ManualDns::EntryData::Type::SmcAddress: + return sb << "SMC:" << data.data.get().smc_address.rserialize(); + case ManualDns::EntryData::Type::StorageAddress: + return sb << "STORAGE:" << data.data.get().bag_id.to_hex(); + } + return sb << ""; +} + +//proto_list_nil$0 = ProtoList; +//proto_list_next$1 head:Protocol tail:ProtoList = ProtoList; +//proto_http#4854 = Protocol; + +//cap_list_nil$0 = SmcCapList; +//cap_list_next$1 head:SmcCapability tail:SmcCapList = SmcCapList; +//cap_method_seqno#5371 = SmcCapability; +//cap_method_pubkey#71f4 = SmcCapability; +//cap_is_wallet#2177 = SmcCapability; +//cap_name#ff name:Text = SmcCapability; +// +td::Result> DnsInterface::EntryData::as_cell() const { + td::Ref res; + td::Status error; + data.visit(td::overloaded( + [&](const EntryDataText& text) { + block::gen::DNSRecord::Record_dns_text dns; + vm::CellBuilder cb; + vm::CellText::store(cb, text.text); + dns.x = vm::load_cell_slice_ref(cb.finalize()); + tlb::pack_cell(res, dns); + }, + [&](const EntryDataNextResolver& resolver) { + block::gen::DNSRecord::Record_dns_next_resolver dns; + vm::CellBuilder cb; + block::tlb::t_MsgAddressInt.store_std_address(cb, resolver.resolver.workchain, resolver.resolver.addr); + dns.resolver = vm::load_cell_slice_ref(cb.finalize()); + tlb::pack_cell(res, dns); + }, + [&](const EntryDataAdnlAddress& adnl_address) { + block::gen::DNSRecord::Record_dns_adnl_address dns; + dns.adnl_addr = adnl_address.adnl_address; + dns.flags = 0; + tlb::pack_cell(res, dns); + }, + [&](const EntryDataSmcAddress& smc_address) { + block::gen::DNSRecord::Record_dns_smc_address dns; + vm::CellBuilder cb; + block::tlb::t_MsgAddressInt.store_std_address(cb, smc_address.smc_address.workchain, + smc_address.smc_address.addr); + dns.smc_addr = vm::load_cell_slice_ref(cb.finalize()); + tlb::pack_cell(res, dns); + }, + [&](const EntryDataStorageAddress& storage_address) { + block::gen::DNSRecord::Record_dns_storage_address dns; + dns.bag_id = storage_address.bag_id; + tlb::pack_cell(res, dns); + })); + if (error.is_error()) { + return error; + } + if (res.is_null()) { + return td::Status::Error("Entry data is emtpy"); + } + return res; + //dns_text#1eda _:Text = DNSRecord; + + //dns_next_resolver#ba93 resolver:MsgAddressInt = DNSRecord; // usually in record #-1 + //dns_adnl_address#ad01 adnl_addr:bits256 flags:(## 8) { flags <= 1 } proto_list:flags . 0?ProtoList = DNSRecord; // often in record #2 + + //dns_smc_address#9fd3 smc_addr:MsgAddressInt flags:(## 8) { flags <= 1 } cap_list:flags . 0?SmcCapList = DNSRecord; // often in record #1 +} + +td::Result DnsInterface::EntryData::from_cellslice(vm::CellSlice& cs) { + switch (block::gen::t_DNSRecord.get_tag(cs)) { + case block::gen::DNSRecord::dns_text: { + block::gen::DNSRecord::Record_dns_text dns; + tlb::unpack(cs, dns); + TRY_RESULT(text, vm::CellText::load(dns.x.write())); + return EntryData::text(std::move(text)); + } + case block::gen::DNSRecord::dns_next_resolver: { + block::gen::DNSRecord::Record_dns_next_resolver dns; + tlb::unpack(cs, dns); + ton::WorkchainId wc; + ton::StdSmcAddress addr; + if (!block::tlb::t_MsgAddressInt.extract_std_address(dns.resolver, wc, addr)) { + return td::Status::Error("Invalid address"); + } + return EntryData::next_resolver(block::StdAddress(wc, addr)); + } + case block::gen::DNSRecord::dns_adnl_address: { + block::gen::DNSRecord::Record_dns_adnl_address dns; + tlb::unpack(cs, dns); + return EntryData::adnl_address(dns.adnl_addr); + } + case block::gen::DNSRecord::dns_smc_address: { + block::gen::DNSRecord::Record_dns_smc_address dns; + tlb::unpack(cs, dns); + ton::WorkchainId wc; + ton::StdSmcAddress addr; + if (!block::tlb::t_MsgAddressInt.extract_std_address(dns.smc_addr, wc, addr)) { + return td::Status::Error("Invalid address"); + } + return EntryData::smc_address(block::StdAddress(wc, addr)); + } + case block::gen::DNSRecord::dns_storage_address: { + block::gen::DNSRecord::Record_dns_storage_address dns; + tlb::unpack(cs, dns); + return EntryData::storage_address(dns.bag_id); + } + } + return td::Status::Error("Unknown entry data"); +} + +SmartContract::Args DnsInterface::resolve_args_raw(td::Slice encoded_name, td::Bits256 category, + block::StdAddress address) { + SmartContract::Args res; + res.set_method_id("dnsresolve"); + res.set_stack( + {vm::load_cell_slice_ref(vm::CellBuilder().store_bytes(encoded_name).finalize()), + td::bits_to_refint(category.cbits(), 256, false)}); + res.set_address(std::move(address)); + return res; +} + +td::Result DnsInterface::resolve_args(td::Slice name, td::Bits256 category, + block::StdAddress address) { + if (name.size() > get_default_max_name_size()) { + return td::Status::Error("Name is too long"); + } + auto encoded_name = encode_name(name); + return resolve_args_raw(encoded_name, category, std::move(address)); +} + +td::Result> DnsInterface::resolve(td::Slice name, td::Bits256 category) const { + TRY_RESULT(raw_entries, resolve_raw(name, category)); + std::vector entries; + entries.reserve(raw_entries.size()); + for (auto& raw_entry : raw_entries) { + Entry entry; + entry.name = std::move(raw_entry.name); + entry.category = raw_entry.category; + entry.partially_resolved = raw_entry.partially_resolved; + auto cs = *raw_entry.data; + auto data = EntryData::from_cellslice(cs); + if (data.is_error()) { + LOG(INFO) << "Failed to parse DNS entry: " << data.move_as_error(); + } else { + entry.data = data.move_as_ok(); + entries.push_back(std::move(entry)); + } + } + return entries; +} + +/* + External message structure: + [Bytes<512b>:signature] [UInt<32b>:seqno] [UInt<6b>:operation] + [Either b0: inline name (<= 58-x Bytes) or b1: reference-stored name) + x depends on operation + Use of 6-bit op instead of 32-bit allows to save 4 bytes for inline name + Inline [Name] structure: [UInt<6b>:length] [Bytes:data] + Operations (continuation of message): + 00 Contract initialization message (only if seqno = 0) (x=-) + 31 TSet: replace ENTIRE DOMAIN TABLE with the provided tree root cell (x=-) + [Cell<1r>:new_domains_table] + 51 OSet: replace owner public key with a new one (x=-) + [UInt<256b>:new_public_key] +*/ +// creation +td::Ref ManualDns::create(td::Ref data, int revision) { + return td::Ref( + true, State{ton::SmartContractCode::get_code(ton::SmartContractCode::ManualDns, revision), std::move(data)}); +} + +td::Ref ManualDns::create(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, int revision) { + return create(create_init_data_fast(public_key, wallet_id), revision); +} + +td::optional ManualDns::guess_revision(const vm::Cell::Hash& code_hash) { + for (auto i : ton::SmartContractCode::get_revisions(ton::SmartContractCode::ManualDns)) { + if (ton::SmartContractCode::get_code(ton::SmartContractCode::ManualDns, i)->get_hash() == code_hash) { + return i; + } + } + return {}; +} +td::optional ManualDns::guess_revision(const block::StdAddress& address, + const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) { + for (auto i : {-1, 1}) { + auto dns = ton::ManualDns::create(public_key, wallet_id, i); + if (dns->get_address() == address) { + return i; + } + } + return {}; +} + +td::Result ManualDns::get_wallet_id() const { + return TRY_VM(get_wallet_id_or_throw()); +} +td::Result ManualDns::get_wallet_id_or_throw() const { + if (state_.data.is_null()) { + return 0; + } + //FIXME use get method + return static_cast(vm::load_cell_slice(state_.data).fetch_ulong(32)); +} + +td::Result> ManualDns::create_set_value_unsigned(td::Bits256 category, td::Slice name, + td::Ref data) const { + //11 VSet: set specified value to specified subdomain->category (x=2) + //[Int<256b>:category] [Name:subdomain] [Cell<1r>:value] + vm::CellBuilder cb; + cb.store_long(11, 6); + if (name.size() <= 58 - 32) { + cb.store_bytes(category.as_slice()); + cb.store_long(0, 1); + cb.store_long(name.size(), 6); + cb.store_bytes(name); + } else { + cb.store_bytes(category.as_slice()); + cb.store_long(1, 1); + cb.store_ref(vm::CellBuilder().store_bytes(name).finalize()); + } + cb.store_maybe_ref(std::move(data)); + return cb.finalize(); +} +td::Result> ManualDns::create_delete_value_unsigned(td::Bits256 category, td::Slice name) const { + //12 VDel: delete specified subdomain->category (x=2) + //[Int<256b>:category] [Name:subdomain] + vm::CellBuilder cb; + cb.store_long(12, 6); + if (name.size() <= 58 - 32) { + cb.store_bytes(category.as_slice()); + cb.store_long(0, 1); + cb.store_long(name.size(), 6); + cb.store_bytes(name); + } else { + cb.store_bytes(category.as_slice()); + cb.store_long(1, 1); + cb.store_ref(vm::CellBuilder().store_bytes(name).finalize()); + } + return cb.finalize(); +} + +td::Result> ManualDns::create_delete_all_unsigned() const { + // 32 TDel: nullify ENTIRE DOMAIN TABLE (x=-) + vm::CellBuilder cb; + cb.store_long(32, 6); + return cb.finalize(); +} + +td::Result> ManualDns::create_set_all_unsigned(td::Span entries) const { + vm::PrefixDictionary pdict(1023); + for (auto& action : entries) { + auto name_key = encode_name(action.name); + int zero_cnt = 0; + for (auto c : name_key) { + if (c == 0) { + zero_cnt++; + } + } + auto new_name_key = vm::load_cell_slice(vm::CellBuilder().store_long(zero_cnt, 7).store_bytes(name_key).finalize()); + auto ptr = new_name_key.data_bits(); + auto ptr_size = new_name_key.size(); + auto o_dict = pdict.lookup(ptr, ptr_size); + td::Ref dict_root; + if (o_dict.not_null()) { + o_dict->prefetch_maybe_ref(dict_root); + } + vm::Dictionary dict(dict_root, 256); + if (!action.data.value().is_null()) { + dict.set_ref(action.category.bits(), 256, action.data.value()); + } + pdict.set(ptr, ptr_size, dict.get_root()); + } + + vm::CellBuilder cb; + cb.store_long(31, 6); + + cb.store_maybe_ref(pdict.get_root_cell()); + + return cb.finalize(); +} + +//21 DSet: replace entire category dictionary of domain with provided (x=0) +//[Name:subdomain] [Cell<1r>:new_cat_table] +//22 DDel: delete entire category dictionary of specified domain (x=0) +//[Name:subdomain] +td::Result> ManualDns::create_delete_name_unsigned(td::Slice name) const { + vm::CellBuilder cb; + cb.store_long(22, 6); + if (name.size() <= 58) { + cb.store_long(0, 1); + cb.store_long(name.size(), 6); + cb.store_bytes(name); + } else { + cb.store_long(1, 1); + cb.store_ref(vm::CellBuilder().store_bytes(name).finalize()); + } + return cb.finalize(); +} +td::Result> ManualDns::create_set_name_unsigned(td::Slice name, td::Span entries) const { + vm::CellBuilder cb; + cb.store_long(21, 6); + if (name.size() <= 58) { + cb.store_long(0, 1); + cb.store_long(name.size(), 6); + cb.store_bytes(name); + } else { + cb.store_long(1, 1); + cb.store_ref(vm::CellBuilder().store_bytes(name).finalize()); + } + + vm::Dictionary dict(256); + + for (auto& action : entries) { + if (action.data.value().is_null()) { + continue; + } + dict.set_ref(action.category.cbits(), 256, action.data.value()); + } + cb.store_maybe_ref(dict.get_root_cell()); + + return cb.finalize(); +} + +td::Result> ManualDns::prepare(td::Ref data, td::uint32 valid_until) const { + TRY_RESULT(wallet_id, get_wallet_id()); + auto hash = data->get_hash().as_slice().substr(28, 4).str(); + + vm::CellBuilder cb; + cb.store_long(wallet_id, 32).store_long(valid_until, 32); + //cb.store_bytes(hash); + cb.store_long(td::Random::secure_uint32(), 32); + cb.append_cellslice(vm::load_cell_slice(data)); + return cb.finalize(); +} + +td::Result> ManualDns::sign(const td::Ed25519::PrivateKey& private_key, td::Ref data) { + auto signature = private_key.sign(data->get_hash().as_slice()).move_as_ok(); + vm::CellBuilder cb; + cb.store_bytes(signature.as_slice()); + cb.append_cellslice(vm::load_cell_slice(data)); + return cb.finalize(); +} + +td::Result> ManualDns::create_init_query(const td::Ed25519::PrivateKey& private_key, + td::uint32 valid_until) const { + vm::CellBuilder cb; + cb.store_long(0, 6); + + TRY_RESULT(prepared, prepare(cb.finalize(), valid_until)); + return sign(private_key, std::move(prepared)); +} + +td::Ref ManualDns::create_init_data_fast(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) { + vm::CellBuilder cb; + cb.store_long(wallet_id, 32).store_long(0, 64).store_bytes(public_key.as_octet_string()); + CHECK(cb.store_maybe_ref({})); + CHECK(cb.store_maybe_ref({})); + return cb.finalize(); +} + +size_t ManualDns::get_max_name_size() const { + return get_default_max_name_size(); +} + +td::Result> ManualDns::resolve_raw(td::Slice name, td::Bits256 category) const { + return TRY_VM(resolve_raw_or_throw(name, category)); +} +td::Result> ManualDns::resolve_raw_or_throw(td::Slice name, + td::Bits256 category) const { + if (name.size() > get_max_name_size()) { + return td::Status::Error("Name is too long"); + } + auto encoded_name = encode_name(name); + auto res = run_get_method(resolve_args_raw(encoded_name, category, address_)); + if (!res.success) { + return td::Status::Error("get method failed"); + } + std::vector vec; + auto data = res.stack.write().pop_maybe_cell(); + if (data.is_null()) { + return vec; + } + size_t prefix_size = res.stack.write().pop_smallint_range((int)encoded_name.size() * 8); + if (prefix_size % 8 != 0) { + return td::Status::Error("Prefix size is not divisible by 8"); + } + prefix_size /= 8; + if (prefix_size == 0) { + return vec; + } + if (prefix_size < encoded_name.size()) { + vec.push_back({decode_name(td::Slice(encoded_name).substr(0, prefix_size)), DNS_NEXT_RESOLVER_CATEGORY, + vm::load_cell_slice_ref(data), true}); + } else { + if (category.is_zero()) { + vm::Dictionary dict(std::move(data), 256); + dict.check_for_each([&](td::Ref cs, td::ConstBitPtr key, int n) { + CHECK(n == 256); + if (cs.is_null() || cs->size_ext() != 0x10000) { + return true; + } + cs = vm::load_cell_slice_ref(cs->prefetch_ref()); + vec.push_back({name.str(), td::Bits256(key), cs}); + return true; + }); + } else { + vec.push_back({name.str(), category, vm::load_cell_slice_ref(data)}); + } + } + + return vec; +} + +td::Result> ManualDns::create_update_query(CombinedActions& combined) const { + if (combined.name.empty()) { + if (combined.actions.value().empty()) { + return create_delete_all_unsigned(); + } + return create_set_all_unsigned(combined.actions.value()); + } + if (combined.category.is_zero()) { + if (!combined.actions) { + return create_delete_name_unsigned(encode_name(combined.name)); + } + return create_set_name_unsigned(encode_name(combined.name), combined.actions.value()); + } + CHECK(combined.actions.value().size() == 1); + auto& action = combined.actions.value()[0]; + if (action.data) { + return create_set_value_unsigned(action.category, encode_name(action.name), action.data.value()); + } else { + return create_delete_value_unsigned(action.category, encode_name(action.name)); + } +} + +td::Result> ManualDns::create_update_query(td::Ed25519::PrivateKey& pk, td::Span actions, + td::uint32 valid_until) const { + auto combined = combine_actions(actions); + std::vector> queries; + for (auto& c : combined) { + TRY_RESULT(q, create_update_query(c)); + queries.push_back(std::move(q)); + } + + td::Ref combined_query; + for (auto& query : td::reversed(queries)) { + if (combined_query.is_null()) { + combined_query = std::move(query); + } else { + auto next = vm::load_cell_slice(combined_query); + combined_query = vm::CellBuilder() + .append_cellslice(vm::load_cell_slice(query)) + .store_ref(vm::CellBuilder().append_cellslice(next).finalize()) + .finalize(); + } + } + + TRY_RESULT(prepared, prepare(std::move(combined_query), valid_until)); + return sign(pk, std::move(prepared)); +} + +std::string DnsInterface::encode_name(td::Slice name) { + std::string res; + if (name.empty() || name == ".") { + res += '\0'; + return res; + } + while (!name.empty()) { + auto pos = name.rfind('.'); + if (pos == td::Slice::npos) { + res += name.str(); + name = td::Slice(); + } else { + res += name.substr(pos + 1).str(); + name.truncate(pos); + } + res += '\0'; + } + return res; +} + +std::string DnsInterface::decode_name(td::Slice name) { + std::string res; + while (!name.empty()) { + auto pos = name.rfind('\0'); + if (pos == td::Slice::npos) { + res += name.str(); + name = td::Slice(); + } else { + res += name.substr(pos + 1).str(); + name.truncate(pos); + res += '.'; + } + } + return res; +} + +std::string ManualDns::serialize_data(const EntryData& data) { + std::string res; + data.data.visit( + td::overloaded([&](const ton::ManualDns::EntryDataText& text) { res = "UNSUPPORTED"; }, + [&](const ton::ManualDns::EntryDataNextResolver& resolver) { res = "UNSUPPORTED"; }, + [&](const ton::ManualDns::EntryDataAdnlAddress& adnl_address) { res = "UNSUPPORTED"; }, + [&](const ton::ManualDns::EntryDataSmcAddress& text) { res = "UNSUPPORTED"; }, + [&](const ton::ManualDns::EntryDataStorageAddress& storage_address) { res = "UNSUPPORTED"; })); + return res; +} + +td::Result> ManualDns::parse_data(td::Slice cmd) { + td::ConstParser parser(cmd); + parser.skip_whitespaces(); + auto type = parser.read_till(':'); + parser.skip(':'); + if (type == "TEXT") { + return ManualDns::EntryData::text(parser.read_all().str()); + } else if (type == "ADNL") { + TRY_RESULT(address, td::adnl_id_decode(parser.read_all())); + return ManualDns::EntryData::adnl_address(address); + } else if (type == "SMC") { + TRY_RESULT(address, block::StdAddress::parse(parser.read_all())); + return ManualDns::EntryData::smc_address(address); + } else if (type == "NEXT") { + TRY_RESULT(address, block::StdAddress::parse(parser.read_all())); + return ManualDns::EntryData::next_resolver(address); + } else if (type == "STORAGE") { + td::Bits256 bag_id; + if (bag_id.from_hex(parser.read_all(), false) != 256) { + return td::Status::Error("failed to parse bag id"); + } + return ManualDns::EntryData::storage_address(bag_id); + } else if (parser.data() == "DELETED") { + return {}; + } + return td::Status::Error(PSLICE() << "Unknown entry type: " << type); +} + +td::Result ManualDns::parse_line(td::Slice cmd) { + // Cmd = + // set name category data | + // delete.name name | + // delete.all + // data = + // TEXT: | + // SMC: | + // NEXT: | + // ADNL: + // DELETED + td::ConstParser parser(cmd); + auto type = parser.read_word(); + if (type == "set") { + auto name = parser.read_word(); + auto category_str = parser.read_word(); + TRY_RESULT(data, parse_data(parser.read_all())); + return ManualDns::ActionExt{name.str(), td::sha256_bits256(td::as_slice(category_str)), std::move(data)}; + } else if (type == "delete.name") { + auto name = parser.read_word(); + if (name.empty()) { + return td::Status::Error("name is empty"); + } + return ManualDns::ActionExt{name.str(), td::Bits256::zero(), {}}; + } else if (type == "delete.all") { + return ManualDns::ActionExt{"", td::Bits256::zero(), {}}; + } + return td::Status::Error(PSLICE() << "Unknown command: " << type); +} + +td::Result> ManualDns::parse(td::Slice cmd) { + auto lines = td::full_split(cmd, '\n'); + std::vector res; + res.reserve(lines.size()); + for (auto& line : lines) { + td::ConstParser parser(line); + parser.skip_whitespaces(); + if (parser.empty()) { + continue; + } + TRY_RESULT(action, parse_line(parser.read_all())); + res.push_back(std::move(action)); + } + return res; +} + +} // namespace ton diff --git a/crypto/smc-envelope/ManualDns.h b/crypto/smc-envelope/ManualDns.h new file mode 100644 index 00000000..d24cd023 --- /dev/null +++ b/crypto/smc-envelope/ManualDns.h @@ -0,0 +1,358 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2019-2020 Telegram Systems LLP +*/ +#pragma once +#include "td/utils/Variant.h" +#include "td/utils/Status.h" +#include "vm/cells/Cell.h" +#include "vm/cells/CellSlice.h" +#include "vm/cells/CellString.h" + +#include "smc-envelope/SmartContract.h" + +#include "Ed25519.h" +#include "common/checksum.h" + +#include + +namespace ton { +const td::Bits256 DNS_NEXT_RESOLVER_CATEGORY = + td::sha256_bits256(td::Slice("dns_next_resolver", strlen("dns_next_resolver"))); + +class DnsInterface { + public: + struct EntryDataText { + std::string text; + bool operator==(const EntryDataText& other) const { + return text == other.text; + } + }; + + struct EntryDataNextResolver { + block::StdAddress resolver; + bool operator==(const EntryDataNextResolver& other) const { + return resolver == other.resolver; + } + }; + + struct EntryDataAdnlAddress { + ton::Bits256 adnl_address; + // TODO: proto + bool operator==(const EntryDataAdnlAddress& other) const { + return adnl_address == other.adnl_address; + } + }; + + struct EntryDataSmcAddress { + block::StdAddress smc_address; + bool operator==(const EntryDataSmcAddress& other) const { + return smc_address == other.smc_address; + } + // TODO: capability + }; + + struct EntryDataStorageAddress { + ton::Bits256 bag_id; + // TODO: proto + bool operator==(const EntryDataStorageAddress& other) const { + return bag_id == other.bag_id; + } + }; + + struct EntryData { + enum Type { Empty, Text, NextResolver, AdnlAddress, SmcAddress, StorageAddress } type{Empty}; + td::Variant + data; + + static EntryData text(std::string text) { + return {Text, EntryDataText{text}}; + } + static EntryData next_resolver(block::StdAddress resolver) { + return {NextResolver, EntryDataNextResolver{resolver}}; + } + static EntryData adnl_address(ton::Bits256 adnl_address) { + return {AdnlAddress, EntryDataAdnlAddress{adnl_address}}; + } + static EntryData smc_address(block::StdAddress smc_address) { + return {SmcAddress, EntryDataSmcAddress{smc_address}}; + } + static EntryData storage_address(ton::Bits256 bag_id) { + return {StorageAddress, EntryDataStorageAddress{bag_id}}; + } + + bool operator==(const EntryData& other) const { + return data == other.data; + } + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const EntryData& data); + + td::Result> as_cell() const; + static td::Result from_cellslice(vm::CellSlice& cs); + }; + + struct Entry { + std::string name; + td::Bits256 category; + EntryData data; + bool partially_resolved = false; + auto key() const { + return std::tie(name, category); + } + bool operator<(const Entry& other) const { + return key() < other.key(); + } + bool operator==(const Entry& other) const { + return key() == other.key() && data == other.data; + } + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const Entry& entry) { + sb << entry.name << ":" << entry.category.to_hex() << ":" << entry.data; + return sb; + } + }; + struct RawEntry { + std::string name; + td::Bits256 category; + td::Ref data; + bool partially_resolved = false; + }; + + struct ActionExt { + std::string name; + td::Bits256 category; + td::optional data; + static td::Result parse(td::Slice); + }; + + struct Action { + std::string name; + td::Bits256 category; + td::optional> data; + + bool does_create_category() const { + CHECK(!name.empty()); + CHECK(!category.is_zero()); + return static_cast(data); + } + bool does_change_empty() const { + CHECK(!name.empty()); + CHECK(!category.is_zero()); + return static_cast(data) && data.value().not_null(); + } + void make_non_empty() { + CHECK(!name.empty()); + CHECK(!category.is_zero()); + if (!data) { + data = td::Ref(); + } + } + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const Action& action) { + sb << action.name << ":" << action.category.to_hex() << ":"; + if (action.data) { + if (action.data.value().is_null()) { + sb << ""; + } else { + sb << ""; + } + } else { + sb << ""; + } + return sb; + } + }; + + virtual ~DnsInterface() = default; + virtual size_t get_max_name_size() const = 0; + virtual td::Result> resolve_raw(td::Slice name, td::Bits256 category) const = 0; + virtual td::Result> create_update_query( + td::Ed25519::PrivateKey& pk, td::Span actions, + td::uint32 valid_until = std::numeric_limits::max()) const = 0; + + td::Result> resolve(td::Slice name, td::Bits256 category) const; + + static std::string encode_name(td::Slice name); + static std::string decode_name(td::Slice name); + + static size_t get_default_max_name_size() { + return 128; + } + static SmartContract::Args resolve_args_raw(td::Slice encoded_name, td::Bits256 category, + block::StdAddress address = {}); + static td::Result resolve_args(td::Slice name, td::Bits256 category, + block::StdAddress address = {}); +}; + +class ManualDns : public ton::SmartContract, public DnsInterface { + public: + ManualDns(State state, block::StdAddress address = {}) + : SmartContract(std::move(state)), address_(std::move(address)) { + } + + ManualDns* make_copy() const override { + return new ManualDns{state_}; + } + + // creation + static td::Ref create(State state, block::StdAddress address = {}) { + return td::Ref(true, std::move(state), std::move(address)); + } + static td::Ref create(td::Ref data = {}, int revision = 0); + static td::Ref create(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, int revision = 0); + + static std::string serialize_data(const EntryData& data); + static td::Result> parse_data(td::Slice cmd); + static td::Result parse_line(td::Slice cmd); + static td::Result> parse(td::Slice cmd); + + static td::optional guess_revision(const vm::Cell::Hash& code_hash); + static td::optional guess_revision(const block::StdAddress& address, + const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id); + + td::Ref create_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 valid_until) const { + return create_init_data_fast(public_key, valid_until); + } + + td::Result get_wallet_id() const; + td::Result get_wallet_id_or_throw() const; + + td::Result> create_set_value_unsigned(td::Bits256 category, td::Slice name, + td::Ref data) const; + td::Result> create_delete_value_unsigned(td::Bits256 category, td::Slice name) const; + td::Result> create_delete_all_unsigned() const; + td::Result> create_set_all_unsigned(td::Span entries) const; + td::Result> create_delete_name_unsigned(td::Slice name) const; + td::Result> create_set_name_unsigned(td::Slice name, td::Span entries) const; + + td::Result> prepare(td::Ref data, td::uint32 valid_until) const; + + static td::Result> sign(const td::Ed25519::PrivateKey& private_key, td::Ref data); + static td::Ref create_init_data_fast(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id); + + size_t get_max_name_size() const override; + td::Result> resolve_raw(td::Slice name, td::Bits256 category) const override; + td::Result> resolve_raw_or_throw(td::Slice name, td::Bits256 category) const; + + td::Result> create_init_query( + const td::Ed25519::PrivateKey& private_key, + td::uint32 valid_until = std::numeric_limits::max()) const; + td::Result> create_update_query( + td::Ed25519::PrivateKey& pk, td::Span actions, + td::uint32 valid_until = std::numeric_limits::max()) const override; + + template + struct CombinedActions { + std::string name; + td::Bits256 category = td::Bits256::zero(); + td::optional> actions; + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const CombinedActions& action) { + sb << action.name << ":" << action.category.to_hex() << ":"; + if (action.actions) { + sb << "" << action.actions.value().size(); + } else { + sb << ""; + } + return sb; + } + }; + + template + static std::vector> combine_actions(td::Span actions) { + struct Info { + std::set known_category; + std::vector actions; + bool closed{false}; + bool non_empty{false}; + }; + + std::map mp; + std::vector> res; + for (auto& action : td::reversed(actions)) { + if (action.name.empty()) { + CombinedActions set_all; + set_all.actions = std::vector(); + for (auto& it : mp) { + for (auto& e : it.second.actions) { + if (e.does_create_category()) { + set_all.actions.value().push_back(std::move(e)); + } + } + } + res.push_back(std::move(set_all)); + return res; + } + + Info& info = mp[action.name]; + if (info.closed) { + continue; + } + if (!action.category.is_zero() && action.does_create_category()) { + info.non_empty = true; + } + if (!info.known_category.insert(action.category).second) { + continue; + } + if (action.category.is_zero()) { + info.closed = true; + auto old_actions = std::move(info.actions); + bool is_empty = true; + for (auto& action : old_actions) { + if (is_empty && action.does_create_category()) { + info.actions.push_back(std::move(action)); + is_empty = false; + } else if (!is_empty && action.does_change_empty()) { + info.actions.push_back(std::move(action)); + } + } + } else { + info.actions.push_back(std::move(action)); + } + } + + for (auto& it : mp) { + auto& info = it.second; + if (info.closed) { + CombinedActions ca; + ca.name = it.first; + ca.category = td::Bits256::zero(); + if (!info.actions.empty() || info.non_empty) { + ca.actions = std::move(info.actions); + } + res.push_back(std::move(ca)); + } else { + bool need_non_empty = info.non_empty; + for (auto& a : info.actions) { + if (need_non_empty) { + a.make_non_empty(); + need_non_empty = false; + } + CombinedActions ca; + ca.name = a.name; + ca.category = a.category; + ca.actions = std::vector(); + ca.actions.value().push_back(std::move(a)); + res.push_back(ca); + } + } + } + return res; + } + td::Result> create_update_query(CombinedActions& combined) const; + private: + block::StdAddress address_; +}; + +} // namespace ton diff --git a/crypto/smc-envelope/MultisigWallet.cpp b/crypto/smc-envelope/MultisigWallet.cpp index 0c8b56d7..5a9fab6a 100644 --- a/crypto/smc-envelope/MultisigWallet.cpp +++ b/crypto/smc-envelope/MultisigWallet.cpp @@ -1,3 +1,21 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2019-2020 Telegram Systems LLP +*/ #include "MultisigWallet.h" #include "SmartContractCode.h" @@ -48,7 +66,8 @@ td::Ref MultisigWallet::QueryBuilder::create(td::int32 id, td::Ed25519 } td::Ref MultisigWallet::create(td::Ref data) { - return td::Ref(true, State{ton::SmartContractCode::multisig(), std::move(data)}); + return td::Ref( + true, State{ton::SmartContractCode::get_code(ton::SmartContractCode::Multisig), std::move(data)}); } int MultisigWallet::processed(td::uint64 query_id) const { diff --git a/crypto/smc-envelope/MultisigWallet.h b/crypto/smc-envelope/MultisigWallet.h index 17395e47..96da297a 100644 --- a/crypto/smc-envelope/MultisigWallet.h +++ b/crypto/smc-envelope/MultisigWallet.h @@ -1,3 +1,21 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2019-2020 Telegram Systems LLP +*/ #pragma once #include "vm/cells.h" diff --git a/crypto/smc-envelope/PaymentChannel.cpp b/crypto/smc-envelope/PaymentChannel.cpp new file mode 100644 index 00000000..ff54996c --- /dev/null +++ b/crypto/smc-envelope/PaymentChannel.cpp @@ -0,0 +1,291 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ + +#include "PaymentChannel.h" +#include "GenericAccount.h" +#include "vm/cells.h" +#include "vm/cellslice.h" +#include "Ed25519.h" +#include "block/block-auto.h" +#include "block/block-parse.h" + +#include "SmartContract.h" +#include "SmartContractCode.h" + +namespace ton { +using smc::pack_grams; +using smc::unpack_grams; +namespace pchan { + +td::Ref Config::serialize() const { + block::gen::ChanConfig::Record rec; + + vm::CellBuilder a_addr_cb; + block::tlb::t_MsgAddressInt.store_std_address(a_addr_cb, a_addr); + rec.a_addr = a_addr_cb.finalize_novm(); + + vm::CellBuilder b_addr_cb; + block::tlb::t_MsgAddressInt.store_std_address(b_addr_cb, b_addr); + rec.b_addr = b_addr_cb.finalize_novm(); + + rec.a_key.as_slice().copy_from(a_key); + rec.b_key.as_slice().copy_from(b_key); + rec.init_timeout = init_timeout; + rec.close_timeout = close_timeout; + rec.channel_id = channel_id; + rec.min_A_extra = pack_grams(min_A_extra); + + td::Ref res; + CHECK(tlb::pack_cell(res, rec)); + return res; +} + +td::Ref MsgInit::serialize() const { + block::gen::ChanMsg::Record_chan_msg_init rec; + rec.min_A = pack_grams(min_A); + rec.min_B = pack_grams(min_B); + rec.inc_A = pack_grams(inc_A); + rec.inc_B = pack_grams(inc_B); + rec.channel_id = channel_id; + + td::Ref res; + CHECK(tlb::pack_cell(res, rec)); + return res; +} + +td::Ref Promise::serialize() const { + block::gen::ChanPromise::Record rec; + rec.channel_id = channel_id; + rec.promise_A = pack_grams(promise_A); + rec.promise_B = pack_grams(promise_B); + td::Ref res; + CHECK(tlb::pack_cell(res, rec)); + return res; +} + +td::SecureString sign(const td::Ref& msg, const td::Ed25519::PrivateKey* key) { + return key->sign(msg->get_hash().as_slice()).move_as_ok(); +} + +td::Ref maybe_sign(const td::Ref& msg, const td::Ed25519::PrivateKey* key) { + if (!key) { + return {}; + } + return vm::CellBuilder().store_bytes(sign(msg, key).as_slice()).finalize(); +} + +td::Ref maybe_ref(td::Ref msg) { + vm::CellBuilder cb; + CHECK(cb.store_maybe_ref(msg)); + return vm::load_cell_slice_ref(cb.finalize()); +} + +td::Ref MsgClose::serialize() const { + block::gen::ChanMsg::Record_chan_msg_close rec; + rec.extra_A = pack_grams(extra_A); + rec.extra_B = pack_grams(extra_B); + rec.promise = signed_promise; + + td::Ref res; + CHECK(tlb::pack_cell(res, rec)); + return res; +} + +td::Ref MsgTimeout::serialize() const { + block::gen::ChanMsg::Record_chan_msg_timeout rec; + td::Ref res; + CHECK(tlb::pack_cell(res, rec)); + return res; +} + +td::Ref MsgPayout::serialize() const { + block::gen::ChanMsg::Record_chan_msg_payout rec; + td::Ref res; + CHECK(tlb::pack_cell(res, rec)); + return res; +} + +td::SecureString SignedPromise::signature(const td::Ed25519::PrivateKey* key, const td::Ref& promise) { + return sign(promise, key); +} +td::Ref SignedPromise::create_and_serialize(td::Slice signature, const td::Ref& promise) { + block::gen::ChanSignedPromise::Record rec; + rec.promise = vm::load_cell_slice_ref(promise); + LOG(ERROR) << "signature.size() = " << signature.size(); + rec.sig = maybe_ref(vm::CellBuilder().store_bytes(signature).finalize()); + td::Ref res; + CHECK(tlb::pack_cell(res, rec)); + return res; +} +td::Ref SignedPromise::create_and_serialize(const td::Ed25519::PrivateKey* key, + const td::Ref& promise) { + block::gen::ChanSignedPromise::Record rec; + rec.promise = vm::load_cell_slice_ref(promise); + rec.sig = maybe_ref(maybe_sign(promise, key)); + td::Ref res; + CHECK(tlb::pack_cell(res, rec)); + return res; +} + +bool SignedPromise::unpack(td::Ref cell) { + block::gen::ChanSignedPromise::Record rec; + if (!tlb::unpack_cell(cell, rec)) { + return false; + } + block::gen::ChanPromise::Record rec_promise; + if (!tlb::csr_unpack(rec.promise, rec_promise)) { + return false; + } + promise.channel_id = rec_promise.channel_id; + if (!unpack_grams(rec_promise.promise_A, promise.promise_A)) { + return false; + } + if (!unpack_grams(rec_promise.promise_B, promise.promise_B)) { + return false; + } + td::Ref sig_cell; + if (!rec.sig->prefetch_maybe_ref(sig_cell)) { + return false; + } + td::SecureString signature(64); + vm::CellSlice cs = vm::load_cell_slice(sig_cell); + if (!cs.prefetch_bytes(signature.as_mutable_slice())) { + return false; + } + o_signature = std::move(signature); + return true; +} + +td::Ref StateInit::serialize() const { + block::gen::ChanState::Record_chan_state_init rec; + rec.expire_at = expire_at; + rec.min_A = pack_grams(min_A); + rec.min_B = pack_grams(min_B); + rec.A = pack_grams(A); + rec.B = pack_grams(B); + rec.signed_A = signed_A; + rec.signed_B = signed_B; + td::Ref res; + CHECK(tlb::pack_cell(res, rec)); + return res; +} + +td::Ref Data::serialize() const { + block::gen::ChanData::Record rec; + rec.config = config; + rec.state = state; + td::Ref res; + CHECK(block::gen::t_ChanData.cell_pack(res, rec)); + return res; +} + +td::Ref Data::init_state() { + return StateInit().serialize(); +} +} // namespace pchan + +td::Result PaymentChannel::get_info() const { + block::gen::ChanData::Record data_rec; + if (!tlb::unpack_cell(get_state().data, data_rec)) { + return td::Status::Error("Can't unpack data"); + } + block::gen::ChanConfig::Record config_rec; + if (!tlb::unpack_cell(data_rec.config, config_rec)) { + return td::Status::Error("Can't unpack config"); + } + pchan::Config config; + config.a_key = td::SecureString(config_rec.a_key.as_slice()); + config.b_key = td::SecureString(config_rec.b_key.as_slice()); + block::tlb::t_MsgAddressInt.extract_std_address(vm::load_cell_slice_ref(config_rec.a_addr), config.a_addr); + block::tlb::t_MsgAddressInt.extract_std_address(vm::load_cell_slice_ref(config_rec.b_addr), config.b_addr); + config.init_timeout = static_cast(config_rec.init_timeout); + config.close_timeout = static_cast(config_rec.close_timeout); + config.channel_id = static_cast(config_rec.channel_id); + + auto state_cs = vm::load_cell_slice(data_rec.state); + Info res; + switch (block::gen::t_ChanState.check_tag(state_cs)) { + case block::gen::ChanState::chan_state_init: { + pchan::StateInit state; + block::gen::ChanState::Record_chan_state_init state_rec; + if (!tlb::unpack_cell(data_rec.state, state_rec)) { + return td::Status::Error("Can't unpack state"); + } + bool ok = unpack_grams(state_rec.A, state.A) && unpack_grams(state_rec.B, state.B) && + unpack_grams(state_rec.min_A, state.min_A) && unpack_grams(state_rec.min_B, state.min_B); + state.expire_at = state_rec.expire_at; + state.signed_A = state_rec.signed_A; + state.signed_B = state_rec.signed_B; + if (!ok) { + return td::Status::Error("Can't unpack state"); + } + res.state = std::move(state); + break; + } + case block::gen::ChanState::chan_state_close: { + pchan::StateClose state; + block::gen::ChanState::Record_chan_state_close state_rec; + if (!tlb::unpack_cell(data_rec.state, state_rec)) { + return td::Status::Error("Can't unpack state"); + } + bool ok = unpack_grams(state_rec.A, state.A) && unpack_grams(state_rec.B, state.B) && + unpack_grams(state_rec.promise_A, state.promise_A) && + unpack_grams(state_rec.promise_B, state.promise_B); + state.expire_at = state_rec.expire_at; + state.signed_A = state_rec.signed_A; + state.signed_B = state_rec.signed_B; + if (!ok) { + return td::Status::Error("Can't unpack state"); + } + res.state = std::move(state); + break; + } + case block::gen::ChanState::chan_state_payout: { + pchan::StatePayout state; + block::gen::ChanState::Record_chan_state_payout state_rec; + if (!tlb::unpack_cell(data_rec.state, state_rec)) { + return td::Status::Error("Can't unpack state"); + } + bool ok = unpack_grams(state_rec.A, state.A) && unpack_grams(state_rec.B, state.B); + if (!ok) { + return td::Status::Error("Can't unpack state"); + } + res.state = std::move(state); + break; + } + default: + return td::Status::Error("Can't unpack state"); + } + + res.config = std::move(config); + res.description = block::gen::t_ChanState.as_string_ref(data_rec.state); + + return std::move(res); +} // namespace ton + +td::optional PaymentChannel::guess_revision(const vm::Cell::Hash& code_hash) { + for (auto i : ton::SmartContractCode::get_revisions(ton::SmartContractCode::PaymentChannel)) { + auto code = SmartContractCode::get_code(SmartContractCode::PaymentChannel, i); + if (code->get_hash() == code_hash) { + return i; + } + } + return {}; +} +} // namespace ton diff --git a/crypto/smc-envelope/PaymentChannel.h b/crypto/smc-envelope/PaymentChannel.h new file mode 100644 index 00000000..0cd0220d --- /dev/null +++ b/crypto/smc-envelope/PaymentChannel.h @@ -0,0 +1,282 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ + +#pragma once +#include "vm/cells.h" +#include "vm/cellslice.h" +#include "Ed25519.h" +#include "block/block-auto.h" +#include "block/block-parse.h" + +#include "td/utils/Variant.h" + +#include "SmartContract.h" +#include "SmartContractCode.h" + +namespace ton { +namespace pchan { + +// +// Payment channels +// +struct Config { + td::uint32 init_timeout{0}; + td::uint32 close_timeout{0}; + td::SecureString a_key; + td::SecureString b_key; + block::StdAddress a_addr; + block::StdAddress b_addr; + td::uint64 channel_id{0}; + td::uint64 min_A_extra{0}; + + td::Ref serialize() const; +}; + +struct MsgInit { + td::uint64 inc_A{0}; + td::uint64 inc_B{0}; + td::uint64 min_A{0}; + td::uint64 min_B{0}; + td::uint64 channel_id{0}; + + td::Ref serialize() const; +}; + +struct Promise { + td::uint64 channel_id; + td::uint64 promise_A{0}; + td::uint64 promise_B{0}; + td::Ref serialize() const; +}; + +td::Ref maybe_sign(const td::Ref& msg, const td::Ed25519::PrivateKey* key); +td::Ref maybe_ref(td::Ref msg); + +struct MsgClose { + td::uint64 extra_A{0}; + td::uint64 extra_B{0}; + td::Ref signed_promise; + td::Ref serialize() const; +}; + +struct MsgTimeout { + td::Ref serialize() const; +}; + +struct MsgPayout { + td::Ref serialize() const; +}; + +struct SignedPromise { + Promise promise; + td::optional o_signature; + + bool unpack(td::Ref cell); + static td::SecureString signature(const td::Ed25519::PrivateKey* key, const td::Ref& promise); + static td::Ref create_and_serialize(td::Slice signature, const td::Ref& promise); + static td::Ref create_and_serialize(const td::Ed25519::PrivateKey* key, const td::Ref& promise); +}; + +struct StateInit { + bool signed_A{false}; + bool signed_B{false}; + td::uint64 min_A{0}; + td::uint64 min_B{0}; + td::uint64 A{0}; + td::uint64 B{0}; + td::uint32 expire_at{0}; + + td::Ref serialize() const; +}; + +struct StateClose { + bool signed_A{false}; + bool signed_B{false}; + td::uint64 promise_A{0}; + td::uint64 promise_B{0}; + td::uint64 A{0}; + td::uint64 B{0}; + td::uint32 expire_at{0}; +}; + +struct StatePayout { + td::uint64 A{0}; + td::uint64 B{0}; +}; + +struct Data { + td::Ref config; + td::Ref state; + + static td::Ref init_state(); + + td::Ref serialize() const; +}; + +template +struct MsgBuilder { + td::Ed25519::PrivateKey* a_key{nullptr}; + td::Ed25519::PrivateKey* b_key{nullptr}; + + T&& with_a_key(td::Ed25519::PrivateKey* key) && { + a_key = key; + return static_cast(*this); + } + T&& with_b_key(td::Ed25519::PrivateKey* key) && { + b_key = key; + return static_cast(*this); + } + + td::Ref finalize() && { + block::gen::ChanSignedMsg::Record rec; + auto msg = static_cast(*this).msg.serialize(); + rec.msg = vm::load_cell_slice_ref(msg); + rec.sig_A = maybe_ref(maybe_sign(msg, a_key)); + rec.sig_B = maybe_ref(maybe_sign(msg, b_key)); + block::gen::ChanOp::Record op_rec; + CHECK(tlb::csr_pack(op_rec.msg, rec)); + LOG(ERROR) << op_rec.msg->size(); + td::Ref res; + CHECK(tlb::pack_cell(res, op_rec)); + return res; + } +}; + +struct MsgInitBuilder : public MsgBuilder { + MsgInit msg; + + MsgInitBuilder&& min_A(td::uint64 value) && { + msg.min_A = value; + return std::move(*this); + } + MsgInitBuilder&& min_B(td::uint64 value) && { + msg.min_B = value; + return std::move(*this); + } + MsgInitBuilder&& inc_A(td::uint64 value) && { + msg.inc_A = value; + return std::move(*this); + } + MsgInitBuilder&& inc_B(td::uint64 value) && { + msg.inc_B = value; + return std::move(*this); + } + MsgInitBuilder&& channel_id(td::uint64 value) && { + msg.channel_id = value; + return std::move(*this); + } +}; + +struct MsgTimeoutBuilder : public MsgBuilder { + MsgTimeout msg; +}; + +struct MsgPayoutBuilder : public MsgBuilder { + MsgPayout msg; +}; + +struct MsgCloseBuilder : public MsgBuilder { + MsgClose msg; + + MsgCloseBuilder&& extra_A(td::uint64 value) && { + msg.extra_A = value; + return std::move(*this); + } + MsgCloseBuilder&& extra_B(td::uint64 value) && { + msg.extra_B = value; + return std::move(*this); + } + MsgCloseBuilder&& signed_promise(td::Ref signed_promise) && { + msg.signed_promise = vm::load_cell_slice_ref(signed_promise); + return std::move(*this); + } +}; + +struct SignedPromiseBuilder { + Promise promise; + td::optional o_signature; + td::Ed25519::PrivateKey* key{nullptr}; + + SignedPromiseBuilder& with_key(td::Ed25519::PrivateKey* key) { + this->key = key; + return *this; + } + SignedPromiseBuilder& promise_A(td::uint64 value) { + promise.promise_A = value; + return *this; + } + SignedPromiseBuilder& promise_B(td::uint64 value) { + promise.promise_B = value; + return *this; + } + SignedPromiseBuilder& channel_id(td::uint64 value) { + promise.channel_id = value; + return *this; + } + SignedPromiseBuilder& signature(td::SecureString signature) { + o_signature = std::move(signature); + return *this; + } + + bool check_signature(td::Slice signature, const td::Ed25519::PublicKey& pk) { + return pk.verify_signature(promise.serialize()->get_hash().as_slice(), signature).is_ok(); + } + td::SecureString calc_signature() { + CHECK(key); + return SignedPromise::signature(key, promise.serialize()); + } + td::Ref finalize() { + if (o_signature) { + return SignedPromise::create_and_serialize(o_signature.value().copy(), promise.serialize()); + } else { + return SignedPromise::create_and_serialize(key, promise.serialize()); + } + } +}; + +} // namespace pchan + +class PaymentChannel : public SmartContract { + public: + PaymentChannel(State state) : SmartContract(std::move(state)) { + } + + struct Info { + pchan::Config config; + td::Variant state; + std::string description; + }; + td::Result get_info() const; + + static td::Ref create(State state) { + return td::Ref(true, std::move(state)); + } + static td::optional guess_revision(const vm::Cell::Hash& code_hash); + static td::Ref create(const pchan::Config& config, td::int32 revision) { + State state; + state.code = SmartContractCode::get_code(SmartContractCode::PaymentChannel, revision); + pchan::Data data; + data.config = config.serialize(); + pchan::StateInit init; + data.state = init.serialize(); + state.data = data.serialize(); + return create(std::move(state)); + } +}; +} // namespace ton diff --git a/crypto/smc-envelope/SmartContract.cpp b/crypto/smc-envelope/SmartContract.cpp index 02964a7c..2578a951 100644 --- a/crypto/smc-envelope/SmartContract.cpp +++ b/crypto/smc-envelope/SmartContract.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "SmartContract.h" @@ -24,50 +24,174 @@ #include "block/block-auto.h" #include "vm/cellslice.h" #include "vm/cp0.h" -#include "vm/continuation.h" +#include "vm/memo.h" +#include "vm/vm.h" #include "td/utils/crypto.h" namespace ton { +int SmartContract::Answer::output_actions_count(td::Ref list) { + int i = -1; + do { + ++i; + list = load_cell_slice(std::move(list)).prefetch_ref(); + } while (list.not_null()); + return i; +} namespace { -td::Ref prepare_vm_stack(td::Ref body) { + +td::Ref build_internal_message(td::RefInt256 amount, td::Ref body, SmartContract::Args args) { + vm::CellBuilder cb; + if (args.address) { + td::BigInt256 dest_addr; + dest_addr.import_bits((*args.address).addr.as_bitslice()); + cb.store_ones(1) + .store_zeroes(2) + .store_long((*args.address).workchain, 8) + .store_int256(dest_addr, 256); + } + auto address = cb.finalize(); + + vm::CellBuilder b; + b.store_long(0b0110, 4); // 0 ihr_disabled:Bool bounce:Bool bounced:Bool + // use -1:00..00 as src:MsgAddressInt + // addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt; + b.store_long(0b100, 3); b.store_ones(8); b.store_zeroes(256); + b.append_cellslice(address); // dest:MsgAddressInt + unsigned len = (((unsigned)amount->bit_size(false) + 7) >> 3); + b.store_long_bool(len, 4) && b.store_int256_bool(*amount, len * 8, false); // grams:Grams + b.store_zeroes(1 + 4 + 4 + 64 + 32 + 1); // extre, ihr_fee, fwd_fee, created_lt, created_at, init + // body:(Either X ^X) + if (b.remaining_bits() >= 1 + (*body).size() && b.remaining_refs() >= (*body).size_refs()) { + b.store_zeroes(1); + b.append_cellslice(body); + } else { + b.store_ones(1); + b.store_ref(vm::CellBuilder().append_cellslice(body).finalize_novm()); + } + return b.finalize_novm(); +} + +td::Ref build_external_message(td::RefInt256 amount, td::Ref body, SmartContract::Args args) { + vm::CellBuilder cb; + if (args.address) { + td::BigInt256 dest_addr; + dest_addr.import_bits((*args.address).addr.as_bitslice()); + cb.store_ones(1) + .store_zeroes(2) + .store_long((*args.address).workchain, 8) + .store_int256(dest_addr, 256); + } + auto address = cb.finalize(); + + vm::CellBuilder b; + b.store_long(0b1000, 4); // ext_in_msg_info$10 src:MsgAddressExt + b.append_cellslice(address); // dest:MsgAddressInt + b.store_zeroes(4); //import_fee:Grams + b.store_zeroes(1); // init + // body:(Either X ^X) + if (b.remaining_bits() >= 1 + (*body).size() && b.remaining_refs() >= (*body).size_refs()) { + b.store_zeroes(1); + b.append_cellslice(body); + } else { + b.store_ones(1); + b.store_ref(vm::CellBuilder().append_cellslice(body).finalize_novm()); + } + return b.finalize_novm(); +} + +td::Ref prepare_vm_stack(td::RefInt256 amount, td::Ref body, SmartContract::Args args, int selector) { td::Ref stack_ref{true}; td::RefInt256 acc_addr{true}; //CHECK(acc_addr.write().import_bits(account.addr.cbits(), 256)); vm::Stack& stack = stack_ref.write(); - stack.push_int(td::RefInt256{true, 10000000000}); - stack.push_int(td::RefInt256{true, 10000000000}); - stack.push_cell(vm::CellBuilder().finalize()); + if(args.balance) { + stack.push_int(td::make_refint(args.balance)); + } else { + stack.push_int(td::make_refint(10000000000)); + } + stack.push_int(amount); + if(selector == 0) { + stack.push_cell(build_internal_message(amount, body, args)); + } else { + stack.push_cell(build_external_message(amount, body, args)); + } stack.push_cellslice(std::move(body)); return stack_ref; } -td::Ref prepare_vm_c7() { - // TODO: fix initialization of c7 +td::Ref prepare_vm_c7(SmartContract::Args args, td::Ref code) { td::BitArray<256> rand_seed; - rand_seed.as_slice().fill(0); + if (args.rand_seed) { + rand_seed = args.rand_seed.unwrap(); + } else { + rand_seed.as_slice().fill(0); + } td::RefInt256 rand_seed_int{true}; rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false); - auto tuple = vm::make_tuple_ref( - td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea - td::make_refint(0), // actions:Integer - td::make_refint(0), // msgs_sent:Integer - td::make_refint(0), // unixtime:Integer - td::make_refint(0), // block_lt:Integer - td::make_refint(0), // trans_lt:Integer - std::move(rand_seed_int), // rand_seed:Integer - block::CurrencyCollection(1000000000).as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)] - vm::load_cell_slice_ref(vm::CellBuilder().finalize()) // myself:MsgAddressInt - //vm::StackEntry::maybe(td::Ref()) - ); // global_config:(Maybe Cell) ] = SmartContractInfo; + + td::uint32 now = 0; + if (args.now) { + now = args.now.unwrap(); + } + + vm::CellBuilder cb; + if (args.address) { + td::BigInt256 dest_addr; + dest_addr.import_bits((*args.address).addr.as_bitslice()); + cb.store_ones(1).store_zeroes(2).store_long((*args.address).workchain, 8).store_int256(dest_addr, 256); + } + auto address = cb.finalize(); + auto config = td::Ref(); + + if (args.config) { + config = (*args.config)->get_root_cell(); + } + + std::vector tuple = { + td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea + td::make_refint(0), // actions:Integer + td::make_refint(0), // msgs_sent:Integer + td::make_refint(now), // unixtime:Integer + td::make_refint(0), //TODO: // block_lt:Integer + td::make_refint(0), //TODO: // trans_lt:Integer + std::move(rand_seed_int), // rand_seed:Integer + block::CurrencyCollection(args.balance).as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)] + vm::load_cell_slice_ref(address), // myself:MsgAddressInt + vm::StackEntry::maybe(config) // vm::StackEntry::maybe(td::Ref()) + }; + if (args.config && args.config.value()->get_global_version() >= 4) { + tuple.push_back(vm::StackEntry::maybe(code)); // code:Cell + tuple.push_back(block::CurrencyCollection::zero().as_vm_tuple()); // in_msg_value:[Integer (Maybe Cell)] + tuple.push_back(td::zero_refint()); // storage_fees:Integer + + // See crypto/block/mc-config.cpp#2115 (get_prev_blocks_info) + // [ wc:Integer shard:Integer seqno:Integer root_hash:Integer file_hash:Integer] = BlockId; + // [ last_mc_blocks:[BlockId...] + // prev_key_block:BlockId ] : PrevBlocksInfo + tuple.push_back(args.prev_blocks_info ? args.prev_blocks_info.value() : vm::StackEntry{}); // prev_block_info + } + if (args.config && args.config.value()->get_global_version() >= 6) { + tuple.push_back(args.config.value()->get_unpacked_config_tuple(now)); // unpacked_config_tuple + tuple.push_back(td::zero_refint()); // due_payment + // precomiled_gas_usage:(Maybe Integer) + td::optional precompiled; + if (code.not_null()) { + precompiled = args.config.value()->get_precompiled_contracts_config().get_contract(code->get_hash().bits()); + } + tuple.push_back(precompiled ? td::make_refint(precompiled.value().gas_usage) : vm::StackEntry()); + } + auto tuple_ref = td::make_cnt_ref>(std::move(tuple)); //LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string(); - return vm::make_tuple_ref(std::move(tuple)); + return vm::make_tuple_ref(std::move(tuple_ref)); } SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref stack, td::Ref c7, - vm::GasLimits gas, bool ignore_chksig) { + vm::GasLimits gas, bool ignore_chksig, td::Ref libraries, + int vm_log_verbosity, bool debug_enabled, + std::shared_ptr config) { auto gas_credit = gas.gas_credit; - vm::init_op_cp0(); + vm::init_vm(debug_enabled).ensure(); vm::DictionaryBase::get_empty_dictionary(); class Logger : public td::LogInterface { @@ -78,21 +202,39 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref= VERBOSITY_NAME(DEBUG)) { - log.log_options.level = 4; - log.log_options.fix_newlines = true; - log.log_mask |= vm::VmLog::DumpStack; - } else { - log.log_options.level = 0; - log.log_mask = 0; + vm::VmLog log{&logger, td::LogOptions(VERBOSITY_NAME(DEBUG), true, false)}; + if (vm_log_verbosity > 1) { + log.log_mask |= vm::VmLog::ExecLocation; + if (vm_log_verbosity > 2) { + log.log_mask |= vm::VmLog::GasRemaining; + if (vm_log_verbosity > 3) { + log.log_mask |= vm::VmLog::DumpStack; + if (vm_log_verbosity > 4) { + log.log_mask |= vm::VmLog::DumpStackVerbose; + } + } + } } SmartContract::Answer res; + if (GET_VERBOSITY_LEVEL() >= VERBOSITY_NAME(DEBUG)) { + std::ostringstream os; + stack->dump(os, 2); + LOG(DEBUG) << "VM stack:\n" << os.str(); + } vm::VmState vm{state.code, std::move(stack), gas, 1, state.data, log}; vm.set_c7(std::move(c7)); vm.set_chksig_always_succeed(ignore_chksig); + if (!libraries.is_null()) { + vm.register_library_collection(libraries); + } + if (config) { + vm.set_global_version(config->get_global_version()); + auto r_limits = config->get_size_limits_config(); + if (r_limits.is_ok()) { + vm.set_max_data_depth(r_limits.ok().max_vm_data_depth); + } + } try { res.code = ~vm.run(); } catch (...) { @@ -103,7 +245,8 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref= VERBOSITY_NAME(DEBUG)) { LOG(DEBUG) << "VM log\n" << logger.res; std::ostringstream os; @@ -113,17 +256,39 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref SmartContract::Args::get_serialized_stack() { + if (!stack) { + return td::Status::Error("Args has no stack"); + } + vm::FakeVmStateLimits fstate(1000); // limit recursive (de)serialization calls + vm::VmStateInterface::Guard guard(&fstate); + // serialize parameters + vm::CellBuilder cb; + td::Ref cell; + if (!(stack.value()->serialize(cb) && cb.finalize_to(cell))) { + return td::Status::Error("Cannot serialize stack in args"); + } + return vm::std_boc_serialize(std::move(cell)); +} + td::Ref SmartContract::empty_slice() { return vm::load_cell_slice_ref(vm::CellBuilder().finalize()); } @@ -145,33 +310,40 @@ td::Ref SmartContract::get_init_state() const { SmartContract::Answer SmartContract::run_method(Args args) { if (!args.c7) { - args.c7 = prepare_vm_c7(); + args.c7 = prepare_vm_c7(args, state_.code); } if (!args.limits) { - args.limits = vm::GasLimits{(long long)0, (long long)1000000, (long long)10000}; + bool is_internal = args.get_method_id().ok() == 0; + + args.limits = vm::GasLimits{is_internal ? (long long)args.amount * 1000 : (long long)0, (long long)1000000, + is_internal ? 0 : (long long)10000}; } CHECK(args.stack); CHECK(args.method_id); args.stack.value().write().push_smallint(args.method_id.unwrap()); auto res = - run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig); + run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig, + args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref{}, + args.vm_log_verbosity_level, args.debug_enabled, args.config ? args.config.value() : nullptr); state_ = res.new_state; return res; } SmartContract::Answer SmartContract::run_get_method(Args args) const { if (!args.c7) { - args.c7 = prepare_vm_c7(); + args.c7 = prepare_vm_c7(args, state_.code); } if (!args.limits) { - args.limits = vm::GasLimits{1000000}; + args.limits = vm::GasLimits{1000000, 1000000}; } if (!args.stack) { args.stack = td::Ref(true); } CHECK(args.method_id); args.stack.value().write().push_smallint(args.method_id.unwrap()); - return run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig); + return run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig, + args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref{}, + args.vm_log_verbosity_level, args.debug_enabled, args.config ? args.config.value() : nullptr); } SmartContract::Answer SmartContract::run_get_method(td::Slice method, Args args) const { @@ -179,6 +351,11 @@ SmartContract::Answer SmartContract::run_get_method(td::Slice method, Args args) } SmartContract::Answer SmartContract::send_external_message(td::Ref cell, Args args) { - return run_method(args.set_stack(prepare_vm_stack(vm::load_cell_slice_ref(cell))).set_method_id(-1)); + return run_method( + args.set_stack(prepare_vm_stack(td::make_refint(0), vm::load_cell_slice_ref(cell), args, -1)).set_method_id(-1)); +} +SmartContract::Answer SmartContract::send_internal_message(td::Ref cell, Args args) { + return run_method( + args.set_stack(prepare_vm_stack(td::make_refint(args.amount), vm::load_cell_slice_ref(cell), args, 0)).set_method_id(0)); } } // namespace ton diff --git a/crypto/smc-envelope/SmartContract.h b/crypto/smc-envelope/SmartContract.h index d5864361..7fc93579 100644 --- a/crypto/smc-envelope/SmartContract.h +++ b/crypto/smc-envelope/SmartContract.h @@ -14,18 +14,19 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells.h" #include "vm/stack.hpp" -#include "vm/continuation.h" +#include "vm/vm.h" #include "td/utils/optional.h" #include "td/utils/crypto.h" #include "block/block.h" +#include "block/mc-config.h" namespace ton { class SmartContract : public td::CntObject { @@ -48,6 +49,9 @@ class SmartContract : public td::CntObject { td::Ref actions; td::int32 code; td::int64 gas_used; + td::optional missing_library; + std::string vm_log; + static int output_actions_count(td::Ref list); }; struct Args { @@ -55,13 +59,28 @@ class SmartContract : public td::CntObject { td::optional limits; td::optional> c7; td::optional> stack; + td::optional now; + td::optional> rand_seed; bool ignore_chksig{false}; + td::uint64 amount{0}; + td::uint64 balance{0}; + int vm_log_verbosity_level{0}; + bool debug_enabled{false}; + + td::optional address; + td::optional> config; + td::optional libraries; + td::optional> prev_blocks_info; Args() { } Args(std::initializer_list stack) : stack(td::Ref(true, std::vector(std::move(stack)))) { } + Args&& set_now(int now) { + this->now = now; + return std::move(*this); + } Args&& set_method_id(td::Slice method_name) { unsigned crc = td::crc16(method_name); return set_method_id((crc & 0xffff) | 0x10000); @@ -86,16 +105,65 @@ class SmartContract : public td::CntObject { this->stack = std::move(stack); return std::move(*this); } + Args&& set_rand_seed(td::BitArray<256> rand_seed) { + this->rand_seed = std::move(rand_seed); + return std::move(*this); + } Args&& set_ignore_chksig(bool ignore_chksig) { this->ignore_chksig = ignore_chksig; return std::move(*this); } + Args&& set_amount(td::uint64 amount) { + this->amount = amount; + return std::move(*this); + } + Args&& set_balance(td::uint64 balance) { + this->balance = balance; + return std::move(*this); + } + Args&& set_address(block::StdAddress address) { + this->address = address; + return std::move(*this); + } + Args&& set_config(const std::shared_ptr& config) { + this->config = config; + return std::move(*this); + } + Args&& set_libraries(vm::Dictionary libraries) { + this->libraries = libraries; + return std::move(*this); + } + Args&& set_prev_blocks_info(td::Ref tuple) { + if (tuple.is_null()) { + this->prev_blocks_info = {}; + } else { + this->prev_blocks_info = std::move(tuple); + } + return std::move(*this); + } + Args&& set_vm_verbosity_level(int vm_log_verbosity_level) { + this->vm_log_verbosity_level = vm_log_verbosity_level; + return std::move(*this); + } + Args&& set_debug_enabled(bool debug_enabled) { + this->debug_enabled = debug_enabled; + return std::move(*this); + } + + td::Result get_method_id() const { + if (!method_id) { + return td::Status::Error("Args has no method id"); + } + return method_id.value(); + } + td::Result get_serialized_stack(); }; Answer run_method(Args args = {}); Answer run_get_method(Args args = {}) const; Answer run_get_method(td::Slice method, Args args = {}) const; Answer send_external_message(td::Ref cell, Args args = {}); + Answer send_internal_message(td::Ref cell, Args args = {}); size_t code_size() const; size_t data_size() const; @@ -109,6 +177,9 @@ class SmartContract : public td::CntObject { const State& get_state() const { return state_; } + CntObject* make_copy() const override { + return new SmartContract(state_); + } protected: State state_; diff --git a/crypto/smc-envelope/SmartContractCode.cpp b/crypto/smc-envelope/SmartContractCode.cpp index e61cab24..585450f6 100644 --- a/crypto/smc-envelope/SmartContractCode.cpp +++ b/crypto/smc-envelope/SmartContractCode.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "SmartContractCode.h" @@ -25,6 +25,13 @@ namespace ton { namespace { +// WALLET_REVISION = 2; +// WALLET2_REVISION = 2; +// WALLET3_REVISION = 2; +// WALLET4_REVISION = 2; +// HIGHLOAD_WALLET_REVISION = 2; +// HIGHLOAD_WALLET2_REVISION = 2; +// DNS_REVISION = 1; const auto& get_map() { static auto map = [] { std::map, std::less<>> map; @@ -32,10 +39,74 @@ const auto& get_map() { map[name] = vm::std_boc_deserialize(td::base64_decode(code_str).move_as_ok()).move_as_ok(); }; #include "smartcont/auto/multisig-code.cpp" -#include "smartcont/auto/simple-wallet-ext-code.cpp" -#include "smartcont/auto/simple-wallet-code.cpp" #include "smartcont/auto/wallet-code.cpp" #include "smartcont/auto/highload-wallet-code.cpp" +#include "smartcont/auto/highload-wallet-v2-code.cpp" +#include "smartcont/auto/dns-manual-code.cpp" +#include "smartcont/auto/payment-channel-code.cpp" +#include "smartcont/auto/restricted-wallet3-code.cpp" + + with_tvm_code("highload-wallet-r1", + "te6ccgEBBgEAhgABFP8A9KQT9KDyyAsBAgEgAgMCAUgEBQC88oMI1xgg0x/TH9Mf+CMTu/Jj7UTQ0x/TH9P/" + "0VEyuvKhUUS68qIE+QFUEFX5EPKj9ATR+AB/jhghgBD0eG+hb6EgmALTB9QwAfsAkTLiAbPmWwGkyMsfyx/L/" + "8ntVAAE0DAAEaCZL9qJoa4WPw=="); + with_tvm_code("highload-wallet-r2", + "te6ccgEBCAEAlwABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQC48oMI1xgg0x/TH9Mf+CMTu/Jj7UTQ0x/TH9P/" + "0VEyuvKhUUS68qIE+QFUEFX5EPKj9ATR+AB/jhYhgBD0eG+lIJgC0wfUMAH7AJEy4gGz5lsBpMjLH8sfy//" + "J7VQABNAwAgFIBgcAF7s5ztRNDTPzHXC/+AARuMl+1E0NcLH4"); + with_tvm_code("highload-wallet-v2-r1", + "te6ccgEBBwEA1gABFP8A9KQT9KDyyAsBAgEgAgMCAUgEBQHu8oMI1xgg0x/TP/gjqh9TILnyY+1E0NMf0z/T//" + "QE0VNggED0Dm+hMfJgUXO68qIH+QFUEIf5EPKjAvQE0fgAf44YIYAQ9HhvoW+" + "hIJgC0wfUMAH7AJEy4gGz5luDJaHIQDSAQPRDiuYxyBLLHxPLP8v/9ADJ7VQGAATQMABBoZfl2omhpj5jpn+n/" + "mPoCaKkQQCB6BzfQmMktv8ld0fFADgggED0lm+hb6EyURCUMFMDud4gkzM2AZIyMOKz"); + with_tvm_code("highload-wallet-v2-r2", + "te6ccgEBCQEA5QABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQHq8oMI1xgg0x/TP/gjqh9TILnyY+1E0NMf0z/T//" + "QE0VNggED0Dm+hMfJgUXO68qIH+QFUEIf5EPKjAvQE0fgAf44WIYAQ9HhvpSCYAtMH1DAB+wCRMuIBs+" + "ZbgyWhyEA0gED0Q4rmMcgSyx8Tyz/L//QAye1UCAAE0DACASAGBwAXvZznaiaGmvmOuF/8AEG+X5dqJoaY+Y6Z/p/" + "5j6AmipEEAgegc30JjJLb/JXdHxQANCCAQPSWb6UyURCUMFMDud4gkzM2AZIyMOKz"); + with_tvm_code("wallet3-r1", + "te6ccgEBAQEAYgAAwP8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/" + "9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA=="); + with_tvm_code("wallet3-r2", + "te6ccgEBAQEAcQAA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/" + "T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA=="); + with_tvm_code( + "dns-manual-r1", + "te6ccgECGAEAAtAAART/APSkE/S88sgLAQIBIAIDAgFIBAUC7PLbPAWDCNcYIPkBAdMf0z/" + "4I6ofUyC58mNTKoBA9A5voTHyYFKUuvKiVBNG+RDyo/gAItcLBcAzmDQBdtch0/" + "8wjoVa2zxAA+" + "IDgyWhyEAHgED0Q44aIIBA9JZvpTJREJQwUwe53iCTMzUBkjIw4rPmNVUD8AQREgICxQYHAgEgDA0CAc8ICQAIqoJfAwIBSAoLACHWQK5Y+" + "J5Z/l//oAegBk9qpAAFF8DgABcyPQAydBBM/Rw8qGAAF72c52omhpr5jrhf/" + "AIBIA4PABG7Nz7UTQ1wsfgD+" + "7owwh10kglF8DcG3hIHew8l4ieNci1wsHnnDIUATPFhPLB8nQAqYI3iDACJRfA3Bt4Ns8FF8EI3ADqwKY0wcBwAAToQLkIG2OnF8DIcjLBiTPF" + "snQhAlUQgHbPAWlFbIgwQEVQzDmMzUilF8FcG3hMgHHAJMxfwHfAtdJpvmBEVEAAYIcAAkjEB4AKAEPRqABztRNDTH9M/0//" + "0BPQE0QE2cFmOlNs8IMcBnCDXSpPUMNCTMn8C4t4i5jAxEwT20wUhwQqOLCGRMeEhwAGXMdMH1AL7AOABwAmOFNQh+wTtQwLQ7R7tU1RiA/" + "EGgvIA4PIt4HAiwRSUMNIPAd5tbSTBHoreJMEUjpElhAkj2zwzApUyxwDyo5Fb4t4kwAuOEzQC9ARQJIAQ9G4wECOECVnwAQHgJMAMiuAwFBUW" + "FwCEMQLTAAHAAZPUAdCY0wUBqgLXGAHiINdJwg/" + "ypiB41yLXCwfyaHBTEddJqTYCmNMHAcAAEqEB5DDIywYBzxbJ0FADACBZ9KhvpSCUAvQEMJIybeICACg0A4AQ9FqZECOECUBE8AEBkjAx4gBmM" + "SLAFZwy9AQQI4QJUELwAQHgIsAWmDIChAn0czAB4DAyIMAfkzD0BODAIJJtAeDyLG0B"); + with_tvm_code( + "restricted-wallet3-r1", + "te6ccgECEgEAAUsAART/APSkE/S88sgLAQIBIAIDAgFIBAUD+PKDCNcYINMf0x/THwL4I7vyY+1E0NMf0x/T/" + "1NDuvKhUWK68qIG+QFUEHb5EPKkAY4fMwHT/9EB0x/0BNH4AAOkyMsfFMsfy/8Syx/0AMntVOEC0x/" + "0BNH4ACH4I9s8IYAg9HtvpTGW+gAwcvsCkTDiApMg10qK6NECpMgPEBEABNAwAgEgBgcCASAICQIBSAwNAgFuCgsAEbjJftRNDXCx+" + "AAXrc52omhpn5jrhf/AABesePaiaGmPmOuFj8ABDbbYHwR7Z5AOAQm1B1tnkA4BTu1E0IEBQNch0x/" + "0BNEC2zz4J28QAoAg9HtvpTGX+gAwoXC2CZEw4g8AOiGOETGA8/gzIG6SMHCU0NcLH+IB3yGSAaGSW3/iAAzTB9QC+wAAHssfFMsfEsv/yx/" + "0AMntVA=="); + with_tvm_code( + "wallet-v4-r2", + "te6cckECFAEAAtQAART/APSkE/S88sgLAQIBIAIDAgFIBAUE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//" + "QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/" + "UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/" + "8QERITAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/" + "ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/" + "yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNBgcCASAICQB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+" + "gIZ9ADLaRfLH1Jgyz8gyYBA+wAGAIpQBIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UAXKwjiOCEGRzdHKDHrFwgBhQBcsFUAPPFiP6AhPLassfyz/" + "JgED7AJJfA+ICASAKCwBZvSQrb2omhAgKBrkPoCGEcNQICEekk30pkQzmkD6f+YN4EoAbeBAUiYcVnzGEAgFYDA0AEbjJftRNDXCx+" + "AA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIA4PABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AAG7SB/" + "oA1NQi+QAFyMoHFcv/ydB3dIAYyMsFywIizxZQBfoCFMtrEszMyXP7AMhAFIEBCPRR8qcCAHCBAQjXGPoA0z/" + "IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBsgQEI1xj6ANM/" + "MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAAr0AMntVGliJeU="); return map; }(); return map; @@ -46,28 +117,95 @@ td::Result> SmartContractCode::load(td::Slice name) { auto& map = get_map(); auto it = map.find(name); if (it == map.end()) { - return td::Status::Error(PSLICE() << "Can't load td::ref " << name); } return it->second; } -td::Ref SmartContractCode::multisig() { - auto res = load("multisig").move_as_ok(); - return res; + +td::Span SmartContractCode::get_revisions(Type type) { + switch (type) { + case Type::WalletV3: { + static int res[] = {1, 2}; + return res; + } + case Type::HighloadWalletV1: { + static int res[] = {-1, 1, 2}; + return res; + } + case Type::HighloadWalletV2: { + static int res[] = {-1, 1, 2}; + return res; + } + case Type::Multisig: { + static int res[] = {-1}; + return res; + } + case Type::ManualDns: { + static int res[] = {-1, 1}; + return res; + } + case Type::PaymentChannel: { + static int res[] = {-1}; + return res; + } + case Type::RestrictedWallet: { + static int res[] = {1}; + return res; + } + case Type::WalletV4: { + static int res[] = {2}; + return res; + } + } + UNREACHABLE(); } -td::Ref SmartContractCode::wallet() { - auto res = load("wallet").move_as_ok(); - return res; + +td::Result SmartContractCode::validate_revision(Type type, int revision) { + auto revisions = get_revisions(type); + if (revision == -1) { + if (revisions[0] == -1) { + return -1; + } + return revisions[revisions.size() - 1]; + } + if (revision == 0) { + return revisions[revisions.size() - 1]; + } + for (auto x : revisions) { + if (x == revision) { + return revision; + } + } + return td::Status::Error("No such revision"); } -td::Ref SmartContractCode::simple_wallet() { - auto res = load("simple-wallet").move_as_ok(); - return res; -} -td::Ref SmartContractCode::simple_wallet_ext() { - static auto res = load("simple-wallet-ext").move_as_ok(); - return res; -} -td::Ref SmartContractCode::highload_wallet() { - static auto res = load("highload-wallet").move_as_ok(); - return res; + +td::Ref SmartContractCode::get_code(Type type, int ext_revision) { + auto revision = validate_revision(type, ext_revision).move_as_ok(); + auto basename = [](Type type) -> td::Slice { + switch (type) { + case Type::WalletV3: + return "wallet3"; + case Type::HighloadWalletV1: + return "highload-wallet"; + case Type::HighloadWalletV2: + return "highload-wallet-v2"; + case Type::Multisig: + return "multisig"; + case Type::ManualDns: + return "dns-manual"; + case Type::PaymentChannel: + return "payment-channel"; + case Type::RestrictedWallet: + return "restricted-wallet3"; + case Type::WalletV4: + return "wallet-v4"; + } + UNREACHABLE(); + }(type); + if (revision == -1) { + return load(basename).move_as_ok(); + } + return load(PSLICE() << basename << "-r" << revision).move_as_ok(); } + } // namespace ton diff --git a/crypto/smc-envelope/SmartContractCode.h b/crypto/smc-envelope/SmartContractCode.h index 0c9e4764..be50d2a1 100644 --- a/crypto/smc-envelope/SmartContractCode.h +++ b/crypto/smc-envelope/SmartContractCode.h @@ -14,18 +14,30 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ +#pragma once #include "vm/cells.h" +#include "td/utils/Span.h" + namespace ton { class SmartContractCode { public: static td::Result> load(td::Slice name); - static td::Ref multisig(); - static td::Ref wallet(); - static td::Ref simple_wallet(); - static td::Ref simple_wallet_ext(); - static td::Ref highload_wallet(); + + enum Type { + WalletV3 = 4, + HighloadWalletV1, + HighloadWalletV2, + ManualDns, + Multisig, + PaymentChannel, + RestrictedWallet, + WalletV4 + }; + static td::Span get_revisions(Type type); + static td::Result validate_revision(Type type, int revision); + static td::Ref get_code(Type type, int revision = 0); }; } // namespace ton diff --git a/crypto/smc-envelope/TestGiver.cpp b/crypto/smc-envelope/TestGiver.cpp index 2d44d730..2a169183 100644 --- a/crypto/smc-envelope/TestGiver.cpp +++ b/crypto/smc-envelope/TestGiver.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "TestGiver.h" #include "GenericAccount.h" @@ -35,14 +35,18 @@ vm::CellHash TestGiver::get_init_code_hash() noexcept { //return vm::CellHash::from_slice(td::base64_decode("YV/IANhoI22HVeatFh6S5LbCHp+5OilARfzW+VQPZgQ=").move_as_ok()); } -td::Ref TestGiver::make_a_gift_message(td::uint32 seqno, td::uint64 gramms, td::Slice message, - const block::StdAddress& dest_address) noexcept { +td::Ref TestGiver::make_a_gift_message_static(td::uint32 seqno, td::Span gifts) noexcept { + CHECK(gifts.size() <= max_gifts_size); + vm::CellBuilder cb; - GenericAccount::store_int_message(cb, dest_address, gramms); - cb.store_bytes("\0\0\0\0", 4); - vm::CellString::store(cb, message, 35 * 8).ensure(); - auto message_inner = cb.finalize(); - return vm::CellBuilder().store_long(seqno, 32).store_long(1, 8).store_ref(message_inner).finalize(); + cb.store_long(seqno, 32); + + for (auto& gift : gifts) { + td::int32 send_mode = 1; + cb.store_long(send_mode, 8).store_ref(create_int_message(gift)); + } + + return cb.finalize(); } td::Result TestGiver::get_seqno() const { diff --git a/crypto/smc-envelope/TestGiver.h b/crypto/smc-envelope/TestGiver.h index 210b6912..b51ac7db 100644 --- a/crypto/smc-envelope/TestGiver.h +++ b/crypto/smc-envelope/TestGiver.h @@ -14,25 +14,38 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "block/block.h" #include "vm/cells/CellString.h" namespace ton { -class TestGiver : public SmartContract { +class TestGiver : public SmartContract, public WalletInterface { public: explicit TestGiver(State state) : ton::SmartContract(std::move(state)) { } + TestGiver() : ton::SmartContract({}) { + } static constexpr unsigned max_message_size = vm::CellString::max_bytes; + static constexpr unsigned max_gifts_size = 1; static const block::StdAddress& address() noexcept; static vm::CellHash get_init_code_hash() noexcept; - static td::Ref make_a_gift_message(td::uint32 seqno, td::uint64 gramms, td::Slice message, - const block::StdAddress& dest_address) noexcept; + static td::Ref make_a_gift_message_static(td::uint32 seqno, td::Span) noexcept; td::Result get_seqno() const; + using WalletInterface::get_init_message; + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + TRY_RESULT(seqno, get_seqno()); + return make_a_gift_message_static(seqno, gifts); + } + size_t get_max_gifts_size() const override { + return max_gifts_size; + } + private: td::Result get_seqno_or_throw() const; }; diff --git a/crypto/smc-envelope/TestWallet.cpp b/crypto/smc-envelope/TestWallet.cpp index 1edf59f9..41a66b68 100644 --- a/crypto/smc-envelope/TestWallet.cpp +++ b/crypto/smc-envelope/TestWallet.cpp @@ -13,64 +13,63 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . -Copyright 2017-2019 Telegram Systems LLP +Copyright 2017-2020 Telegram Systems LLP */ #include "TestWallet.h" #include "GenericAccount.h" +#include "SmartContractCode.h" + #include "vm/boc.h" #include "td/utils/base64.h" namespace ton { -td::Ref TestWallet::get_init_state(const td::Ed25519::PublicKey& public_key) noexcept { - auto code = get_init_code(); +td::Ref TestWallet::get_init_state(const td::Ed25519::PublicKey& public_key, td::int32 revision) noexcept { + auto code = get_init_code(revision); auto data = get_init_data(public_key); return GenericAccount::get_init_state(std::move(code), std::move(data)); } -td::Ref TestWallet::get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept { +td::Ref TestWallet::get_init_message_new(const td::Ed25519::PrivateKey& private_key) noexcept { std::string seq_no(4, 0); auto signature = private_key.sign(vm::CellBuilder().store_bytes(seq_no).finalize()->get_hash().as_slice()).move_as_ok(); return vm::CellBuilder().store_bytes(signature).store_bytes(seq_no).finalize(); } -td::Ref TestWallet::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, - td::int64 gramms, td::Slice message, - const block::StdAddress& dest_address) noexcept { - td::int32 send_mode = 3; - if (gramms == -1) { - gramms = 0; - send_mode += 128; - } +td::Ref TestWallet::make_a_gift_message_static(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, + td::Span gifts) noexcept { + CHECK(gifts.size() <= max_gifts_size); + vm::CellBuilder cb; - GenericAccount::store_int_message(cb, dest_address, gramms); - cb.store_bytes("\0\0\0\0", 4); - vm::CellString::store(cb, message, 35 * 8).ensure(); - auto message_inner = cb.finalize(); - auto message_outer = - vm::CellBuilder().store_long(seqno, 32).store_long(send_mode, 8).store_ref(message_inner).finalize(); + cb.store_long(seqno, 32); + + for (auto& gift : gifts) { + td::int32 send_mode = 3; + if (gift.gramms == -1) { + send_mode += 128; + } + cb.store_long(send_mode, 8).store_ref(create_int_message(gift)); + } + auto message_outer = cb.finalize(); auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok(); return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize(); } -td::Ref TestWallet::get_init_code() noexcept { - static auto res = [] { - auto serialized_code = td::base64_decode( - "te6ccgEEAQEAAAAAUwAAov8AIN0gggFMl7qXMO1E0NcLH+Ck8mCBAgDXGCDXCx/tRNDTH9P/" - "0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVA==") - .move_as_ok(); - return vm::std_boc_deserialize(serialized_code).move_as_ok(); - }(); - return res; +td::Ref TestWallet::get_init_code(td::int32 revision) noexcept { + return ton::SmartContractCode::get_code(ton::SmartContractCode::WalletV1, revision); } vm::CellHash TestWallet::get_init_code_hash() noexcept { return get_init_code()->get_hash(); } +td::Ref TestWallet::get_data(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) noexcept { + return vm::CellBuilder().store_long(seqno, 32).store_bytes(public_key.as_octet_string()).finalize(); +} + td::Ref TestWallet::get_init_data(const td::Ed25519::PublicKey& public_key) noexcept { - return vm::CellBuilder().store_long(0, 32).store_bytes(public_key.as_octet_string()).finalize(); + return get_data(public_key, 0); } td::Result TestWallet::get_seqno() const { @@ -88,4 +87,20 @@ td::Result TestWallet::get_seqno_or_throw() const { return static_cast(seqno); } +td::Result TestWallet::get_public_key() const { + return TRY_VM(get_public_key_or_throw()); +} + +td::Result TestWallet::get_public_key_or_throw() const { + if (state_.data.is_null()) { + return td::Status::Error("data is null"); + } + //FIXME use get method + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(32); + td::SecureString res(td::Ed25519::PublicKey::LENGTH); + cs.fetch_bytes(res.as_mutable_slice().ubegin(), td::narrow_cast(res.size())); + return td::Ed25519::PublicKey(std::move(res)); +} + } // namespace ton diff --git a/crypto/smc-envelope/TestWallet.h b/crypto/smc-envelope/TestWallet.h index 161aef58..38e0f12b 100644 --- a/crypto/smc-envelope/TestWallet.h +++ b/crypto/smc-envelope/TestWallet.h @@ -14,35 +14,53 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "vm/cells.h" #include "Ed25519.h" #include "block/block.h" #include "vm/cells/CellString.h" namespace ton { -class TestWallet : public ton::SmartContract { +class TestWallet : public ton::SmartContract, public WalletInterface { public: explicit TestWallet(State state) : ton::SmartContract(std::move(state)) { } + explicit TestWallet(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) + : TestWallet(State{get_init_code(), get_data(public_key, seqno)}) { + } static constexpr unsigned max_message_size = vm::CellString::max_bytes; - static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key) noexcept; - static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept; - static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, - td::int64 gramms, td::Slice message, - const block::StdAddress& dest_address) noexcept; + static constexpr unsigned max_gifts_size = 1; + static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::int32 revision = 0) noexcept; + static td::Ref get_init_message_new(const td::Ed25519::PrivateKey& private_key) noexcept; + static td::Ref make_a_gift_message_static(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, + td::Span gifts) noexcept; - static td::Ref get_init_code() noexcept; + static td::Ref get_init_code(td::int32 revision = 0) noexcept; static vm::CellHash get_init_code_hash() noexcept; + static td::Ref get_data(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) noexcept; static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key) noexcept; td::Result get_seqno() const; + using WalletInterface::get_init_message; + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + TRY_RESULT(seqno, get_seqno()); + return make_a_gift_message_static(private_key, seqno, gifts); + } + size_t get_max_gifts_size() const override { + return max_gifts_size; + } + + td::Result get_public_key() const override; + private: td::Result get_seqno_or_throw() const; + td::Result get_public_key_or_throw() const; }; } // namespace ton diff --git a/crypto/smc-envelope/Wallet.cpp b/crypto/smc-envelope/Wallet.cpp index 61958ed9..fac4f1a6 100644 --- a/crypto/smc-envelope/Wallet.cpp +++ b/crypto/smc-envelope/Wallet.cpp @@ -14,10 +14,11 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "Wallet.h" #include "GenericAccount.h" +#include "SmartContractCode.h" #include "vm/boc.h" #include "vm/cells/CellString.h" @@ -26,13 +27,13 @@ #include namespace ton { -td::Ref Wallet::get_init_state(const td::Ed25519::PublicKey& public_key) noexcept { - auto code = get_init_code(); +td::Ref Wallet::get_init_state(const td::Ed25519::PublicKey& public_key, td::int32 revision) noexcept { + auto code = get_init_code(revision); auto data = get_init_data(public_key); return GenericAccount::get_init_state(std::move(code), std::move(data)); } -td::Ref Wallet::get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept { +td::Ref Wallet::get_init_message_new(const td::Ed25519::PrivateKey& private_key) noexcept { td::uint32 seqno = 0; td::uint32 valid_until = std::numeric_limits::max(); auto signature = @@ -43,46 +44,39 @@ td::Ref Wallet::get_init_message(const td::Ed25519::PrivateKey& privat } td::Ref Wallet::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, - td::uint32 valid_until, td::int64 gramms, td::Slice message, - const block::StdAddress& dest_address) noexcept { - td::int32 send_mode = 3; - if (gramms == -1) { - gramms = 0; - send_mode += 128; - } - vm::CellBuilder cb; - GenericAccount::store_int_message(cb, dest_address, gramms); - cb.store_bytes("\0\0\0\0", 4); - vm::CellString::store(cb, message, 35 * 8).ensure(); - auto message_inner = cb.finalize(); + td::uint32 valid_until, td::Span gifts) noexcept { + CHECK(gifts.size() <= max_gifts_size); - auto message_outer = vm::CellBuilder() - .store_long(seqno, 32) - .store_long(valid_until, 32) - .store_long(send_mode, 8) - .store_ref(message_inner) - .finalize(); + vm::CellBuilder cb; + cb.store_long(seqno, 32).store_long(valid_until, 32); + + for (auto& gift : gifts) { + td::int32 send_mode = 3; + if (gift.gramms == -1) { + send_mode += 128; + } + cb.store_long(send_mode, 8).store_ref(create_int_message(gift)); + } + + auto message_outer = cb.finalize(); auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok(); return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize(); } -td::Ref Wallet::get_init_code() noexcept { - static auto res = [] { - auto serialized_code = td::base64_decode( - "te6ccgEEAQEAAAAAVwAAqv8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/" - "0VExuvKhA/kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VQ=") - .move_as_ok(); - return vm::std_boc_deserialize(serialized_code).move_as_ok(); - }(); - return res; +td::Ref Wallet::get_init_code(td::int32 revision) noexcept { + return SmartContractCode::get_code(ton::SmartContractCode::WalletV2, revision); } vm::CellHash Wallet::get_init_code_hash() noexcept { return get_init_code()->get_hash(); } +td::Ref Wallet::get_data(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) noexcept { + return vm::CellBuilder().store_long(seqno, 32).store_bytes(public_key.as_octet_string()).finalize(); +} + td::Ref Wallet::get_init_data(const td::Ed25519::PublicKey& public_key) noexcept { - return vm::CellBuilder().store_long(0, 32).store_bytes(public_key.as_octet_string()).finalize(); + return get_data(public_key, 0); } td::Result Wallet::get_seqno() const { @@ -97,4 +91,20 @@ td::Result Wallet::get_seqno_or_throw() const { return static_cast(vm::load_cell_slice(state_.data).fetch_ulong(32)); } +td::Result Wallet::get_public_key() const { + return TRY_VM(get_public_key_or_throw()); +} + +td::Result Wallet::get_public_key_or_throw() const { + if (state_.data.is_null()) { + return td::Status::Error("data is null"); + } + //FIXME use get method + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(32); + td::SecureString res(td::Ed25519::PublicKey::LENGTH); + cs.fetch_bytes(res.as_mutable_slice().ubegin(), td::narrow_cast(res.size())); + return td::Ed25519::PublicKey(std::move(res)); +} + } // namespace ton diff --git a/crypto/smc-envelope/Wallet.h b/crypto/smc-envelope/Wallet.h index 7cd33c81..db074f6a 100644 --- a/crypto/smc-envelope/Wallet.h +++ b/crypto/smc-envelope/Wallet.h @@ -14,35 +14,53 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "vm/cells.h" #include "Ed25519.h" #include "block/block.h" #include "vm/cells/CellString.h" namespace ton { -class Wallet : ton::SmartContract { +class Wallet : public ton::SmartContract, public WalletInterface { public: explicit Wallet(State state) : ton::SmartContract(std::move(state)) { } + explicit Wallet(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) + : Wallet(State{get_init_code(), get_data(public_key, seqno)}) { + } static constexpr unsigned max_message_size = vm::CellString::max_bytes; - static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key) noexcept; - static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept; + static constexpr unsigned max_gifts_size = 4; + static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::int32 revision = 0) noexcept; + static td::Ref get_init_message_new(const td::Ed25519::PrivateKey& private_key) noexcept; static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, - td::uint32 valid_until, td::int64 gramms, td::Slice message, - const block::StdAddress& dest_address) noexcept; + td::uint32 valid_until, td::Span gifts) noexcept; - static td::Ref get_init_code() noexcept; + static td::Ref get_init_code(td::int32 revision = 0) noexcept; static vm::CellHash get_init_code_hash() noexcept; static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key) noexcept; + static td::Ref get_data(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) noexcept; td::Result get_seqno() const; + using WalletInterface::get_init_message; + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + TRY_RESULT(seqno, get_seqno()); + return make_a_gift_message(private_key, seqno, valid_until, gifts); + } + size_t get_max_gifts_size() const override { + return max_gifts_size; + } + + td::Result get_public_key() const override; + private: td::Result get_seqno_or_throw() const; + td::Result get_public_key_or_throw() const; }; } // namespace ton diff --git a/crypto/smc-envelope/WalletInterface.cpp b/crypto/smc-envelope/WalletInterface.cpp new file mode 100644 index 00000000..e02759b2 --- /dev/null +++ b/crypto/smc-envelope/WalletInterface.cpp @@ -0,0 +1,80 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ +#include "WalletInterface.h" + +namespace ton { +td::Result WalletInterface::get_balance(td::uint64 account_balance, td::uint32 now) const { + return TRY_VM([&]() -> td::Result { + Answer answer = this->run_get_method(Args().set_method_id("balance").set_balance(account_balance).set_now(now)); + if (!answer.success) { + return td::Status::Error("balance get method failed"); + } + return static_cast(answer.stack.write().pop_long()); + }()); +} + +td::Result WalletInterface::get_public_key() const { + return GenericAccount::get_public_key(*this); +}; + +td::Result WalletInterface::get_seqno() const { + return GenericAccount::get_seqno(*this); +} + +td::Result WalletInterface::get_wallet_id() const { + return GenericAccount::get_wallet_id(*this); +} + +td::Result> WalletInterface::get_init_message(const td::Ed25519::PrivateKey &private_key, + td::uint32 valid_until) const { + return make_a_gift_message(private_key, valid_until, {}); +} + +td::Ref WalletInterface::create_int_message(const Gift &gift) { + vm::CellBuilder cbi; + GenericAccount::store_int_message(cbi, gift.destination, gift.gramms < 0 ? 0 : gift.gramms); + if (gift.init_state.not_null()) { + cbi.store_ones(2); + cbi.store_ref(gift.init_state); + } else { + cbi.store_zeroes(1); + } + store_gift_message(cbi, gift); + return cbi.finalize(); +} +void WalletInterface::store_gift_message(vm::CellBuilder &cb, const Gift &gift) { + if (gift.body.not_null()) { + auto body = vm::load_cell_slice(gift.body); + if (cb.can_extend_by(1 + body.size(), body.size_refs())) { + CHECK(cb.store_zeroes_bool(1) && cb.append_cellslice_bool(body)); + } else { + CHECK(cb.store_ones_bool(1) && cb.store_ref_bool(gift.body)); + } + return; + } + + cb.store_zeroes(1); + if (gift.is_encrypted) { + cb.store_long(EncryptedCommentOp, 32); + } else { + cb.store_long(0, 32); + } + vm::CellString::store(cb, gift.message, 35 * 8).ensure(); +} +} // namespace ton diff --git a/crypto/smc-envelope/WalletInterface.h b/crypto/smc-envelope/WalletInterface.h new file mode 100644 index 00000000..c4e1f270 --- /dev/null +++ b/crypto/smc-envelope/WalletInterface.h @@ -0,0 +1,138 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ +#pragma once + +#include "td/utils/common.h" +#include "Ed25519.h" +#include "block/block.h" +#include "block/block-parse.h" +#include "vm/cells/CellString.h" + +#include "SmartContract.h" +#include "SmartContractCode.h" +#include "GenericAccount.h" + +#include + +namespace ton { +class WalletInterface : public SmartContract { + public: + static constexpr uint32_t EncryptedCommentOp = 0x2167da4b; + struct Gift { + block::StdAddress destination; + td::int64 gramms; + td::int32 send_mode{-1}; + + bool is_encrypted{false}; + std::string message; + + td::Ref body; + td::Ref init_state; + }; + struct DefaultInitData { + td::SecureString public_key; + td::uint32 wallet_id{0}; + td::uint32 seqno{0}; + DefaultInitData() = default; + DefaultInitData(td::Slice key, td::uint32 wallet_id) : public_key(key), wallet_id(wallet_id) { + } + }; + + WalletInterface(State state) : SmartContract(std::move(state)) { + } + + virtual ~WalletInterface() { + } + + virtual size_t get_max_gifts_size() const = 0; + virtual size_t get_max_message_size() const = 0; + virtual td::Result> make_a_gift_message(const td::Ed25519::PrivateKey &private_key, + td::uint32 valid_until, td::Span gifts) const = 0; + + virtual td::Result get_seqno() const; + virtual td::Result get_wallet_id() const; + virtual td::Result get_balance(td::uint64 account_balance, td::uint32 now) const; + virtual td::Result get_public_key() const; + + td::Result> get_init_message(const td::Ed25519::PrivateKey &private_key, + td::uint32 valid_until = std::numeric_limits::max()) const; + + static td::Ref create_int_message(const Gift &gift); + + private: + static void store_gift_message(vm::CellBuilder &cb, const Gift &gift); +}; + +template +class WalletBase : public WalletInterface { + public: + using Traits = TraitsT; + using InitData = typename Traits::InitData; + + explicit WalletBase(State state) : WalletInterface(std::move(state)) { + } + + size_t get_max_gifts_size() const override { + return Traits::max_gifts_size; + } + size_t get_max_message_size() const override { + return Traits::max_message_size; + } + + static td::Ref create(State state) { + return td::Ref(true, std::move(state)); + } + static td::Ref get_init_code(int revision) { + return SmartContractCode::get_code(get_code_type(), revision); + }; + static State get_init_state(int revision, const InitData &init_data) { + return {get_init_code(revision), WalletT::get_init_data(init_data)}; + } + static SmartContractCode::Type get_code_type() { + return Traits::code_type; + } + static td::optional guess_revision(const vm::Cell::Hash &code_hash) { + for (auto revision : ton::SmartContractCode::get_revisions(get_code_type())) { + auto code = get_init_code(revision); + if (code->get_hash() == code_hash) { + return revision; + } + } + return {}; + } + static td::Span get_revisions() { + return ton::SmartContractCode::get_revisions(get_code_type()); + } + static td::optional guess_revision(block::StdAddress &address, const InitData &init_data) { + for (auto revision : get_revisions()) { + if (WalletT(get_init_state(revision, init_data)).get_address(address.workchain) == address) { + return revision; + } + } + return {}; + } + static td::Ref create(const InitData &init_data, int revision) { + return td::Ref(true, State{get_init_code(revision), WalletT::get_init_data(init_data)}); + } + CntObject *make_copy() const override { + return new WalletT(get_state()); + } +}; + +} // namespace ton diff --git a/crypto/smc-envelope/WalletV3.cpp b/crypto/smc-envelope/WalletV3.cpp index db39c725..35a302e6 100644 --- a/crypto/smc-envelope/WalletV3.cpp +++ b/crypto/smc-envelope/WalletV3.cpp @@ -14,10 +14,11 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "WalletV3.h" #include "GenericAccount.h" +#include "SmartContractCode.h" #include "vm/boc.h" #include "vm/cells/CellString.h" @@ -26,105 +27,60 @@ #include namespace ton { -td::Ref WalletV3::get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept { - auto code = get_init_code(); - auto data = get_init_data(public_key, wallet_id); - return GenericAccount::get_init_state(std::move(code), std::move(data)); -} - -td::Ref WalletV3::get_init_message(const td::Ed25519::PrivateKey& private_key, - td::uint32 wallet_id) noexcept { - td::uint32 seqno = 0; - td::uint32 valid_until = std::numeric_limits::max(); - auto signature = private_key - .sign(vm::CellBuilder() - .store_long(wallet_id, 32) - .store_long(valid_until, 32) - .store_long(seqno, 32) - .finalize() - ->get_hash() - .as_slice()) - .move_as_ok(); - return vm::CellBuilder() - .store_bytes(signature) - .store_long(wallet_id, 32) - .store_long(valid_until, 32) - .store_long(seqno, 32) - .finalize(); -} - -td::Ref WalletV3::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, - td::uint32 seqno, td::uint32 valid_until, td::int64 gramms, - td::Slice message, const block::StdAddress& dest_address) noexcept { - td::int32 send_mode = 3; - if (gramms == -1) { - gramms = 0; - send_mode += 128; - } +td::Result> WalletV3::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, + td::uint32 valid_until, td::Span gifts) const { + CHECK(gifts.size() <= get_max_gifts_size()); + TRY_RESULT(seqno, get_seqno()); + TRY_RESULT(wallet_id, get_wallet_id()); vm::CellBuilder cb; - GenericAccount::store_int_message(cb, dest_address, gramms); - cb.store_bytes("\0\0\0\0", 4); - vm::CellString::store(cb, message, 35 * 8).ensure(); - auto message_inner = cb.finalize(); + cb.store_long(wallet_id, 32).store_long(valid_until, 32).store_long(seqno, 32); - auto message_outer = vm::CellBuilder() - .store_long(wallet_id, 32) - .store_long(valid_until, 32) - .store_long(seqno, 32) - .store_long(send_mode, 8) - .store_ref(message_inner) - .finalize(); + for (auto& gift : gifts) { + td::int32 send_mode = 3; + if (gift.gramms == -1) { + send_mode += 128; + } + if (gift.send_mode > -1) { + send_mode = gift.send_mode; + } + cb.store_long(send_mode, 8).store_ref(create_int_message(gift)); + } + + auto message_outer = cb.finalize(); auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok(); return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize(); } -td::Ref WalletV3::get_init_code() noexcept { - static auto res = [] { - auto serialized_code = td::base64_decode( - "te6ccgEBAQEAYgAAwP8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/" - "9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA==") - .move_as_ok(); - return vm::std_boc_deserialize(serialized_code).move_as_ok(); - }(); - return res; -} - -vm::CellHash WalletV3::get_init_code_hash() noexcept { - return get_init_code()->get_hash(); -} - -td::Ref WalletV3::get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept { +td::Ref WalletV3::get_init_data(const InitData& init_data) noexcept { return vm::CellBuilder() - .store_long(0, 32) - .store_long(wallet_id, 32) - .store_bytes(public_key.as_octet_string()) + .store_long(init_data.seqno, 32) + .store_long(init_data.wallet_id, 32) + .store_bytes(init_data.public_key) .finalize(); } -td::Result WalletV3::get_seqno() const { - return TRY_VM(get_seqno_or_throw()); -} - -td::Result WalletV3::get_seqno_or_throw() const { - if (state_.data.is_null()) { - return 0; - } - //FIXME use get method - return static_cast(vm::load_cell_slice(state_.data).fetch_ulong(32)); -} - td::Result WalletV3::get_wallet_id() const { - return TRY_VM(get_wallet_id_or_throw()); + return TRY_VM([&]() -> td::Result { + if (state_.data.is_null()) { + return 0; + } + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(32); + return static_cast(cs.fetch_ulong(32)); + }()); } -td::Result WalletV3::get_wallet_id_or_throw() const { - if (state_.data.is_null()) { - return 0; - } - //FIXME use get method - auto cs = vm::load_cell_slice(state_.data); - cs.skip_first(32); - return static_cast(cs.fetch_ulong(32)); +td::Result WalletV3::get_public_key() const { + return TRY_VM([&]() -> td::Result { + if (state_.data.is_null()) { + return td::Status::Error("data is null"); + } + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(64); + td::SecureString res(td::Ed25519::PublicKey::LENGTH); + cs.fetch_bytes(res.as_mutable_slice().ubegin(), td::narrow_cast(res.size())); + return td::Ed25519::PublicKey(std::move(res)); + }()); } } // namespace ton diff --git a/crypto/smc-envelope/WalletV3.h b/crypto/smc-envelope/WalletV3.h index a6e4162d..926e7ffb 100644 --- a/crypto/smc-envelope/WalletV3.h +++ b/crypto/smc-envelope/WalletV3.h @@ -14,37 +14,160 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "vm/cells.h" #include "Ed25519.h" #include "block/block.h" #include "vm/cells/CellString.h" namespace ton { -class WalletV3 : ton::SmartContract { - public: - explicit WalletV3(State state) : ton::SmartContract(std::move(state)) { - } + +struct WalletV3Traits { + using InitData = WalletInterface::DefaultInitData; + static constexpr unsigned max_message_size = vm::CellString::max_bytes; - static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept; - static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id) noexcept; - static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, - td::uint32 seqno, td::uint32 valid_until, td::int64 gramms, - td::Slice message, const block::StdAddress& dest_address) noexcept; + static constexpr unsigned max_gifts_size = 4; + static constexpr auto code_type = SmartContractCode::WalletV3; +}; - static td::Ref get_init_code() noexcept; - static vm::CellHash get_init_code_hash() noexcept; - static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept; +class WalletV3 : public WalletBase { + public: + explicit WalletV3(State state) : WalletBase(std::move(state)) { + } + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override; + static td::Ref get_init_data(const InitData& init_data) noexcept; - td::Result get_seqno() const; - td::Result get_wallet_id() const; - - private: - td::Result get_seqno_or_throw() const; - td::Result get_wallet_id_or_throw() const; + // can't use get methods for compatibility with old revisions + td::Result get_wallet_id() const override; + td::Result get_public_key() const override; +}; +} // namespace ton + +namespace ton { + +struct RestrictedWalletTraits { + struct InitData { + td::SecureString init_key; + td::SecureString main_key; + td::uint32 wallet_id{0}; + }; + + static constexpr unsigned max_message_size = vm::CellString::max_bytes; + static constexpr unsigned max_gifts_size = 4; + static constexpr auto code_type = SmartContractCode::RestrictedWallet; +}; + +class RestrictedWallet : public WalletBase { + public: + struct Config { + td::uint32 start_at{0}; + std::vector> limits; + }; + + explicit RestrictedWallet(State state) : WalletBase(std::move(state)) { + } + + td::Result get_config() const { + return TRY_VM([this]() -> td::Result { + auto cs = vm::load_cell_slice(get_state().data); + Config config; + td::Ref dict_root; + auto ok = cs.advance(32 + 32 + 256) && cs.fetch_uint_to(32, config.start_at) && cs.fetch_maybe_ref(dict_root); + vm::Dictionary dict(std::move(dict_root), 32); + dict.check_for_each([&](auto cs, auto ptr, auto ptr_bits) { + auto r_seconds = td::narrow_cast_safe(dict.key_as_integer(ptr, true)->to_long()); + if (r_seconds.is_error()) { + ok = false; + return ok; + } + td::uint64 value; + ok &= smc::unpack_grams(cs, value); + config.limits.emplace_back(r_seconds.ok(), value); + return ok; + }); + if (!ok) { + return td::Status::Error("Can't parse config"); + } + std::sort(config.limits.begin(), config.limits.end()); + return config; + }()); + } + + static td::Ref get_init_data(const InitData& init_data) { + vm::CellBuilder cb; + cb.store_long(0, 32); + cb.store_long(init_data.wallet_id, 32); + CHECK(init_data.init_key.size() == 32); + CHECK(init_data.main_key.size() == 32); + cb.store_bytes(init_data.init_key.as_slice()); + cb.store_bytes(init_data.main_key.as_slice()); + return cb.finalize(); + } + + td::Result> get_init_message(const td::Ed25519::PrivateKey& init_private_key, + td::uint32 valid_until, const Config& config) const { + vm::CellBuilder cb; + TRY_RESULT(seqno, get_seqno()); + TRY_RESULT(wallet_id, get_wallet_id()); + LOG(ERROR) << "seqno: " << seqno << " wallet_id: " << wallet_id; + if (seqno != 0) { + return td::Status::Error("Wallet is already inited"); + } + + cb.store_long(wallet_id, 32); + cb.store_long(valid_until, 32); + cb.store_long(seqno, 32); + + cb.store_long(config.start_at, 32); + vm::Dictionary dict(32); + + auto add = [&](td::int32 till, td::uint64 value) { + auto key = dict.integer_key(td::make_refint(till), 32, true); + vm::CellBuilder gcb; + block::tlb::t_Grams.store_integer_value(gcb, td::BigInt256(value)); + dict.set_builder(key.bits(), 32, gcb); + }; + for (auto limit : config.limits) { + add(limit.first, limit.second); + } + cb.store_maybe_ref(dict.get_root_cell()); + + auto message_outer = cb.finalize(); + auto signature = init_private_key.sign(message_outer->get_hash().as_slice()).move_as_ok(); + return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize(); + } + + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + CHECK(gifts.size() <= Traits::max_gifts_size); + + vm::CellBuilder cb; + TRY_RESULT(seqno, get_seqno()); + TRY_RESULT(wallet_id, get_wallet_id()); + if (seqno == 0) { + return td::Status::Error("Wallet is not inited yet"); + } + cb.store_long(wallet_id, 32); + cb.store_long(valid_until, 32); + cb.store_long(seqno, 32); + + for (auto& gift : gifts) { + td::int32 send_mode = 3; + if (gift.gramms == -1) { + send_mode += 128; + } + cb.store_long(send_mode, 8).store_ref(create_int_message(gift)); + } + + auto message_outer = cb.finalize(); + auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok(); + return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize(); + } }; } // namespace ton diff --git a/crypto/smc-envelope/WalletV4.cpp b/crypto/smc-envelope/WalletV4.cpp new file mode 100644 index 00000000..738fa9c7 --- /dev/null +++ b/crypto/smc-envelope/WalletV4.cpp @@ -0,0 +1,71 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#include "WalletV4.h" +#include "GenericAccount.h" +#include "SmartContractCode.h" + +#include "vm/boc.h" +#include "vm/cells/CellString.h" +#include "td/utils/base64.h" + +#include + +namespace ton { +td::Result> WalletV4::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, + td::uint32 valid_until, td::Span gifts) const { + CHECK(gifts.size() <= get_max_gifts_size()); + TRY_RESULT(seqno, get_seqno()); + TRY_RESULT(wallet_id, get_wallet_id()); + vm::CellBuilder cb; + cb.store_long(wallet_id, 32).store_long(valid_until, 32).store_long(seqno, 32); + cb.store_long(0, 8); // The only difference with wallet-v3 + + for (auto& gift : gifts) { + td::int32 send_mode = 3; + if (gift.gramms == -1) { + send_mode += 128; + } + if (gift.send_mode > -1) { + send_mode = gift.send_mode; + } + cb.store_long(send_mode, 8).store_ref(create_int_message(gift)); + } + + auto message_outer = cb.finalize(); + auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok(); + return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize(); +} + +td::Ref WalletV4::get_init_data(const InitData& init_data) noexcept { + return vm::CellBuilder() + .store_long(init_data.seqno, 32) + .store_long(init_data.wallet_id, 32) + .store_bytes(init_data.public_key) + .store_zeroes(1) // plugins dict + .finalize(); +} + +td::Result WalletV4::get_wallet_id() const { + return TRY_VM([&]() -> td::Result { + auto answer = run_get_method("get_subwallet_id"); + if (!answer.success) { + return td::Status::Error("get_subwallet_id get method failed"); + } + return static_cast(answer.stack.write().pop_long_range(std::numeric_limits::max())); + }()); +} +} // namespace ton diff --git a/tonlib/tonlib/TestWallet.h b/crypto/smc-envelope/WalletV4.h similarity index 53% rename from tonlib/tonlib/TestWallet.h rename to crypto/smc-envelope/WalletV4.h index ef726b55..721e8103 100644 --- a/tonlib/tonlib/TestWallet.h +++ b/crypto/smc-envelope/WalletV4.h @@ -13,28 +13,34 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - - Copyright 2017-2019 Telegram Systems LLP */ #pragma once +#include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "vm/cells.h" #include "Ed25519.h" #include "block/block.h" -#include "CellString.h" +#include "vm/cells/CellString.h" + +namespace ton { + +struct WalletV4Traits { + using InitData = WalletInterface::DefaultInitData; -namespace tonlib { -class TestWallet { - public: static constexpr unsigned max_message_size = vm::CellString::max_bytes; - static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key) noexcept; - static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept; - static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, - td::int64 gramms, td::Slice message, - const block::StdAddress& dest_address) noexcept; - - static td::Ref get_init_code() noexcept; - static vm::CellHash get_init_code_hash() noexcept; - static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key) noexcept; + static constexpr unsigned max_gifts_size = 4; + static constexpr auto code_type = SmartContractCode::WalletV4; }; -} // namespace tonlib + +class WalletV4 : public WalletBase { + public: + explicit WalletV4(State state) : WalletBase(std::move(state)) { + } + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override; + static td::Ref get_init_data(const InitData& init_data) noexcept; + + td::Result get_wallet_id() const override; +}; +} // namespace ton \ No newline at end of file diff --git a/crypto/test/Ed25519.cpp b/crypto/test/Ed25519.cpp index 5c263acf..45e8891f 100644 --- a/crypto/test/Ed25519.cpp +++ b/crypto/test/Ed25519.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "crypto/Ed25519.h" #include "td/utils/logging.h" diff --git a/crypto/test/fift.cpp b/crypto/test/fift.cpp index 694dc7a8..3f370ee5 100644 --- a/crypto/test/fift.cpp +++ b/crypto/test/fift.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "fift/words.h" #include "fift/Fift.h" @@ -33,9 +33,14 @@ std::string load_test(std::string name) { return td::read_file_str(current_dir() + "fift/" + name).move_as_ok(); } -td::Status run_fift(std::string name, bool preload_fift = true) { - TRY_RESULT(res, fift::mem_run_fift(load_test(name))); - REGRESSION_VERIFY(res.output); +td::Status run_fift(std::string name, bool expect_error = false) { + auto res = fift::mem_run_fift(load_test(name)); + if (expect_error) { + res.ensure_error(); + return td::Status::OK(); + } + res.ensure(); + REGRESSION_VERIFY(res.ok().output); return td::Status::OK(); } @@ -79,7 +84,7 @@ TEST(Fift, testvmprog) { run_fift("testvmprog.fif"); } TEST(Fift, bug) { - run_fift("bug.fif"); + run_fift("bug.fif", true); } TEST(Fift, contfrac) { run_fift("contfrac.fif"); @@ -110,3 +115,55 @@ TEST(Fift, test_sort) { TEST(Fift, test_sort2) { run_fift("sort2.fif"); } + +TEST(Fift, test_hmap) { + run_fift("hmap.fif"); +} + +TEST(Fift, test_disasm) { + run_fift("disasm.fif"); +} + +TEST(Fift, test_fiftext) { + run_fift("fift-ext.fif"); +} + +TEST(Fift, test_namespaces) { + run_fift("namespaces.fif"); +} + +TEST(Fift, test_asm_nested_program) { + run_fift("asm-nested-program.fif"); +} + +TEST(Fift, test_adddiv) { + run_fift("adddiv.fif"); +} + +TEST(Fift, test_tvm_runvm) { + run_fift("tvm_runvm.fif"); +} + +TEST(Fift, test_hash_ext) { + run_fift("hash_ext.fif"); +} + +TEST(Fift, test_deep_stack_ops) { + run_fift("deep_stack_ops.fif"); +} + +TEST(Fift, test_rist255) { + run_fift("rist255.fif"); +} + +TEST(Fift, test_bls) { + run_fift("bls.fif"); +} + +TEST(Fift, test_bls_ops) { + run_fift("bls_ops.fif"); +} + +TEST(Fift, test_levels) { + run_fift("levels.fif"); +} diff --git a/crypto/test/fift/adddiv.fif b/crypto/test/fift/adddiv.fif new file mode 100644 index 00000000..96be6525 --- /dev/null +++ b/crypto/test/fift/adddiv.fif @@ -0,0 +1,144 @@ +{ + =: ans-r =: ans-q =: mode + =: z =: w =: y =: x + ."MULADDDIVMOD " @' x . @' y . @' w . @' z . @' mode . ."= " @' ans-q . @' ans-r . cr + @' x @' y @' w @' z + abort"Incorrect r" + @' ans-q <> abort"Incorrect q" +} : test-muladddivmod + +{ + =: ans-r =: ans-q =: mode + =: y =: w =: x + ."ADDDIVMOD " @' x . @' w . @' y . @' mode . ."= " @' ans-q . @' ans-r . cr + @' x @' w @' y + abort"Incorrect r" + @' ans-q <> abort"Incorrect q" +} : test-adddivmod + +{ + =: ans-r =: ans-q =: mode + =: z =: w =: y =: x + ."SHLADDDIVMOD " @' x . @' y . @' w . @' z . @' mode . ."= " @' ans-q . @' ans-r . cr + @' x @' w @' z @' y + abort"Incorrect r" + @' ans-q <> abort"Incorrect q" +} : test-shladddivmod + +{ + =: ans-r =: ans-q =: mode + =: z =: w =: y =: x + ."MULADDSHRMOD " @' x . @' y . @' w . @' z . @' mode . ."= " @' ans-q . @' ans-r . cr + @' x @' y @' w @' z + abort"Incorrect r" + @' ans-q <> abort"Incorrect q" +} : test-muladdshrmod + +729 -212 552 0 0 517 test-adddivmod +823 -139 -918 1 -1 -234 test-adddivmod +-470 977 47 2 11 -10 test-adddivmod +-5 -171 880 0 -1 704 test-adddivmod +605 699 -379 1 -3 167 test-adddivmod +982 -24 -267 2 -3 157 test-adddivmod +194 826 859 0 1 161 test-adddivmod +-553 33 -715 1 1 195 test-adddivmod +-423 -714 547 2 -2 -43 test-adddivmod +-806 266 637 0 -1 97 test-adddivmod +-487 863 90 1 4 16 test-adddivmod +444 659 232 2 5 -57 test-adddivmod +847 -700 -365 -798 0 743 -351 test-muladddivmod +494 -849 840 741 1 -565 99 test-muladddivmod +400 -324 -34 146 2 -887 -132 test-muladddivmod +-794 -276 -111 -353 0 -621 -180 test-muladddivmod +251 311 869 -582 1 -136 -222 test-muladddivmod +979 131 -24 -94 2 -1364 9 test-muladddivmod +772 67 -467 -873 0 -59 -250 test-muladddivmod +648 881 123 875 1 653 -364 test-muladddivmod +-972 -809 473 720 2 1093 -139 test-muladddivmod +-184 454 689 607 0 -137 312 test-muladddivmod +368 280 -998 253 1 403 83 test-muladddivmod +10 695 776 -318 2 -24 94 test-muladddivmod +-501 5 441 782 0 -20 49 test-shladddivmod +-872 3 878 162 1 -38 58 test-shladddivmod +-546 3 645 981 2 -3 -780 test-shladddivmod +-709 8 -83 -814 0 223 -65 test-shladddivmod +-836 5 792 40 1 -649 0 test-shladddivmod +910 7 -777 -383 2 -302 37 test-shladddivmod +128 4 447 -745 0 -4 -485 test-shladddivmod +121 5 668 888 1 5 100 test-shladddivmod +106 3 973 637 2 3 -90 test-shladddivmod +235 8 203 -411 0 -147 -54 test-shladddivmod +-89 1 221 634 1 0 43 test-shladddivmod +-212 5 178 -505 2 14 464 test-shladddivmod +-406 -624 -613 2 0 63182 3 test-muladdshrmod +-933 254 344 4 1 -14790 2 test-muladdshrmod +-25 -859 -817 10 2 21 -846 test-muladdshrmod +551 -734 795 2 0 -100910 1 test-muladdshrmod +891 -921 725 1 1 -409943 0 test-muladdshrmod +839 432 890 8 2 1420 -182 test-muladdshrmod +399 -199 715 8 0 -308 162 test-muladdshrmod +-436 68 662 3 1 -3623 -2 test-muladdshrmod +739 -560 833 10 2 -403 -335 test-muladdshrmod +207 690 945 6 0 2246 31 test-muladdshrmod +187 -437 -78 7 1 -639 -5 test-muladdshrmod +352 313 434 5 2 3457 -14 test-muladdshrmod +25527586395537789869570741399081402682256851060036756777914400562788085574414 -25113160904436633803354228324572450480033217030010944063278205598870671865288 -2256017777568044393040721989849804550406163786581604155567013427587080747425 0 -1 -1841592286466888326824208915340852348182529756555791440930818463669667038299 test-adddivmod +-32640246231411323106115073889225624429888478001529402430751550293198007894765 -37905632746827282747258884343685092209247121139851002852469398603088743361022 18753000550171967665955942657615296985943924316121913951069685581208211144632 1 -4 4466123222449264810449812397550471304640098123107250521057793428546093322741 test-adddivmod +-95549375212741069607980980863332591640631650412403896229315709698307081396373 106240530356563762252550465652293166037855203388135409179201250774369290711413 63027891874179850739525204890840444316414650119364284762072976150516424705738 2 1 -52336736730357158094955720101879869919191097143632771812187435074454215390698 test-adddivmod +51890526097631236478215460328003213527162540175278758108042092328342498521783 19183851048961131310064308531261717471288729454763519442604661504489273629484 37741726035775552009535280639145255965240492091225040562231509956705484553131 0 1 33332651110816815778744488220119675033210777538817236988415243876126287598136 test-adddivmod +94071222795948162024492074951491627130123473883831806178173544936543536671735 -79181277644495598779765208777007083667700785694819686730856530041854640922750 101162201634522410327363362416275476419529310370084833033537453927088908146625 1 0 14889945151452563244726866174484543462422688189012119447317014894688895748985 test-adddivmod +106849434942283600005926697698751951080751408318517871682753577855332568068854 -24045207490958235002714107597178464324559754040415520751984110097220649552695 74679027778960833059271215597415768445807741415021004423546293120031915119911 2 2 -66553828106596301115329841093258050135423828551939657916323118481951911723663 test-adddivmod +-111083603622141356205460042548478779196244111142279948844255415507465496179136 75885719295648796212066209648237285368659643103523469813991536525258102456646 17487288729375302809394363552184340106515810164188804948959885094558256876961 0 -3 17263981861633348434789257756311526491962962453809935816615776301467376908393 test-adddivmod +33525382765608498977714984645580937896464574678957613892455278004374765395791 -31203451453013366071092485074296252911543265223612191519873428434459391093162 8205361318951100784724737477089194897616580930791932431609075219761273143789 1 0 2321931312595132906622499571284684984921309455345422372581849569915374302629 test-adddivmod +-75999952514508567055051892339604431790575024284443576993806836353012574839934 60096525995012658905135417908615298741753181036991250015243181812480959008245 25156241733999421479698222967097698882671132665627779117308277826680836574301 2 0 -15903426519495908149916474430989133048821843247452326978563654540531615831689 test-adddivmod +-25810038415830521876908334245348683093953748145428320530928049623042163542952 13768870762468614895982523795103333718119339794093637483718383357794846902545 -23455630873759864343226329762160671724884569294252944494055101268304334927469 0 0 -12041167653361906980925810450245349375834408351334683047209666265247316640407 test-adddivmod +65295636229319469142829758722387755590067603179313024098599396192036031682932 -14508864012052681942310481748034005897814022127985455644382059031122725256639 101258243423658080118042170583591218535452530372858366205652259477702813508611 1 1 -50471471206391292917522893609237468843198949321530797751434922316789507082318 test-adddivmod +-9782778804673764937901196234199348720011483228792837975519798751976472498576 97723276666278794523978327321824380269179391405498018632142147588187660001006 -49342195947431856333198825767811410708861002865285674269877261690183407963360 2 -1 38598301914173173252878305319813620840306905311419506386745087146027779539070 test-adddivmod +69314796062889467182180663903322273169679119368960545268948720263471816924632 93795938161576335898249305761803240528541213785951419531969609595386829401314 -110346049538998400484346689828188917177083570083289016450597131164930681126587 81149113889296322255375738811288869158926250513403232273765830197055460701260 0 80117280566567035416625919712742621817262121093084845174084285957987697395276 42036691045037437094861959671408390901385540330966807308270651575596167392101 test-muladddivmod +-21971450167728355573106502799062118429867408154846289128369107182511643511483 45393555019045493054213057422940072801743824102220594624535868754509418047929 60641652588698494073802822494164156953515858330346793601084497228687965671868 65539824510463171124453489789915597533293103308691110446148835625201340366598 1 -15217651854984270515658273738098718647212153016787679186857576358263739302058 -29371413441460037863394670149687230435838766165616129771684695789862414338155 test-muladddivmod +73634008615213489758113683559933051942050608504790565710917645985405082665491 -80155625965090770138821073870266428949137773405175831429923552413761952441361 -38999294453130493506999209141746004309873017414282530140731810289885759492802 -107314816556101242244916895079818037889329087681097150645968648449136338086659 2 54998743344874712983373052080844956119323047070936199528619384334263523872368 13082689556615721830015990667012489975468766353417550442844496303849124272459 test-muladddivmod +68441985454806782074858763992903374541757416422223520705458179994903492447753 -61267036145362164840470993649039311748277914273628051012777836672446118682406 88886725386626308975645991554561163959863903429407091061340939714760192334434 -55192455525382179382269200386360902483303866088141254649003462525396159498006 0 75974833096374772090455746107157391488128832802648627140447901963962285642644 -35281244576421605907379124849976697589234314365299818483123805214351816431420 test-muladddivmod +13991345100229597325861930387540671073560101799369013558248988792137436010794 32261366615236683296917985913895811707233003589900325435836147185374329094359 -35081305217709330815376010427375742966580511272499524644418734096933080609103 34617350357576952655827361801899194862477556493235499468927833026852355915741 1 13039123706936329824826187692520278079826111394035475962656812793878481550617 15501972367754182150585081456806290271752173495030969288120604381172009339746 test-muladddivmod +54599312873997957959553141264956574941720752239162126863069720527738322459321 743784849758283770307843473486531088082399957077059004634332280604041992918 -82452755014987554458459818028526388807440160648566275282022897198718871469325 38291925157759929750140152840087355208836284729075633154257868030142639468974 2 1060540611514863450709563259060420401132951143137987621813620016383521959845 -25576601802989288764264589884413515994814497538433618559906725414501597729677 test-muladddivmod +-33715442018098691670021742265266162391174212035380017502572244718418822715623 104887034421874269674272525533704680767916752281317133765280341780898862714955 40882837978738774675184345709913504579827188391303805465905655071457659357026 56761521466376613401789060160037629513193949115508135629309104361041554577464 0 -62301232175317956930653590924038385722281926697603233811485244969539670459488 12675521639977150682490928392886170001456429479565150104484728889568554893493 test-muladddivmod +-109706830967107194245468623157302140481713559442721649770259230494532080435232 91091244832469326414116901981654442590329304825709696121493056753007506269108 25586919878947735367325917367337541421822321271842417480093738776553133015696 92030412800134708014057990497075561862656985932946512327644864855223778050176 1 -108587275612051383813143325045450855254642049763863834262763340362417287376196 14236417728543624555544633487823994857557292455110879135165049494799852613136 test-muladddivmod +-55349740795363582739414701155082958898639391946026926848599395086577482106561 5153084015148055450516513461735201914781593572521892141092888805185704024388 -65052157988798026904166625513790079385321361518951356077015576483034859919945 -102114306233907646655227677599549837347948442788318424043410967802211179284463 2 2793162633665004752332533372431053166813934025727573730237936408503456830715 70263858264069476764017250048942592796103230166090146586104085945931001951432 test-muladddivmod +-39413219476407785933419341321985162122574957075787517059529471719124009278433 -11397560582871643424486223364245599528148517163558642929375020427946855266926 72776685568498650781344357948907544676236244067805626443550533574512635929751 58457602475162312341105487265860863804074002837155658624106642883511552164475 0 7684450571493046035065075993797738633610800975055222799851151005079197271998 25447845742266018765952184307141986304437190746678223193451015171399298065659 test-muladddivmod +28902743859707224532426872535592986589584185136999529704427764834614586536146 30118285526375438691436500554083725474584606124188443987423181314058619658259 -112947693515558587819874917596387701093051538659457576158697245759111287251112 -20731780512952622217494754630018335482763668682808767673633845662171371751908 1 -41988727958917592867685340883133510770733630298849576855386410060863664014757 6354934151364300917311007564393521634968794138377927567288802927545928772346 test-muladddivmod +25071799008788838242365403068059094326298333378650328613993357620568407227394 -27895932377486960662691379394722126815225783254727720878708826335618109428175 -34783759350459778495483474263506062490621190762057336926096642777973708891199 -46562500545170684944465306233470472124382229989733955650634447552911654749066 2 15020696945874349483297599474537626462120625640258473403957682700588115661859 6123529058588613987393724235023234231421775050193606259696039938834907756545 test-muladddivmod +4637334961015630956409530377119128659169399268652032534941609159910059786532 174 -12797497544848087213503981396389428116533825336325463794725594822879690679136 93018556588730187003792638630891717650672350374200759319903911998812002342061 0 1193763005785115313715849486131901382204829468191839 86009298512437248420661887032254663876544001080814196765583913598468234097773 test-shladddivmod +63737576485202419511398757422488387344373087691300709744039454443359600742212 17 4434935617055335860270673726237887676208015615002198868506400573861444556411 2395017004775767547761160570739886212190100215618999647636254869255450984262 1 3488166 -825476601398469938219841341785340947550825081895127156723070705988041475817 test-shladddivmod +-21264162232365890547240808872203618534857207092355857230053229357857057625546 79 83558436280879393517752968647441646810997015153366115449062116401734553793990 -19667935035506461778339921177556508690003173951785474262950610395510252887673 2 653520430812207296424074 11997579569142787892139960093229284888868038054542931791709721193377458047744 test-shladddivmod +50327589756875372367121062573124726147569135695110368165104387616199145495643 159 -56582804136162261915738821952650758787670281453734279574878610404888378091325 41794171917267054297819695791653745099726306264896745093211576006595621865899 0 879953489426631344181134857312933722690648995542 7479251035838043422275284357505479089411783830647758441772426265279908213201 test-shladddivmod +-6790346796221361248085433832482913268295667476610056123099942839800062619360 99 -30655005682505720678929136056715874580886440550632733046480550204329894400037 107280989788626394506897862107199815360330755655673306259970134464147378777352 1 -40117951973353319220033820651 15419963034230519057842831193590832882628829971176441336761525930488437456435 test-shladddivmod +19046177038646178344769825269053799430308794840550024608332777213474587282651 33 3489319764096497921939317127917520109408688444737580375038679679318170964558 32230702261963164880326384192551720153568540700317784363615498612027586166919 2 5076073543 -31149228513135397726284948367312236333921327766221169520773046236565040396067 test-shladddivmod +-110976730374012300153801377038320854184737548404302035494280064993634691387718 177 62599745911697906087525399889373248851994389233059198427444302208529620264342 2939901734097150061182352721440114362417026502565316012346244994332394908737 0 -7231166201303287776306953132498157445481985675557698778 861631401006029279966827198914714166628754059441816200263218068486981128432 test-shladddivmod +31852390609689123349381354058350609996064615917952716537339963695989162024629 72 -96928192014181974842953135946922776102933173463512906699090906721752057028243 92656901870085089608820151790239083253300815942245860126341275046871599043017 1 1623394035183378246274 -40717467475217300542569194922405492138512784779596821986363220696851482878117 test-shladddivmod +108325270025037161417115474167849009240382377052459846115851642255437288685912 79 -47995506721499545563379889382854078032364619862028413664240909132079634758006 -6166659869789359796343211847537623762770094929722294841335533066616771405539 2 -10618164339787666672859802 2975209691732235673066443530343246725711182217991112340969803628522007294972 test-shladddivmod +-102030576317847732070693755030275793160072651528715762716678938494538431797856 162 92259248995288252713115460931891173987977282785918620735892298001910599707421 93069691103831234597581468228357432179178680731051507965844021657079407259554 0 -6408868561946334361312419431284710070541388837754 30090871674656727061174631170248864679275378922263881716308993939778213471313 test-shladddivmod +109919709354382523017092140618288318375226272062645130510340454988137017880658 32 11403889262848182921779993946145880893942415285082669079885795084154364295755 -35203479084324376157414643218816253627488669490337878341936871887579427112193 1 -13410650571 -9079152369425295070217928786983170793628233088577531145426537161436231255680 test-shladddivmod +32604320264365535336905506430047200856461747148561589244458238442999859570507 87 53600018835628474925583604580250089573381703851509998750334950185805444447855 -13714304553912094478086374486990066534941196173664347737943079140839510240 2 -367884070882655630785891877843 6730574288892465511219887918570728429135366732586091870751580223301793231 test-shladddivmod +-6528863440223970638249234714151581865561638514865295384371332680662061970029 -36913540142944821177073496030226577063991271774591429797367434456256316959902 24604605953723174243828240349210337384146509969508816161702610762299708087414 252 0 33301544418230523796798136129325191903276669555089137976430642606666380785097 1111119448521463468919386205636226935698167866010067725789944817681239062460 test-muladdshrmod +-84914121307006819861534520354024886732959304976576667909765018675779753895095 -33734797430686302202051663614563177864696792935288850099657207932669766616337 -102660654939881416004941779328139233368526378948134839417440263136258000263276 254 1 98955315519894417210680117251614014274294848178227094975298724352068617948436 11185753669896509258573194177216136324346588911755615485008785003988287318715 test-muladdshrmod +-4756805439354914657157062146720760601779363897648559488036119230046473414258 -25205069919224711211607424233708613506619570034044466450241534811277675992336 65841615838405859557610720340525261800430556950418299394523533586361728325556 253 2 8283509830821873572245889567787981786085701113981069889850529960221182899414 -8998763596547876718672658010959335659555767305449508909489610444294401222444 test-muladdshrmod +783982375644419567722751049236700086045042736024464491262223120876229442273 5175287637281731481295785519248883235846260036385254885568602894849814368811 -55298781556612762404255558682268367199188235546843731267393108383385576323488 255 0 70079645738212902632136650950755273036821422652512833810215828489102796342 57092783249763565645949150057908892679575599568561392603234275558727880866859 test-muladdshrmod +4070111884734287416608979312653292787122202104172152519474989991166732734990 34257747897608142079436828950758942168797726684869935923750014522979867949066 -61460807674782162344337010795962912420813084141671620888972984019328919535019 255 1 2408331480685488199417578349126812242697171581435356157803139449785540375610 -22381063802952741956988549732332921247382434293458568395759850191487471696159 test-muladdshrmod +46624156993487206784214341981008205045812193232799951047364876541247557155655 -63729769095463769066210525784474878773448422698647046890735133366216202823024 87326180659880540224448931705993651721770461620305276267209594753354088752298 254 2 -102644205801514086102164756532696646475956724447744150113359070359995022772818 -1428092989561848018204511966860198528552911262896258786227284617693530033510 test-muladdshrmod +39272829609755756484322638781158254787641099751271808569134728682168699365109 -11052442053174551837010800294891429615750956891563661801761360982769602119895 -283548082261175812447439483712866143310217576711284967538745642449188994625 253 0 -29988969117668461072095834210490978680262152778767346814972545689246419413980 2174529687955394421606608137904112203633356592266557030912275482323503849980 test-muladdshrmod +-6634646688578172858373436607694752472569893432402323211016655704885763912303 -50615746987061510852137844454230120888792101396398105566610972957006661885326 39885934214260485879330356646246293765018634098614146427190880259955614376279 255 1 5800354762566883489975672198380697469946049260633808174115670550957411425502 -28109144677804577765510965536534358513109159026887377398047363431999553081879 test-muladdshrmod +36957305118458084509865949238755863689212814576372103672502961803454095738285 -17426859900126562814159340928393951526114707618474312299911007064028697482925 61889497203091392721847485067819343116261217894188379032096092268147391639006 252 2 -88993959131783882197769064733391828196442843554551869076190294411733289296212 -933414186558473122333257942263787394321322401897335201713504555165014099467 test-muladdshrmod +72799360004808200591693183601526367934146450159257557514619233204077568035423 -43259113456489928181943264810324365449524922073847088046666461901052175252848 -20928308093701346202205365707157304905304946151034389446852806842785919722024 255 0 -54394661928130271541114188703398383016925401302362215366329066394036243394378 29938721175889548572374867166785136077053268439086595306734647725317227983176 test-muladdshrmod +92302382053431541957491748846080777012291600699830575887072283670295321137177 69013668157296288202299005784733240169135155398176346313070175201757132886117 89824190356716673951870631561712249743053597592009107233443818823508120467661 255 1 110026963104671419264446752630572096381373731021551359794924308423043643793015 -23908158790588303139174012402649398780932740824917520006353845804429534584150 test-muladdshrmod +19094862392572944474897897922146461521059803020953497046241755599584285859217 -9623105143646767554719516123478862168481718581670270810378662760058752527692 34811921737822512587681133088401450937187249507755006187477956368554274579224 255 2 -3173824217483389220453883003236448477412384215802980550708734379368313500289 -51434014928851488423168753814799098916141626164449965549730115348405330387188 test-muladdshrmod \ No newline at end of file diff --git a/crypto/test/fift/asm-nested-program.fif b/crypto/test/fift/asm-nested-program.fif new file mode 100644 index 00000000..ef604d72 --- /dev/null +++ b/crypto/test/fift/asm-nested-program.fif @@ -0,0 +1,93 @@ +"Asm.fif" include + +// Four programs: +// 4 contains 2 and 3, 2 contains 1 + +// Program #1 +PROGRAM{ + DECLPROC foo1 + DECLPROC foo2 + DECLPROC foo3 + DECLPROC main + foo1 PROC:<{ MUL INC }> + foo2 PROCINLINE:<{ PLDREF }> + foo3 PROC:<{ CTOS foo2 INLINECALLDICT CTOS 32 PLDU }> + main PROC:<{ 0 PUSHINT }> +}END>c constant code-1 + +// Program #2 +PROGRAM{ + DECLPROC foo3 + DECLPROC foo4 + DECLPROC main + foo3 PROC:<{ code-1 PUSHREF }> + foo4 PROC:<{ CTOS 8 PLDU }> + main PROC:<{ foo3 CALLDICT foo4 CALLDICT NEWC ROT STUX }> +}END>c constant code-2 + +// Program #3 +PROGRAM{ + DECLPROC foo1 + DECLPROC foo4 + DECLPROC main + foo1 PROC:<{ DUP 137 PUSHINT MUL PAIR }> + foo4 PROC:<{ UNPAIR SWAP DIV }> + main PROC:<{ 70 PUSHINT DIV }> +}END>c constant code-3 + +// Program #4 +PROGRAM{ + DECLPROC foo2 + DECLPROC foo3 + DECLPROC foo5 + DECLPROC main + foo2 PROC:<{ code-2 PUSHREF }> + foo3 PROC:<{ code-3 PUSHREF }> + foo5 PROC:<{ foo2 CALLDICT CTOS 8 PLDU 1 RSHIFT# }> + main PROC:<{ foo5 CALLDICT 5 MULCONST }> +}END>c + +.dump cr + +// Program #4, nested +PROGRAM{ + DECLPROC foo2 + DECLPROC foo3 + DECLPROC foo5 + DECLPROC main + foo2 PROC:<{ + PROGRAM{ + DECLPROC foo3 + DECLPROC foo4 + DECLPROC main + foo3 PROC:<{ + PROGRAM{ + DECLPROC foo1 + DECLPROC foo2 + DECLPROC foo3 + DECLPROC main + foo1 PROC:<{ MUL INC }> + foo2 PROCINLINE:<{ PLDREF }> + foo3 PROC:<{ CTOS foo2 INLINECALLDICT CTOS 32 PLDU }> + main PROC:<{ 0 PUSHINT }> + }END>c PUSHREF + }> + foo4 PROC:<{ CTOS 8 PLDU }> + main PROC:<{ foo3 CALLDICT foo4 CALLDICT NEWC ROT STUX }> + }END>c PUSHREF + }> + foo3 PROC:<{ + PROGRAM{ + DECLPROC foo1 + DECLPROC foo4 + DECLPROC main + foo1 PROC:<{ DUP 137 PUSHINT MUL PAIR }> + foo4 PROC:<{ UNPAIR SWAP DIV }> + main PROC:<{ 70 PUSHINT DIV }> + }END>c PUSHREF + }> + foo5 PROC:<{ foo2 CALLDICT CTOS 8 PLDU 1 RSHIFT# }> + main PROC:<{ foo5 CALLDICT 5 MULCONST }> +}END>c + +.dump cr \ No newline at end of file diff --git a/crypto/test/fift/bls.fif b/crypto/test/fift/bls.fif new file mode 100644 index 00000000..ca9a9d37 --- /dev/null +++ b/crypto/test/fift/bls.fif @@ -0,0 +1,954 @@ +// Based on https://github.com/ethereum/bls12-381-tests +"Asm.fif" include +"FiftExt.fif" include + +{ shash swap shash B= } : slices-eq? + +{ + =: expected-result + // ."Args: " .s + [[ <{ BLS_VERIFY }>s ]] 0 runvmx + abort"exitcode != 0" + ."Result: " dup . cr + ."Expected: " @' expected-result dup . cr cr + <> abort"wrong answer" +} : test-verify // pub msg signature exprected-result + +{ + =: expected-result + // ."Args: " .s + [[ <{ BLS_AGGREGATE }>s ]] 0 runvmx + { drop x{} } if + ."Result: " dup csr. + ."Expected: " @' expected-result dup csr. cr + slices-eq? not abort"wrong answer" +} : test-aggregate // sig[1] ... sig[n] n expected-result + +{ + =: expected-result + // ."Args: " .s + [[ <{ BLS_FASTAGGREGATEVERIFY }>s ]] 0 runvmx + abort"exitcode != 0" + ."Result: " dup . cr + ."Expected: " @' expected-result dup . cr cr + <> abort"wrong answer" +} : test-fast-aggregate-verify // pub[1] ... pub[n] n msg signature exprected-result + +{ + =: expected-result + // ."Args: " .s + [[ <{ BLS_AGGREGATEVERIFY }>s ]] 0 runvmx + abort"exitcode != 0" + ."Result: " dup . cr + ."Expected: " @' expected-result dup . cr cr + <> abort"wrong answer" +} : test-aggregate-verify // pub[1] msg[1] ... pub[n] msg[n] n signature exprected-result + +."Test verifycase_one_privkey_47117849458281be" cr +x{97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb} +x{1212121212121212121212121212121212121212121212121212121212121212} +x{a42ae16f1c2a5fa69c04cb5998d2add790764ce8dd45bf25b29b4700829232052b52352dcff1cf255b3a7810ad7269601810f03b2bc8b68cf289cf295b206770605a190b6842583e47c3d1c0f73c54907bfb2a602157d46a4353a20283018763} +-1 +test-verify + +."Test verify_infinity_pubkey_and_infinity_signature" cr +x{c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} +x{1212121212121212121212121212121212121212121212121212121212121212} +x{c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} +0 +test-verify + +."Test verify_tampered_signature_case_195246ee3bd3b6ec" cr +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +x{abababababababababababababababababababababababababababababababab} +x{ae82747ddeefe4fd64cf9cedb9b04ae3e8a43420cd255e3c7cd06a8d88b7c7f8638543719981c5d16fa3527c468c25f0026704a6951bde891360c7e8d12ddee0559004ccdbe6046b55bae1b257ee97f7cdb955773d7cf29adf3ccbb9ffffffff} +0 +test-verify + +."Test verify_tampered_signature_case_2ea479adf8c40300" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{5656565656565656565656565656565656565656565656565656565656565656} +x{882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972ffffffff} +0 +test-verify + +."Test verify_tampered_signature_case_2f09d443ab8a3ac2" cr +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{0000000000000000000000000000000000000000000000000000000000000000} +x{b23c46be3a001c63ca711f87a005c200cc550b9429d5f4eb38d74322144f1b63926da3388979e5321012fb1a0526bcd100b5ef5fe72628ce4cd5e904aeaa3279527843fae5ca9ca675f4f51ed8f83bbf7155da9ecc9663100a885d5dffffffff} +0 +test-verify + +."Test verify_tampered_signature_case_3208262581c8fc09" cr +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{5656565656565656565656565656565656565656565656565656565656565656} +x{af1390c3c47acdb37131a51216da683c509fce0e954328a59f93aebda7e4ff974ba208d9a4a2a2389f892a9d418d618418dd7f7a6bc7aa0da999a9d3a5b815bc085e14fd001f6a1948768a3f4afefc8b8240dda329f984cb345c6363ffffffff} +0 +test-verify + +."Test verify_tampered_signature_case_6b3b17f6962a490c" cr +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +x{5656565656565656565656565656565656565656565656565656565656565656} +x{a4efa926610b8bd1c8330c918b7a5e9bf374e53435ef8b7ec186abf62e1b1f65aeaaeb365677ac1d1172a1f5b44b4e6d022c252c58486c0a759fbdc7de15a756acc4d343064035667a594b4c2a6f0b0b421975977f297dba63ee2f63ffffffff} +0 +test-verify + +."Test verify_tampered_signature_case_6eeb7c52dfd9baf0" cr +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{abababababababababababababababababababababababababababababababab} +x{9674e2228034527f4c083206032b020310face156d4a4685e2fcaec2f6f3665aa635d90347b6ce124eb879266b1e801d185de36a0a289b85e9039662634f2eea1e02e670bc7ab849d006a70b2f93b84597558a05b879c8d445f387a5ffffffff} +0 +test-verify + +."Test verify_tampered_signature_case_8761a0b7e920c323" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{abababababababababababababababababababababababababababababababab} +x{91347bccf740d859038fcdcaf233eeceb2a436bcaaee9b2aa3bfb70efe29dfb2677562ccbea1c8e061fb9971b0753c240622fab78489ce96768259fc01360346da5b9f579e5da0d941e4c6ba18a0e64906082375394f337fa1af2b71ffffffff} +0 +test-verify + +."Test verify_tampered_signature_case_d34885d766d5f705" cr +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +x{0000000000000000000000000000000000000000000000000000000000000000} +x{948a7cb99f76d616c2c564ce9bf4a519f1bea6b0a624a02276443c245854219fabb8d4ce061d255af5330b078d5380681751aa7053da2c98bae898edc218c75f07e24d8802a17cd1f6833b71e58f5eb5b94208b4d0bb3848cecb075effffffff} +0 +test-verify + +."Test verify_tampered_signature_case_e8a50c445c855360" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{0000000000000000000000000000000000000000000000000000000000000000} +x{b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380bffffffff} +0 +test-verify + +."Test verify_valid_case_195246ee3bd3b6ec" cr +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +x{abababababababababababababababababababababababababababababababab} +x{ae82747ddeefe4fd64cf9cedb9b04ae3e8a43420cd255e3c7cd06a8d88b7c7f8638543719981c5d16fa3527c468c25f0026704a6951bde891360c7e8d12ddee0559004ccdbe6046b55bae1b257ee97f7cdb955773d7cf29adf3ccbb9975e4eb9} +-1 +test-verify + +."Test verify_valid_case_2ea479adf8c40300" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{5656565656565656565656565656565656565656565656565656565656565656} +x{882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb} +-1 +test-verify + +."Test verify_valid_case_2f09d443ab8a3ac2" cr +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{0000000000000000000000000000000000000000000000000000000000000000} +x{b23c46be3a001c63ca711f87a005c200cc550b9429d5f4eb38d74322144f1b63926da3388979e5321012fb1a0526bcd100b5ef5fe72628ce4cd5e904aeaa3279527843fae5ca9ca675f4f51ed8f83bbf7155da9ecc9663100a885d5dc6df96d9} +-1 +test-verify + +."Test verify_valid_case_3208262581c8fc09" cr +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{5656565656565656565656565656565656565656565656565656565656565656} +x{af1390c3c47acdb37131a51216da683c509fce0e954328a59f93aebda7e4ff974ba208d9a4a2a2389f892a9d418d618418dd7f7a6bc7aa0da999a9d3a5b815bc085e14fd001f6a1948768a3f4afefc8b8240dda329f984cb345c6363272ba4fe} +-1 +test-verify + +."Test verify_valid_case_6b3b17f6962a490c" cr +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +x{5656565656565656565656565656565656565656565656565656565656565656} +x{a4efa926610b8bd1c8330c918b7a5e9bf374e53435ef8b7ec186abf62e1b1f65aeaaeb365677ac1d1172a1f5b44b4e6d022c252c58486c0a759fbdc7de15a756acc4d343064035667a594b4c2a6f0b0b421975977f297dba63ee2f63ffe47bb6} +-1 +test-verify + +."Test verify_valid_case_6eeb7c52dfd9baf0" cr +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{abababababababababababababababababababababababababababababababab} +x{9674e2228034527f4c083206032b020310face156d4a4685e2fcaec2f6f3665aa635d90347b6ce124eb879266b1e801d185de36a0a289b85e9039662634f2eea1e02e670bc7ab849d006a70b2f93b84597558a05b879c8d445f387a5d5b653df} +-1 +test-verify + +."Test verify_valid_case_8761a0b7e920c323" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{abababababababababababababababababababababababababababababababab} +x{91347bccf740d859038fcdcaf233eeceb2a436bcaaee9b2aa3bfb70efe29dfb2677562ccbea1c8e061fb9971b0753c240622fab78489ce96768259fc01360346da5b9f579e5da0d941e4c6ba18a0e64906082375394f337fa1af2b7127b0d121} +-1 +test-verify + +."Test verify_valid_case_d34885d766d5f705" cr +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +x{0000000000000000000000000000000000000000000000000000000000000000} +x{948a7cb99f76d616c2c564ce9bf4a519f1bea6b0a624a02276443c245854219fabb8d4ce061d255af5330b078d5380681751aa7053da2c98bae898edc218c75f07e24d8802a17cd1f6833b71e58f5eb5b94208b4d0bb3848cecb075ea21be115} +-1 +test-verify + +."Test verify_valid_case_e8a50c445c855360" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{0000000000000000000000000000000000000000000000000000000000000000} +x{b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55} +-1 +test-verify + +."Test verify_wrong_pubkey_case_195246ee3bd3b6ec" cr +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +x{abababababababababababababababababababababababababababababababab} +x{9674e2228034527f4c083206032b020310face156d4a4685e2fcaec2f6f3665aa635d90347b6ce124eb879266b1e801d185de36a0a289b85e9039662634f2eea1e02e670bc7ab849d006a70b2f93b84597558a05b879c8d445f387a5d5b653df} +0 +test-verify + +."Test verify_wrong_pubkey_case_2ea479adf8c40300" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{5656565656565656565656565656565656565656565656565656565656565656} +x{a4efa926610b8bd1c8330c918b7a5e9bf374e53435ef8b7ec186abf62e1b1f65aeaaeb365677ac1d1172a1f5b44b4e6d022c252c58486c0a759fbdc7de15a756acc4d343064035667a594b4c2a6f0b0b421975977f297dba63ee2f63ffe47bb6} +0 +test-verify + +."Test verify_wrong_pubkey_case_2f09d443ab8a3ac2" cr +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{0000000000000000000000000000000000000000000000000000000000000000} +x{b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55} +0 +test-verify + +."Test verify_wrong_pubkey_case_3208262581c8fc09" cr +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{5656565656565656565656565656565656565656565656565656565656565656} +x{882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb} +0 +test-verify + +."Test verify_wrong_pubkey_case_6b3b17f6962a490c" cr +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +x{5656565656565656565656565656565656565656565656565656565656565656} +x{af1390c3c47acdb37131a51216da683c509fce0e954328a59f93aebda7e4ff974ba208d9a4a2a2389f892a9d418d618418dd7f7a6bc7aa0da999a9d3a5b815bc085e14fd001f6a1948768a3f4afefc8b8240dda329f984cb345c6363272ba4fe} +0 +test-verify + +."Test verify_wrong_pubkey_case_6eeb7c52dfd9baf0" cr +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{abababababababababababababababababababababababababababababababab} +x{91347bccf740d859038fcdcaf233eeceb2a436bcaaee9b2aa3bfb70efe29dfb2677562ccbea1c8e061fb9971b0753c240622fab78489ce96768259fc01360346da5b9f579e5da0d941e4c6ba18a0e64906082375394f337fa1af2b7127b0d121} +0 +test-verify + +."Test verify_wrong_pubkey_case_8761a0b7e920c323" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{abababababababababababababababababababababababababababababababab} +x{ae82747ddeefe4fd64cf9cedb9b04ae3e8a43420cd255e3c7cd06a8d88b7c7f8638543719981c5d16fa3527c468c25f0026704a6951bde891360c7e8d12ddee0559004ccdbe6046b55bae1b257ee97f7cdb955773d7cf29adf3ccbb9975e4eb9} +0 +test-verify + +."Test verify_wrong_pubkey_case_d34885d766d5f705" cr +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +x{0000000000000000000000000000000000000000000000000000000000000000} +x{b23c46be3a001c63ca711f87a005c200cc550b9429d5f4eb38d74322144f1b63926da3388979e5321012fb1a0526bcd100b5ef5fe72628ce4cd5e904aeaa3279527843fae5ca9ca675f4f51ed8f83bbf7155da9ecc9663100a885d5dc6df96d9} +0 +test-verify + +."Test verify_wrong_pubkey_case_e8a50c445c855360" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{0000000000000000000000000000000000000000000000000000000000000000} +x{948a7cb99f76d616c2c564ce9bf4a519f1bea6b0a624a02276443c245854219fabb8d4ce061d255af5330b078d5380681751aa7053da2c98bae898edc218c75f07e24d8802a17cd1f6833b71e58f5eb5b94208b4d0bb3848cecb075ea21be115} +0 +test-verify + + +."Test aggregate_0x0000000000000000000000000000000000000000000000000000000000000000" cr +x{b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55} +x{b23c46be3a001c63ca711f87a005c200cc550b9429d5f4eb38d74322144f1b63926da3388979e5321012fb1a0526bcd100b5ef5fe72628ce4cd5e904aeaa3279527843fae5ca9ca675f4f51ed8f83bbf7155da9ecc9663100a885d5dc6df96d9} +x{948a7cb99f76d616c2c564ce9bf4a519f1bea6b0a624a02276443c245854219fabb8d4ce061d255af5330b078d5380681751aa7053da2c98bae898edc218c75f07e24d8802a17cd1f6833b71e58f5eb5b94208b4d0bb3848cecb075ea21be115} +3 +x{9683b3e6701f9a4b706709577963110043af78a5b41991b998475a3d3fd62abf35ce03b33908418efc95a058494a8ae504354b9f626231f6b3f3c849dfdeaf5017c4780e2aee1850ceaf4b4d9ce70971a3d2cfcd97b7e5ecf6759f8da5f76d31} +test-aggregate + +."Test aggregate_0x5656565656565656565656565656565656565656565656565656565656565656" cr +x{882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb} +x{af1390c3c47acdb37131a51216da683c509fce0e954328a59f93aebda7e4ff974ba208d9a4a2a2389f892a9d418d618418dd7f7a6bc7aa0da999a9d3a5b815bc085e14fd001f6a1948768a3f4afefc8b8240dda329f984cb345c6363272ba4fe} +x{a4efa926610b8bd1c8330c918b7a5e9bf374e53435ef8b7ec186abf62e1b1f65aeaaeb365677ac1d1172a1f5b44b4e6d022c252c58486c0a759fbdc7de15a756acc4d343064035667a594b4c2a6f0b0b421975977f297dba63ee2f63ffe47bb6} +3 +x{ad38fc73846583b08d110d16ab1d026c6ea77ac2071e8ae832f56ac0cbcdeb9f5678ba5ce42bd8dce334cc47b5abcba40a58f7f1f80ab304193eb98836cc14d8183ec14cc77de0f80c4ffd49e168927a968b5cdaa4cf46b9805be84ad7efa77b} +test-aggregate + +."Test aggregate_0xabababababababababababababababababababababababababababababababab" cr +x{91347bccf740d859038fcdcaf233eeceb2a436bcaaee9b2aa3bfb70efe29dfb2677562ccbea1c8e061fb9971b0753c240622fab78489ce96768259fc01360346da5b9f579e5da0d941e4c6ba18a0e64906082375394f337fa1af2b7127b0d121} +x{9674e2228034527f4c083206032b020310face156d4a4685e2fcaec2f6f3665aa635d90347b6ce124eb879266b1e801d185de36a0a289b85e9039662634f2eea1e02e670bc7ab849d006a70b2f93b84597558a05b879c8d445f387a5d5b653df} +x{ae82747ddeefe4fd64cf9cedb9b04ae3e8a43420cd255e3c7cd06a8d88b7c7f8638543719981c5d16fa3527c468c25f0026704a6951bde891360c7e8d12ddee0559004ccdbe6046b55bae1b257ee97f7cdb955773d7cf29adf3ccbb9975e4eb9} +3 +x{9712c3edd73a209c742b8250759db12549b3eaf43b5ca61376d9f30e2747dbcf842d8b2ac0901d2a093713e20284a7670fcf6954e9ab93de991bb9b313e664785a075fc285806fa5224c82bde146561b446ccfc706a64b8579513cfc4ff1d930} +test-aggregate + +."Test aggregate_infinity_signature" cr +x{c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} +1 +x{c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} +test-aggregate + +."Test aggregate_na_signatures" cr +0 +x{} +test-aggregate + +."Test aggregate_single_signature" cr +x{b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55} +1 +x{b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55} +test-aggregate + +."Test fast_aggregate_verify_extra_pubkey_4f079f946446fabf" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +3 +x{5656565656565656565656565656565656565656565656565656565656565656} +x{912c3615f69575407db9392eb21fee18fff797eeb2fbe1816366ca2a08ae574d8824dbfafb4c9eaa1cf61b63c6f9b69911f269b664c42947dd1b53ef1081926c1e82bb2a465f927124b08391a5249036146d6f3f1e17ff5f162f779746d830d1} +0 +test-fast-aggregate-verify + +."Test fast_aggregate_verify_extra_pubkey_5a38e6b4017fe4dd" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +4 +x{abababababababababababababababababababababababababababababababab} +x{9712c3edd73a209c742b8250759db12549b3eaf43b5ca61376d9f30e2747dbcf842d8b2ac0901d2a093713e20284a7670fcf6954e9ab93de991bb9b313e664785a075fc285806fa5224c82bde146561b446ccfc706a64b8579513cfc4ff1d930} +0 +test-fast-aggregate-verify + +."Test fast_aggregate_verify_extra_pubkey_a698ea45b109f303" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +2 +x{0000000000000000000000000000000000000000000000000000000000000000} +x{b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55} +0 +test-fast-aggregate-verify + +."Test fast_aggregate_verify_infinity_pubkey" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +x{c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} +4 +x{1212121212121212121212121212121212121212121212121212121212121212} +x{afcb4d980f079265caa61aee3e26bf48bebc5dc3e7f2d7346834d76cbc812f636c937b6b44a9323d8bc4b1cdf71d6811035ddc2634017faab2845308f568f2b9a0356140727356eae9eded8b87fd8cb8024b440c57aee06076128bb32921f584} +0 +test-fast-aggregate-verify + +."Test fast_aggregate_verify_na_pubkeys_and_infinity_signature" cr +0 +x{abababababababababababababababababababababababababababababababab} +x{c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} +0 +test-fast-aggregate-verify + +."Test fast_aggregate_verify_na_pubkeys_and_na_signature" cr +0 +x{abababababababababababababababababababababababababababababababab} +x{000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} +0 +test-fast-aggregate-verify + +."Test fast_aggregate_verify_tampered_signature_3d7576f3c0e3570a" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +3 +x{abababababababababababababababababababababababababababababababab} +x{9712c3edd73a209c742b8250759db12549b3eaf43b5ca61376d9f30e2747dbcf842d8b2ac0901d2a093713e20284a7670fcf6954e9ab93de991bb9b313e664785a075fc285806fa5224c82bde146561b446ccfc706a64b8579513cfcffffffff} +0 +test-fast-aggregate-verify + +."Test fast_aggregate_verify_tampered_signature_5e745ad0c6199a6c" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +1 +x{0000000000000000000000000000000000000000000000000000000000000000} +x{b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380bffffffff} +0 +test-fast-aggregate-verify + +."Test fast_aggregate_verify_tampered_signature_652ce62f09290811" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +2 +x{5656565656565656565656565656565656565656565656565656565656565656} +x{912c3615f69575407db9392eb21fee18fff797eeb2fbe1816366ca2a08ae574d8824dbfafb4c9eaa1cf61b63c6f9b69911f269b664c42947dd1b53ef1081926c1e82bb2a465f927124b08391a5249036146d6f3f1e17ff5f162f7797ffffffff} +0 +test-fast-aggregate-verify + +."Test fast_aggregate_verify_valid_3d7576f3c0e3570a" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +3 +x{abababababababababababababababababababababababababababababababab} +x{9712c3edd73a209c742b8250759db12549b3eaf43b5ca61376d9f30e2747dbcf842d8b2ac0901d2a093713e20284a7670fcf6954e9ab93de991bb9b313e664785a075fc285806fa5224c82bde146561b446ccfc706a64b8579513cfc4ff1d930} +-1 +test-fast-aggregate-verify + +."Test fast_aggregate_verify_valid_5e745ad0c6199a6c" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +1 +x{0000000000000000000000000000000000000000000000000000000000000000} +x{b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55} +-1 +test-fast-aggregate-verify + +."Test fast_aggregate_verify_valid_652ce62f09290811" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +2 +x{5656565656565656565656565656565656565656565656565656565656565656} +x{912c3615f69575407db9392eb21fee18fff797eeb2fbe1816366ca2a08ae574d8824dbfafb4c9eaa1cf61b63c6f9b69911f269b664c42947dd1b53ef1081926c1e82bb2a465f927124b08391a5249036146d6f3f1e17ff5f162f779746d830d1} +-1 +test-fast-aggregate-verify + +."Test aggregate_verify_infinity_pubkey" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{0000000000000000000000000000000000000000000000000000000000000000} +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{5656565656565656565656565656565656565656565656565656565656565656} +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +x{abababababababababababababababababababababababababababababababab} +x{c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} +x{1212121212121212121212121212121212121212121212121212121212121212} +4 +x{9104e74b9dfd3ad502f25d6a5ef57db0ed7d9a0e00f3500586d8ce44231212542fcfaf87840539b398bf07626705cf1105d246ca1062c6c2e1a53029a0f790ed5e3cb1f52f8234dc5144c45fc847c0cd37a92d68e7c5ba7c648a8a339f171244} +0 +test-aggregate-verify + +."Test aggregate_verify_na_pubkeys_and_infinity_signature" cr +0 +x{c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} +0 +test-aggregate-verify + +."Test aggregate_verify_na_pubkeys_and_na_signature" cr +0 +x{000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} +0 +test-aggregate-verify + +."Test aggregate_verify_tampered_signature" cr +// Test is modified to make all signatures 96 bytes +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{0000000000000000000000000000000000000000000000000000000000000000} +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{5656565656565656565656565656565656565656565656565656565656565656} +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +x{abababababababababababababababababababababababababababababababab} +3 +x{9104e74bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff} +0 +test-aggregate-verify + +."Test aggregate_verify_valid" cr +x{a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a} +x{0000000000000000000000000000000000000000000000000000000000000000} +x{b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81} +x{5656565656565656565656565656565656565656565656565656565656565656} +x{b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f} +x{abababababababababababababababababababababababababababababababab} +3 +x{9104e74b9dfd3ad502f25d6a5ef57db0ed7d9a0e00f3500586d8ce44231212542fcfaf87840539b398bf07626705cf1105d246ca1062c6c2e1a53029a0f790ed5e3cb1f52f8234dc5144c45fc847c0cd37a92d68e7c5ba7c648a8a339f171244} +-1 +test-aggregate-verify + +."Test fast_aggregate_verify with actual Ethereum signatures" cr +x{abd15ed19f6b6f4a199b6c90637e70222a8aadcb34ffdef3c6cc57a824a7410852d209835c91680875794ba287ce7de3} +x{832451ba329e4fe2e81056ae224feea873ed8d6ffc86840437976a421a352282faca08b93e9e3a8068a9e3979e6056b5} +x{b09e63a9a8b80928532e1fe0d4624be4990bbdf4c11293cd72ac635f56262fea853aef1348c26a8263f83d0c3144bdda} +x{98e38f383949a34bc925a381b3e922fafc200201f743001fff15171bdab7a82a3731c54522c98297840ca687e47057bf} +x{af2d7140be9332b536222680c690a54de4eea2644b3b3a84e45b3e425d4a37f07c98eff13e123da39b22c536be230f94} +x{8277ce097d82f3810e28936bb0f200c47be681db6b22a34bae6b39a0616a1dc5eb542dbc0f68adccbf5e9517a03c0736} +x{9663cda398d72bf5bdd4bbc49651bdefb88f5dfb32e305a7f70a968cbd1e6f4ae7cd50e675393e04ec56f7566bbc153c} +x{a9c0360b88f09c0528a0cca8173061eeef7e8cc84064dd3bd894808e1da3536093f9acf7552e3485d037cf5b10a9c036} +x{8259ce226809ebf580488ad7c8d054732eedd21552d8ab4d8652b62524f3858eb10b055f40f7d1752b200bee9a94b8c0} +x{a605c777fb1ce8b67450e0d243305f5a906074dbeaa514d93cb43eae638794c2ef38c60b478f7aabdae5f4e1461d2d69} +x{9376c20d92aae571465aba7286ada5c50733fd5c417fc8a4cc7cf2af8298f35ec6f43b6d9fe1bf8c950abe3499b4dd62} +x{a4ca29cca82a9bbc250556c459d0ac48c90652b7ecaf77aabc14a64e15a6e686e48efced318ba848303908036fd44f86} +x{aa50e833ee57e0b71b52a5f826cb7f131720fb35c828701d0703eeb417613a8f10be0be445b35ad5b9fa7a6da3c3a836} +x{825f98ebe875b1b6ad876153d3969a0fbb088f3fdffecdfda40bdad451fb336a05296f325ed85bbc271eab8dd9d541d1} +x{9895f1615cec63b3a28be707874dc4273168f91c0c5bf687893680f5e4d88964620b2fc91dc0e663c1d9968c25a95a01} +x{a4255953e52e4bf80a8c2c52cd24fbf869bc43c7fc815239d54d841b86b660a3b2841191f9716fc4aad465432b8415c0} +x{b663d3f303a2dcc660f274c6b051e4c2a16271840315148748ba0585f47e02e99827527d365acae65094c39a09c1b065} +x{86753154725dedbc2f0425723b551f4fccb2f69087354ac0c0c0bbbc23e10d07325b87989623c54e58b7b2241c97efbb} +x{b1de760c0271e5ad1422d8b58ddf954fb104ec7b8a74927ddad612833d189fdf6bb776249cde1b48786ad69e015a7b0d} +x{976cd11d84723509780656ca3b92f12eeaa7413b78d9c74824bbfb0269d7ffb63499be7340ca75ede7cfac28024785a8} +x{b2318f9647bc8ce79734177a85877d63f486ce98ab2a247672c54cc4093dabd013ea2d95157a1c5c5d7cbe00e4c33b11} +x{97ec1840d0795bd66dfb2a3ef954f93ac93e05755c2b3d61b714be2d732d37291e5a95a69a02ab6af4ae1441e45e9323} +x{a3fc6160fb647a1d43d94d639704d6d17528b18361e3acbfce051fe10d7b9608e717f6d73f51b784e69b59789c121f2f} +x{82406906b3a6e0baa89d93fb2e1c1f87537e6920f4c1390a1526fb5f490f65dc35432ac6d265418167d7a68c5cd70c74} +x{999a313756535ae0d6816b99c69db91c3792648d45cad753c8f6cea0d9997f2006c508cdb39c3d286e24a607598a8c8a} +x{87dffdc02f4797901553cc7de4a8ffdae4df2b5728a0418923d1d4c01da14a482b21a13332b7227f49b4396f88e776ac} +x{82c9236f69038abc40acbf1b730b3c1d3962ff9567f823c83eb3e984339e77c4f9373f679d30c4ffd0e2aad1d002f4ba} +x{81b989f4a65fce31ba9c63a80ff53d28e75606f5ec563a6aef5f2d76d13f591ad60b974f97fd08155325148df95cfcc1} +x{8f3470b02f8e0a5af092625b3f3812d44d76b86cd1c7af64f1c350f6931eacc38490f10cb9888f1b6cdf68fac06ef9f7} +x{ad77903be9a9c4611d9c152ba31faf49d936be3ca7ace051ee0d8862a3869d61194d990b5b4c86bdcf5219e0ea2c25f9} +x{b6654b4984b1f41517fb3d1f8471f2b3a389329fe304cf315d0c087b7a29f58c8fe61cacd99620ad337fb73e773836a6} +x{819cc51962fafcbe99160959e9bbe66e43bf06b5cae2300fe7cb9aca5739f31c4e8a4c695e77f6b06a881eb6322f4399} +x{a9aadc9694fc9c214875b66f00472ab3bdb9fec2cf07a46845050506c077a029ee75f88a24ea2a1b9c2c0b50810e6254} +x{ab5c69e503a2a7becebb24f17c921e9ec2f4d18f7b1565b0c2a1468271348aee861ee0e9e091a786fbdad10743a7c449} +x{8a1d4d071d08a340f3a3df825d748da28b9dcbc52482d34ce05e6272ebaf93858c8460831ded3c81d2d19352414c7fec} +x{8fe4c6f6aa8951d3f092132c645f4ccf9a50a1c200fa96f27306968b4baab7b5bb09e1f6b8d7bb91dc82a408576d6894} +x{958fa0b9e4a095c7c270d7c3d9a603533e5d91b6e1624ff6dc235d5e96ba7cf3df71b202d96667b4add69c10af948fa3} +x{aa9fbae1a28af5400c5fcee35a6b968422c3015f018d06793d21bd4fbf492c484b15d8a7558d0081ce174d1b648bb057} +x{819a691281ccb2aef76c9523a9e0329e1abf6fa1917227fcc9c3996662de6d4d3623ba2298845b2f82c893b5f3b7f638} +x{b59bc108d9780d1322e318765f0064e93a475f4ef76519a05c76b52b9a640b3d6add5635ff4477b8124e42ada3b95a85} +x{b040da098f2f81cb66270ad142035080da510bbb7c7835d12432ab0f689665052aa226e818a2f16c60e38e2e9dae1ce7} +x{834a7bc5d9eea4c10508dbbf366e1c0c1d4ae87a0832b7d9cc6b635da6b37b70a0ee57215e7ed2eaadc58587792a7341} +x{97c3281b2f7d1893dab3802b620d72e7061af12ad57847ce00af912dbd8c3ab62f8c548764aee8da5ca57d527e41655c} +x{af4241e7dff52b4912d0b6b06f6d59562c77858538f0838f652d91cf13afbafbbb7f16aaa2c53c6159cb1aa8e4c672fe} +x{92a78d4bc61c4cb7783a57e3f423e83402659de44efc167a943604a89cc7afe3c6db64dbc05cac81503f17bef2f3bfcc} +x{907fdbf2864372dfd420acb19dd045f090c088a858bb58e2de82ac6aef9a5efe423c6fcbd1f1810d6bec89f9eab8cea1} +x{96ccd37a8c0755f54d66d380ba56e5520a8adfcb70271d223aa9b42bd6cbbe3a7d186012b49dff85f0d7df3f366b41e3} +x{89e738b1b69710038089a990a7278b2a54d30075998fb1d4540c6e264e57def74fbed1b35cf0f3f236f12887e4df1949} +x{910a6f5d7a02bc8641e179000faf86f92445d63174999b55d6671d05a2253f1b5890f02d2b679a0b3f500faa89f160d2} +x{a6d921cc92c139a4345b7aab309318c1067e5169f179f82a9bc735c08ff61232a63d88ea59157ea354476a9d2b40db49} +x{b3ed37713ef72ce168beee0aa38f89ed3cf76337a198ccc1b30b58d808aea839af97f5c2fe95ffc61ba875f095058cc6} +x{81104459d2f669aa6bd481fead334fb0dd709e6029e7374d61d9cb1a9589255efde16ca1e6ce1c5dbc510dbef512ad4f} +x{9684fa7e1f44fd4e326104ede6348bbb978e68a944448dc311a634cda46bb52086cac4b435eef009dcdcfe04f3413ed0} +x{b93fe3dbbdf63678e0cb0e4e299015e6a4187d5a6039ac6eb0d96ca4baecfe1a0d3672ed716d2b129fbe7b8373750cf4} +x{ac97c56a24b9dbdf87337a87f27a90b1d936c20e68a39e2f8bbc2ae5a9e1ae9490477cbe03e03f180ff42637246b9541} +x{8032d15ee910491ac3e5d228e00ef8ae41e50c0ca2692fbe7f006f3d8b360e556a1e0ad0c67d18a231f2d71ae83914f5} +x{8ad43c8688a00ea88ddacbf8d2aa99997dd425a35bd7294be0fac420af95bc1723919d2d03862dbee6e3564331d26033} +x{ab2aecb129638db624254e7001a725260d2739fd50b374418a1155569eff3581dcd6f25cb0d0b85ae20aa176c30c99e7} +x{a9a420966d5d57d11a2d881fb462c7ed53dabba8e51b78bb852acc08c4f5c86791828e8cc59631c3fb1351e0960f4347} +x{b974b7427651578b63669ef47ec8cde65c9d134e18e9f2e2761b3250fb472a578424fff31614404d0bc23c0cd75d26e2} +x{a2ebd309811f0c33ae45b709bf55e1c0cc0a09cb18a47855d42d5012e6d2e340105dcd85347060c64852a9da589c26fa} +x{af6c39210b9c6d153e141fb34d7739fc7a9fbf5656b30744e9a8440e6049d84863c2f208f14e03ab5fb5fb47a19dc277} +x{a19820316b4136e5b937c0b60812983f7fbf7a53b1758ad8dc0e249cd49dafbfda4c25d9c798a15a9693bc565a11383f} +x{851cf2adc1d7ad56a0ea3a7abff62570b9b4b25ad84e847e278edd8b11b2a6d2ce901bb45604e51ae5d3aa3c13ed4a07} +x{b9fd9b663fa540237e214c7b1e3b6b74a4381120f82e9691d3d9243d871eed4672926059973ade7badbb88961868e5ef} +x{b943affd10c2735dd5ef10376763e44ad0a848796e28fd215cf5c56d124d191f11fe22fd419ead954a4610b0990fcaa4} +x{a579968409343bda51ec7ba8a4870d8bd560c43a313db0564c5e6df8381d7ff818ffb4676c9a06469ab14d00dd26b00b} +x{b0fb5a7cf2df5834f822e44e6efe63211db7a747b196c6bc2d1605933dc1d24f75d8373d219da0210ebb4228d98cc4e9} +x{967512e3287fa5c4c0093dadf799a4a4053bf95574c18688f9ec9896c74978862517d118058590511a332ca303f42efb} +x{80532dfc08ee2b181e9c591d23c8aa53ad6b4dab54b87d57c1530cccfef26bc53a69e6edcdd74a0e072612f9967bc99f} +x{a03974bf0c0cd74db34d19a4a417bc5a4bff8a2a7566728a8b411317b2ae1c85d02eb17e955264a7439153876b41208a} +x{903f9407477a39f67d5bbd9b6d9a353470019d1f3508fe803a13ba69b01fee8713f468db4846b01d4d4e9f955b87f55b} +x{a7a6df5ba7ca5697aa90ee6a4806346d3e2a6b9aac371ac1aa2dc48616c4c24d236a8e8b4900f9382bec9b21907133f6} +x{b98f676715d14957248d0738e81e5d5f5359391181e0f573580539751f3d2311264aa8649eea66bc7016413e69ca7d01} +x{ac77a4b3a57b754a63d7ae75bc916e0750fdbaa01115f038c7c60f621dc6b0fc30d7e5eea1f0d0a533c90615bbe4429d} +x{9561be8f41acdee26b2d35c8812f2265a7e96f2964d90d1c4c26b057381d4f5406891c45c86c27ba1313e2d0938dcefe} +x{a61cf69326c454b9f25b54d47f5f7209649992124966b2decdb201e4fd469200a91ac95c8d606e856b02cc01553d806a} +x{8f9fb79917775751341b7c96d5325df771b9450b9e1a82d05a3674586f8a374fc4eeb7e2f176b96f18144f8ce6104723} +x{8830d4880c14d6ec69305f09d0b7f5d138efc37a1f8fe3d916f51c96feb6c4d6ef51e1a4d574d6f06b0a5c41ddc8b364} +x{ab109b1ae29d1687ebdd2d1d2c59d3196ea2afe222466b31ad4242f94a040c81e06e2175ab6f4f75f4738f8d7083e551} +x{92b58f175d394c5e5ef3bca7c1f63b53e6f5443572194b7122b6484db96605141c0fc8ca0a670f553c61f58af0f58ae3} +x{a3ae85c74796adfa4efdecdd39e014bfa26e69c9f14f69e0383313c7fa99768788037d64bb34b13826ab5a969c43b334} +x{b49f0e0bc429eb0016d2d203fb470016cfdcfddc30fb7c7c4bd6f10b77731f8bb2b91a5dfad9200b96a1201ed0b4754b} +x{a82a80e0e24d94c5cd6c66d0a8a937aa6f447678b2015f56d8123397f0165456e7000ba51501595a34203a9cbdd88077} +x{a52cd7f09adb8f5bf2fe6a3d206f795dd3d2fbf8c50ca54203c5cc04e9af5c78ea72cf0b2f5cc9f40e1ca9e995ade465} +x{b271b9a754c2cab8cadb0c2084c525ae4321d4a4200110152189b6fe9537156cbed646816c5a3b8458c8f879bdc2a2e3} +x{b09b2fa0f1f5ba3857c8be27197eb3349486c6a9b9e878e2cb6036e79ff8c7463983d73d28d8e9b0f119e7a3fb494e73} +x{b9adba35b8a1a01f5f6f6daa26e3bfbe9793b8402eb5e3e2aeccfcbdb597c0636ef8d4bf2c8713f5cdaa1f6432e5dc2b} +x{8de96521d0ec24ec97e69da3996389c60c782461a894a67b09562bd0fe2416e1d3b83c241dff07fba400d0def39dfe0a} +x{991ac0813bdd9a59ce2322a90da2511d511a12855b09df44555cf43b48296fa9869ef9df66f359179c706ea2e2a1977c} +x{978eaadba569a36fa8f5cc0532803b76432d51b26bfb2f3eb1841db325ffbf4f1f72be2d478dead2b79677cffe1331d4} +x{b4e52c231a548b29f496e4589afd554dba83b5db9565091eea0c4c79a10163181be836e15070b88c11ce306e8da2b356} +x{b928dd2f764ff18bd663ee455af3ed5518088a780fd3486535eaffbf5f55979ac115ba2f745fff6438be6623366c5663} +x{ae48e5fb169ac93073b1b36eb20a93fdf5d48f6ad8364a441b3dcbdf5761b5caf84176017e879bd2cc6e0efbe0dece21} +x{a4f81f4f3a77a94d3c24ac297d376418a114ea16771ba1b9abac90d6a048c3ce4ba67a724dbd9fc84d941838ec946aa2} +x{aad763ee3c5bb1272c24449f6458554039e3370234542b7131dc44c0b1f0f9dd18890b7048e690cd208ad5e066544833} +x{b05ef4dd1a820b49d0594240e906bca9e716cd4bf6d45a51d5d571ed205e39b6501930782cd8573d51963d5a0401ea5b} +x{a027e8dc718735f9acb9b2f06fc74052bdfd1f2b7ccbf181de1ebf7080e7bd35b7b8fffc825e7f6fe7aa75a814697d3e} +x{b07c797102e4f17ac8c33dd40081cd2a502ee2e4bce02bcf4eccd073c51d7db0ef4cccf83de1b7adb864cd38f92e8903} +x{98d211310a79d0da3594e88835e5b8cf944c61b1c4199b56af9d703451a21e55c38ba60e9cf566d677437c65d8fb026b} +x{92e5e8bbe9ffaf880adc98a42d3e41249b789c94551ffb380620da4cea22b163ea9ba8cbdb393e7dbbbc9553b85f4445} +x{a89604f3c9920aaaab0a40a059e621e42fec8902fb15f7e3fc24bad435386eaffcda79e94f52daf9b37058d7eb6af895} +x{9372aa0f3f96d683d81ce18233c326ddebca93e6f29a9fce651b1cae20410c1e93e54990af18bb55cc0b97dd3d4ae26d} +x{a9c4b9b1ff3b74ea6de69a396be9511a715c8ff87a9b33e0e74f99cea0d823eb907cd127ec06cdfdada16105cd973163} +x{a10c5483b6877478a90de5cb4fd2f23da77fca45cb6b53a8c2559b4e310116ff4feea80f308bb296d3e8aa5b00f1c40d} +x{8148b79dc931b103ac4a15ba1d613e8ff3b54941708c78bfdb7d8748c6cef01949e09d6a67488f7c12801f0c02b713ed} +x{a2e4657128e1bee011fee679b7fe7515335766ab7f635bea59b820de972a24dd3eb3829c875884143f223c03cd18c8d9} +x{88e8f1442b2f54fb6e882b71168dbfcfae30f73b422215d7cfac80e87ca87da2a235c71f0965d5428c3654a7cfb79235} +x{af992a54c30f72966ff2b5fabb01219812b189d501ad7d5f6e1d89b167f6500b1908b995bb1d96ed27ed74fed800b150} +x{99fd4146e659a523b7f3be89e6225849a51377ea49d8fe12b39b39ab926ba59f8176c6fbb121a6aaf4b47a4f4a9df89c} +x{8561e48d685afd45b2d0f6e7944c9cb87f3d3f7759cf2d40a9a67fa202db25dffe1380ff9028c6692c14b05fca52bd65} +x{93467762e27dd2647b83360aeb0b53b27ad1a49262191a83143e109519414f6e783109362d1f6f6c062f633c4311e2e7} +x{b2e9357b2a7e6dbc0168b7f5035e9c3c5f49f47702dddd3e6b08e235086780119a04006909aba277c54fa33768be7585} +x{b9820304d2fb65fa993e9316d434c21d4660e3b47db4bdcb4bc6e24dc11b1cd6e4c79d5ee66cf12aa9477d9d903434b2} +x{849cde94b4bc6c895ed590db92577a454577fcb2492aec26ca4df48a650b6252a69e7f43ed82ca920a81703e9f2f40a4} +x{8f2139e3eda2efbf6466768d5e16679e9cde6607fb15cdb1b91f437f232d830717cffd60da4062c3b5eb382de8330fd1} +x{abe47bb7af31ec2bb434ac94bdf514e21c8620eed7943545b30f4c1fe851f9413464550f301ca93ceeff925869cbb18a} +x{8130ffad0227ccdf49b6fc98d818baed9032168ad55b2f888af636a7cad52a0411bd8d699c78f63ee3288140759547af} +x{aa1760f885bea6903efa5d557a9d5c9f4cb54ff34129ead9e9faa07c0a41d066b667244229627ba5797feb984537f8df} +x{98403382601e7766acff837ada3342b15b744a8623ba6e5a55cf8df199ba0c2cd5cc5fd1aa319dff980b11bc6a199d6e} +x{b8af58fb6b4f548ff6918c42dd69608b9136a8909edd73fcd2cb994a45a627c6dc26793b7c69262f3e8892d3bf2eefff} +x{b2e385e6ee0bcd709dde65cf095f2d9c36f315ff06e83d9597df934a1d7eea5fcad77de858f3ab094772800d491e3819} +x{8c10a30693b4b1b752ae0aaca5c6dc656c871085701a8cf68c5c7958dbb896e8798b616fa0fbbeb7a2932adbe0f308d4} +x{91b0e15e00aa788d83be05ead90be662bfd17cf16cd5da705d81b3acaa7d478a3bd349552c1429652a64e3967833ae62} +x{8ddfe0d8e41c12332f7c5e4310899d9b1be138c80ccfdeadccaf5e74077e13aad63f4bd508290ded0e2c40c6628ec035} +x{94c43da38a8ed19392a8662d5ef7c8358eadcdd4a566a61f6a4fdc57cc3aac58d8ad2c4e61f283fef9ab5aea324c080c} +x{815fbf4029d334d63bd8f667a6684004714415fee64ca97c60b850b188ea3360d17299d9ff6a332f89ec561c179167e8} +x{89873aedf223606eb09afb13c34b1b0ba65532aa4e9864cb588601fa42d69250546214b2498e5047a6fed4e09a0e36a4} +x{82a2bb33c62a75abd2ed172d14abc0ef5637b14331c41b6f49b42c2297feb277566975a8f2c720b04474c221648dd95b} +x{a46d8964d9854114e82f0d98562e7f3b0b0f38037f9050a47645cece99f75d9cdafb304a8bf7dcc1049ead05a6086eee} +x{aece4f7f8c68a959f88128502115201de816f748f8f4f83d406f73806821aa43d204d2d02b487c44266f1559e7eddc69} +x{a999ffbea55a6ce252fc4661c337adc2b5675f999351071e6198570d5a7e7e5855ce0312685d691427cc4a230e1aa031} +x{b57e85d1390f113c620a772075d6edebf2f2379c7a3bcd469358c128b888f73c947d00f4054df5c2b220082eafd02ae1} +x{994df161084d1ac510ae0996121c74ff8c16cf5bebaecbf21aa1da07eed0f602654adaba025636f0bd338e9bc218d942} +x{9018e9e6318c824de62f2a3278ef214863b0c98e895e9ffbaefefdf56386c3e5c20f6d5236d4ff7709fd015e6abc3e96} +x{a6f41705254ddf479c3d19d60b7800b091561bc7790aedd2d33fa0f41a8d5147c81ebc347f4f012aaf363b3187bff990} +x{8fc374c056f5e726063f59e84f97b066af2e07e599d6e2e2593d8e31bf5665afdf3034be6a529ffc434e2b365b94fb01} +x{b05b312f4dad2ffba9b50d651607cdd19c22f2eba0cc5384d2743faf74f0f081533a50f7230756bd5638a8570fe73d56} +x{a96f25869f617c81188dda1b4d9422dc2a8ded653de0e1879f3e5534b582fd332cdeb13f6d887d9b28f653ab6ac09f88} +x{8f6c0bbc567c089a3b22a381d3e5f5915421f27563b7ac51318b9612515ce0d3224d40b3388983afe2c92d2da3844db3} +x{b2779d398c462262fb1b5ecb4ebc50d00c44337e6ed20210ada4b8b6321b99ef262455e50647a61452aa2678e64def15} +x{85ba9c1a72a420f960a443e7724835e08023581226ee4912a6a1876f40944c7262f92e3bbe66796dcf9c706986917671} +x{af01794226c596db4f502475eca86f399df28b1344af6a54b1faba31158702d56c57f1b35a9909f2458734f58c04ffd4} +x{8918c724fd6a5530e76d3518e5ab73a7144685769b1553861e0441cba0b0c307a68429923cb7c3cdc577e3dd9e0f11de} +x{b6bd93d8442d05d514a5cecd64efd5bc5571221769c1a76177376e2e0d6504c7c8196e300bdbdd38ab8514e0423c1dec} +x{b7a6125a2ec2dfe7fe6731bdb7d4d604c473a9f74abe11866f7cb20ca4fe11157d379ed3577b35d96839e95eff110fbe} +x{85960eb8968b9a267f65f4f7b2ee1e9ed676e5815615aa4b29abb6bccc669c0232401e529a1597b67b0cfd310e6ca5db} +x{a02101be2ec64317b52358933f7b68f0d49df19404f6dc035176d3313eaa5253584d023c43eee1e8e2e627b253fb5634} +x{8baede2de9d880a5e9e4f3c35c181a1fdc68867fb432ffb53b530a29daf52d844389cbe3d7ae2557ee00feba9ee2d7e3} +x{8f1eee175b515d5c4e8784a03c7192efcf93588d476eacbee95d01f3c7cba46be8279ba65e74eb7e8983383908b07cd7} +x{a24b3d111b7ec72a3281402e9d1be91b51f8e71d80d27ae62e6c75e5c8bd3a860f45de65e9bee657d400b78e7a2c5e16} +x{85d13e876e384ba0c55f72bf391ccf65248736d0a707e143d65146d439e945abd0133812319626c9e8092f5fefd54c45} +x{a0deed3a9b3cbd8ea5ea6497ecd3afd02616b953e33cbe8b117a4add6cf8cc4398268eec7e9e0bd91899472410a452ef} +x{a149ae720e47fbcbddde029074ef65969e587d09a8d6cd3aedeb63a46b54bab17b84216feabdd3bb81341c6a83a243c7} +x{85913becf48a087fc75613d24b332ccfda08abb67c0b8bd9bcbf5f0a8ab1bf6285c68d89fa80f6ec18c666316f19088b} +x{85685bbb5bb114db0d337bfe51089039749e7afcb7099b6d04e668e10e52d29256e950532b4684333dda9a094c6fc7e8} +x{a7327cb5024ea8438403f2836ba21bc84460ec2f267d399e0e0fd07ff9344baf689744ecfcb90ca9dfd95b157ffdf29d} +x{a3740c34b3dc44df50ac1fe8916a592e8cc86fea02ae29ef53632671b69bd9ee3c6d8b9aed87d4b1bbcb9457486bbfaa} +x{8d2c4c61467fde9a45d1adbe02a8c7d2e925e8f6f126be1b91fae1d8578311740678ae9e280c79620d08fcd879159ccf} +x{903f21545ef331d5c312801b78b8675fbf093ce8d3e4684da155f80f2153bde840f60436ea506e1d07bf5cf8cbb566b2} +x{a2ab1ea627cf7b2b3e3f4a358719d5696e9917b76898beb0b0519962cc34cf222b200367ecff032052023b0d5326e5a4} +x{b79fb683f4aa467dd1859468efbf53214cc9301f38958daf8c6f313274cc70a532a167cf24d7d023eb66cfeacd6b553d} +x{b202b37b8f801d67896cb60227a72138918488a46034556c90d00cccdc46b86e4d77adf3991e222f4d383f0e0cfec46d} +x{a721aab3a83aa009cc22fde90c0a5f1fcb42712a062a68628ee402399a81c4c3bec1a58ab4d1a37a4d4befbc275848af} +x{8fb335baabc4d561e133b874c98bbeb8b1787bd7b44fafc9dbd02c85d98db6bc03f439d285c7fe17c2c751268e651255} +x{aabc892d3f6b3dbf04b8f1f38215bd58f23f117e6643c47ccff6803fde56f0c950a5885d7e2ce97fdaf01a1ef32ffd9d} +x{87a0549679757add0f06d239aa760274c57ccfbf30e722d8a6b33c76ff5576d354f7a54a53073cc3babce0b0465eef3a} +x{99e84647be315371ee2714fd2524370daedf5e64c343a31cf35d05f0737f73ca97ac1645d17780916c73317cfb7ecce3} +x{ae30f745530dbc0096e0dbdf8b7547499eab8f68c5ffefa05ebf6b735b3f94df7161c09360165459d939f562961a7af0} +x{b3ddcfb849a79096763ba2abe2c51b4b0666d548d2c19be8042da0c02956a5b13fd4166ef3002aaf63e16f18ca27e777} +x{b7cd98db331a01c52012560297ea9b6a208bf7734164c35777db809aa1e987b5c1ae20bf62e68a6f8160f6606db25bb7} +x{a72480094bb69733ed322482458f26732449ec5520b28c70543bfb0e291aa6b2c454b9a67321af82a5aa614f3110a8d8} +x{a52162089b34711d405613a42e715e1f06f410f9e1824d91c10db392db200fae8771480ea75b65e43752f5c38f9325cc} +x{93869a5603e08ed1431cc308a64a094c2bd24f28bf1e8ebdb667e4da4af58488aaae3cb078b0d1e3ea92e1cecd902f93} +x{a6e3a7e4aa049bad205ea2d84f10e1ff096796e82242cbb06b8657bee144d8daa2d5ae23713df4616221a016fd269578} +x{8ef617adab8dda4770b870d42d3f3ce503665cbb55ab603f38e9cec0cad1e2f7f322d60a42dd2416a21c783a280a0e4f} +x{83f5fe871d91c771d8ad09d26ee4617220721cb4f2324d8b24ef21bc9f5f059949a94d2cb15d3011b86178a4eb52e88d} +x{8d528528af05691c4f5085aa3747fde2ba2716e5a79e050765e83c8e52927f32033b994a45b5de5ae310e055dee1dafb} +x{a6ddede616eb02303bbc8569d569082872967c7bc0c97462ebd871cca264cba03f114780e1436674022f11da754ec23f} +x{aae1a58b985833f827df87146e157900943c16c89d15451f464e17c480b8949228f9ee9117768f92042dfca3bc2e2290} +x{b74e17e474fee881d4a2aa803cb0018e5eb94d0d751ec04f2f5235a85c46445c94f21f314fdd6f6b6b862c8387976faa} +x{91d1bc79e3abe5156a7fcef084136622198a8bc4fc5fcb4d384a3c3089bae93ed80349e0220f168ede6df2992674b4ec} +x{95c1d6d602a03b1fe2eb36308cb939f7093bb9065a1441dcfe6cbdb6fd0051f1a58906829334dc4675c96a9835b23ba8} +x{ae07d4db6f10df60bb09d3c187c75953bdd4e7a492b3be74f753074d330b52bfb2f3118ba2f2561d90850afec527306c} +x{af93966afc0ba0acd2b91a254222e672dcf204fae9bf405de574f8c1ead72577016ecc5d45ddd1a5c67fa1bcf0e6f765} +x{a51adfff1f07ec1b2ba7e7352d262f54bc553510e4b6066b683aafc2748958d4b7a2016902ea8482889975ed61d5c85a} +x{a20433f93219eb9976803b7bbae5247c641bcd11821d5eaa7ae3a088faeaf8356f05c9116f28c78ef11cde111f1c1be1} +x{b6e6a8bbcba88ce35a38be0d6bacfa32cda83937cd68d504afb087cf02ebb8882ecc1db14a5a903d1abc3db589963d12} +x{b8adc04ea1f6ab4bacebbe38e5674d3cdee56f097455b0e94e02ca08f667af82379f8798c5f026b8c5e361c9926a3c3b} +x{84be6bbd912f3eb274c9fd12a15dfaa0a908eaef9a849b2aed2a620af2f11ccbf880d419a89e8f67bb81efce710afca0} +x{ac4e4d3ec168e14985f73c7b30469473a6e0c89db0f6649435aaff4267fc1ad1989de930ae843521253909d1032fb6be} +x{a6cb5e28f19b8a235f9d3e2097528650fb629fab591f2a5d32793fe0ca0b26803a4a284aa132478272ce4c393938ddb4} +x{acef262359340b6fc7104d2ad0f67b420f66cb1f8b51956257942a2a5b515db3c5b03a09c09bec9284af2ca4a73507b4} +x{ac3f31a5c3ad602dceb6fe7a1a64e167b268bb8493ede809938e2213b3b1608e679918b37a069499f95596ee1735778c} +x{8b774778623dcc4d48a049d75c9d42ca7b4a66f79583377fa5ae59c2a3719a97911f55c1768ed655fbbbd16540ea6095} +x{a8ad6c5a569b856997ef600b1b1728e4ad9ab8606d90619e2fbc336d4562b9d38498f69556e651b3cba7d61a69e48eb5} +x{abd6f511bf89d9c18e7fa0cf6c8aa0f028c31ff78b5d980136e1c528d593d259f1d26ae4c79576a47af8996396b76094} +x{a4479f00869f56b0b389d79ab9e23ec2cae1460593f6847b3f2bf5415929db91a0b94aefdf093c99870b1f66c6a0ae64} +x{a03f2529e611683bfa7977b08f27c9f5b1eb962acdaaa8c72933ba22376cf461a6fde95e4102a25908ce056f14ddf525} +x{a1b6f7748d7c2f7e767773316c7f4d489bd5b793cb30fd750f2a33a02b6db34f64c7a4180a05ac6fef049392cf87e70e} +x{93528f173f2dafe0114d688d33c4eb6b33753fb53c54f50c3c7b5990d873593dd6faa65906f1df7c7eb1bc1f6ddc21f7} +x{ad8996fb3c231514562b9926628906edf4dc6fdc714d35a8dece3a235c3f49637534166d1ab0715fd7bfc22ec5d17e26} +x{b6e5ed2b40848c59db54850ca64971759de81d0601a56d552060f1a6d98305ee97667cefacead2c3dccbcc58b75b1c87} +x{83fb0b6260971d174b14377e999617ab4d2621982a3d26555c27520ff8439e2b3585d12a61f34e1bbaf93779b89c2907} +x{8b6cdaaf5946847b1e7b7c6d2d5fa8ea1e6c2fc9dd21641b936fbf1b32121fb3bbf65dcba0df83b822e370c9c35a9a5f} +x{872eed4104bddf37251a144cf766ba3bc01bd27904b320e42b6122831087a7da0c5a5cf8dc6c00a4c8631f7a91dd3fa4} +x{919532809f2e0c285fbb90a52dc9e3d5004eacdc71622bcf8144376c0a63b995a2531ca0d18a6e0ca4df50044626f00c} +x{8f1d94624744ba2b92ef07663324c7784081e22911de67b1dd1780942c9bde1dd9667914e749829d76e87a1e86170485} +x{94a257f057123beaab8baacf6d3fbbd932ab05c84540273f3ea69633fa9cefe0cebf778da45661d9dd2f617dce9fb960} +x{8a33dbe5e72280da6e8d781246af15e92ebcdaa0add650bc0c13535537efe9ca4d51a1a083c952427d77f70954a9430c} +x{8d78bcf42dec8ef23bd0fdc515115cef37a89d3d2f5ac0723374c4076a3718600b8505eae1faaf854728ca398dfb1649} +x{952601b96441c611dbccaa960cd3657de437e4ef6a0458e4078c4ca45c90a0c90eab2189c6512691bb615ede1a0d835d} +x{b5e65e21d0b97ee6fbf858e270bc51d3cf2a95e6364b94eba8d9b5a5817dd345aca03b631b9d30c3fa018c482184303d} +x{938af819a7f9662a49dd1353191b43f7c3ed87ccc2b4472dba0f73fd9117177891d6fdd754605a0a98fc9d93b3132d5c} +x{a83a552c4b56d70f98d95c68c022959b1f0688df8d13631ba960fc0052b7db74a58a5e56e7389b41c05e0bc7d2ac9ba9} +x{ab68cb8be7456a2086cd3695e1bda4135da91894d2f9b49560c39a9ae07f7d91ac2d453c4be9497a7b8631ba83aa9cc6} +x{a5e60edcf107dff7d57e51c250862b3eb8c106e40d452a95246f2be08d163a333d626c377d1945cbd514733b5421bfed} +x{a72f4482408250365ca1648b6c9a1a6e0adde5bfc1e47933bb93bb72b4418e7782a13abfa550287aaa9137d44ca1d17e} +x{a3e0045f65097ab4951747733730d233df35935a08b8934148846c4aa57559c0cbd74de20dad1ebfc6fee1b26b94b868} +x{a611d5c5c00db0c6c98c41c74f5f4a6be5523e39a2ec3b172e4b6f142100f5cd700d7eb21d432c4cf3b8dea3fb981cdc} +x{8ed47f35a890e5238ae1445fb519646c17a17187370c10c267723cb3c95613da9bc4c9d3fa5dcd0de052716b2f3773a9} +x{86b93b4cdc3b3186d8ffcf7c75472b41176fccf3845d83c5e0172bcf6d04443ec644f8916c39d07e6a3f3e947212fe7d} +x{8290d28317930e75ce905397d9b203afd6bdb8daac8f654317c7fde3b61f36e67e517ffb43db58cb09fc67f571270119} +x{b34fbdd89c0fe771088ce52e16e1a166cabd4643e8848560f376c56f8265e478998c5dc236fd693509bf1615bff5495d} +x{b7c3cf81dbec8f4b430a6ce91006b90b8e90d04f5c299161e2db4caadede926ca3a082224342d37120ef42254eabb3b3} +x{a072b63854a4c2c6d003d79fd959ff4e6d2fa22d90bd87de6a08b568714caa6b9ee1a43766fb813e01882a9a48086758} +x{b319caf57dd380fbdc537ae09f5f59f9d30f3607d9ef7410b2c2d8d88ff8e64e66154c363299c67a8526b5f07d507e2e} +x{878fb941a76dce335d77daba3bc41cd9d1b145c4df980e5be9868b37d3c60421e3509d46ed0ff3ea01e8f6b717996774} +x{8d2262fa7cfa4fb5c1b5cb6daeadabb220f10171e4c041de13ad9b5aba30599ba9e7f0bf2eb19a446a3a6b0fe6720fcd} +x{b249b90858c33e11fa84aa6f412986a886743f7a8a3b81d59c752baec291ad58d9d592aeeb26a00103b8bac4f882eac8} +x{b02aa595959b64b09da7de5ffedd316a98fa7e913d442e4bcdc08a584302ebef651c22a5c06dfb891504c53ceb15e5ec} +x{9077c6fa2e76347814dbb1c3e9291b4536a5d23441db87ebfcf453b2a07cecdd7a8aed655a71c28112c826ef146a5b85} +x{8368584e6e9fd52e8dd9bfdb58087e380b99e9e8cbc551dd40d6d6e4e5207d9384ed6295e03704c00a874ec280b6cf6f} +x{b34d3cc4d5cd507fbcbabd2c808c068ef41d3b8efaf7e5302ead08301b4f79324f028968dca7e0a56d628718ff164c83} +x{ab9ae1b9172c4d067445552f7da039141ef72ad4fdd82462182d7daad8f6fd3789d2d40c8174dda3fe196f8665c48f64} +x{b95ec7ef32d3b0ec97b28073cfc2eacdad7c4a063d5f31c791169bbff0ec4496822624b67264002b1a2eba048c4fbd38} +x{b2ed832b474cc84c4d9296bcef944a26a5eacbfbb411cf9834010d3e67127c36759a92f667b46078ffb9482fe5ba37bc} +x{95004b20bceeaf744a40289440c1145b4e8d4f7f40460b38cfe272d49b2abcf99880573ea0dd9defd4991a0620d9e528} +x{90a921f83766c423cfd9ef04cc3faf208dad5d52b77af9678842b8db2894c33d2d8962edfe2c6e8b477dd6a89223fb61} +x{986217666c2fe4711465a2da8daa7d6ee44e4cf32b63a09aba26204b371f296b086f31701e114a3233799b8d2052d7f8} +x{88a94b40813d23933c86e6ebbaf36531c7de89fbc345d7593a62c355aedb2ac504ed1a58aa735c2fabf82e73bba87679} +x{8441f6b6db07ceeb3cda0d8649b557e082fdfd610a9760a811d0302bc9da6c3107d8a22a0c752fa20ef82054017c20e1} +x{854ad0cfac2b94faac229ec7792123fc54377e0d285570ef1435aec0e72d072da978c00d453975415550ab52e8284501} +x{b0ad4b51e7c698f77828d3c034fd9b177a877510d7552025fd3cd1c68e405dba23a7a72d921c4798a6cc237887baf967} +x{a59e691f1ae2265788523bb6ac5cae309420caaa54d4feaffa6882c1542fdaba817f497c5a8e30bfeaea3bc3a6ba4b42} +x{841fdbbdfa848144fc7b29520199a00c3398ef3d78ac67491a9c0f933eee22059f9a0446fa599df627336b1fb84b0145} +x{89a8c1e13030ff719ca22c939f4c84b1bdcf0ed11b6d0ff0deb1c3bf9c62640f80c96362e4e88362f7f7d69de831f523} +x{88b306b8323a1ccd933782e31e94ec070117a02a8f8349b445da66b33d1dca77e23256644904416751136bed9adc0d45} +x{a77b375d16d85257c5f4b996f5514ffa8a5ca31454129d2a25b4bfcf4309f27c97a71e6c33ec7fb9cc57914b3b7baca9} +x{a8098c4186848d9d9ba4a4b1af79decdafacdfea7b5cbfd3536aee6ed67653c6129557836252b22421f61b66c84b2e9d} +x{912569e76a70ee20160774d2956b70965689d19c8d64941313fcb0d61f35b10648860f85be7a81e90ef6975730f2f636} +x{b144b4047dd0ece9b3619bc04e7c971aa608d9143608a49c46a293df2bf2c30facfe4d39675fe23f5b7fc274d07f1b17} +x{89489bc8c8ef1224ed29d5f82370c6c3fef80ea23809673414309a1f4b2941acb049a0648c34cf0740f682624a660c3b} +x{b72f1a424b0c3609531bb9f5299ee2120066dd5561e42a3beb526f8dd32e89cfc51937fcc668834414b7c16a1516510d} +x{8a462a5dfe9b78315fb9945fb6717a27849785d98744d3bf798697823e1f8e6acad5581ed337d4d9e212f9dd96d94ae7} +x{a4d046c2d04306fba6c56bf0c9f546ef1a0b5c5762484c5de70e42d1b6a30d39cc80b22f66933bf3534b1d26fb6cb7eb} +x{ad8903641d07eb96663b0a2fbac3115127b24945a138ea6617e14545cec689dfd162422818cc4050a23a8df42cb08ce9} +x{b50cb2de4074397cc88600126c50da4bb2acbb02ef90bb3b370e1828c39fd78c17902818556e1689e5a7bca50237aafb} +x{8f5fb00cd6e476bed5b5562e934383cfc2f1a2b799e9c1f3dc2e0b0ccacee4734a31ff2d9a986e7c0251778e41bb99df} +x{aae8828201155e602880a4a9137d3d44c01d32b2374e4223576e8fa4ec56411aded934c2f3121526fe2d834e37c713d4} +x{8a074eb84f1ae6d049947cd6dc6bbbbada2aa4209ffc167fad467b44ba3926f3b6ddacae7186c9e155e233226c7fd3e0} +x{a85635c3cb2397e961c821cad4a9945bbbbf53c20721f9f1dba9a8c616b2819625767884e22757c29507f0ed73304c43} +x{809ac8f6102f411e5ace89bafee2b73c8a2f5340d7677c0877eb9de7b7d522d77b79499bb27bcdec06a2434ade31d40a} +x{82134713d5873008449747fb04026b02790c89d9656f2d2b3c22fcdb4e9bf1b328e4051200afd86bf2c7ceda424dc447} +x{96a195a8bc8e61dd270c9e1e2b2ac60ffcce8156b11cb2ec45229fa3151a6f8c1a796a752312ecebd5c5bafa2ab4991e} +x{89192f77e8caa08c0a976650701206da423fd3e4adf4c11c0b12cf693966e2d32a06bec27de11ef76cb57b1abee47a12} +x{89e7e76e55b1b3bde410d10c8c32d0f10c63e1284d4636f5ba73a285abcc9def139c3e419ddce83ea07a9f825b441523} +x{97d8551ed66159c461b1679ae0ae0bdc34f306f4cad6cb39b77999f85ad976f2ee2218f777e4e1368133ab31604df540} +x{941b0de89934fde7ad4dd6ee46bb1ac0d1228649469969fa9e016a51fe63f3a687b536f53eb70e867e11b306b66954f9} +x{8ad57d3d490a57d2cbada20ec81812c429daf3ae6ed372276c1c527dce09737ef4ba3bec464c44165594c8e552b95f55} +x{a6cd2543e60f86f7c976df845ca30debc686413418d559162597bf34e44952243906f249939d06d878e1d942500a7c4d} +x{b2e066f07c6cfd5e045f94f39f1e040ca09cae4b1b8e9cae55a57ca85301787e772f51b15000fbbfca19b1d28341eeab} +x{88242a0a598559686228cca4b72b47d503276c6d3f9270d943e43f6652ac600d91a6245ae3ce365b20d8121ffcd2c7eb} +x{b47776fda2429344019841061308ae5cc3e39e55355b4fa2bab80a79be35250cf38127be2d3f93eca196ef8bfa3049f4} +x{929c6bc299a6b479b7a5ea1a2c73cf7575161f184fa1fa536fed71fce16cc0127df765c9c3d02d5df15442b50e4d7c81} +x{87fd7e06cbdc2e1ffc1a0cf328870fa73cc1df6d4562508efbdd0d71d97866d8890f085e02a8845c8be69840e73c0698} +x{855a53160cd2baff4f69b01af39a8e6486aef4c8fba5b23fa958f8c4ae9a4779e3a171c35ba28a23b63d5a746e0def27} +x{b4e1464b63413c94f636d648ec6e9dabd357c23d7a37c9fcc3bedbbde7a94249c6dec2192702ced872856c92cc829da2} +x{aaaaa6549f519e43b3935cc1d9edcac6a32209ec8f9e9eb8598e7ad1448a4504d204175eda00d2a8bbeb736171389f2a} +x{8a6205892d6986bcdc1c3f8deb7f7301cb9be41a131c7c046bd2515e3b36f3253fdfc8609c80478dcc51a7b66b279a45} +x{b20d4402ef47455ba7d8f4f9bcf9a7c0d402396e6aaa14d3b1a6f1f5d0ef6705ba9dca6538d0e9fdacde8a5e252c786c} +x{a30880c55a04b15d14381001ec06340bf8d60f6c952ec50dcdd8222bb393f88f635dc85d99384a597eebde0693424d47} +x{b729dce9e0bf10d615d74931773c3dd81ec38050f597f7d6005def587526d4c587d7767b30f4108d83342dfff313c74f} +x{a1cced77186993b4215c66ddcb187e397e10a8ee9666986c6ef2f41398bedfed12c087430d81f4a608da46d275b9bc04} +x{862144b431ab9de9c4ceaa784858a2c6788b3758866a228b8e2671433bc56ebe9918a08a3be11d05a7ff9fc64ca882e7} +x{b4205cbe870360c5452b2efe87efe9b4aacb4f7edbb88ff2e414931a8613d686608b583fa9bc053ce96bb9930d4a4e8d} +x{8063a97bae4b2025eef8522d73a5e3932140a1afff478765dc1279b2bb8133d4bfeb256861afb213f2ace1d885b514cb} +x{a4f78536213b3cfdf5ce14fc845cb0a06726f80726740319c812c938417ce6346ca2cc055e2735eeae9b8cd1aafdbe9e} +x{a82edc8ff174d91a5c195a778aa265f35a4b7adb596caeddb566d86b15b4ad446ebc4a7c34889d0f69e6b820ae5ee2d1} +x{85ff418bd6582cd00828c5cc5fabbe68927d8cff526f435bdac63071707dc2f6cf0d4f916f2e52f8a341ad8e3725cf67} +x{ace11bad2e6d2ad1cf84b01c3f194c23d5b8e96840e76444f42a063ef357a3f5376e3ddd3b2031c99400c2f82afdaedb} +x{88eb0d46e735ddbdccc6d3763db910420a2c864895f03f3537dfe49aff54784b181d2d02ff37165b29eea18b529d8f82} +x{b904f873c0cb532c4633dace5b389ecca9513ec84562ea714d52e1d359c97c567c0267c4c747f060e5f6e3e2fa0f2c13} +x{b63791ae81e8abf907f7a6a1091d72e37b655cf1830a6a0bc634049c6f9f4afb597c4389b9636c7fe5e8db52867b26fa} +x{b3294d1fc9c45bb0f080db57cb628faf8e2684876c853390a548ee78878697030c932b9e5b5f77ba54cdc4162b439400} +x{a38e9faa508b2feb73b4040a0d79acf3bf39bc5a62ef30bbe43c8073a98f12ff9e286ff1490c8c9aed3faac16957b7aa} +x{ada99e8309ed1d1fa2621693c8c9e5d618218dde92888a3aeaa0692754e7dd94a4268afbb75550c2dfeaf5764adfc0c9} +x{9232ab744f72c5ae7fc674890d07027dc5541eaaf8dc1edc8d8fb4d5c763053a53697d9e5100ad9afa1e9fc888cf1197} +x{8b87270d542706c9d0db167db7302c5e3fa81642f8bc707eae2bbbbf731b3948dad68cf0dd1c1d69f61a911d1f8e87bb} +x{a9664311d8ac973f6ce364f7033c0d710a98df5ac6d7c3d1df70f78a33701bc0a9a3b2da1113e1667b89aad41c7d1b4e} +x{a6d02f30da6b850da8377c718d2d8ca90d2f1d352edc0abc5c0af2435dd180be4d0e1cade63382c990b8ca930dccb1be} +x{824c864ee1af1691d009e4c7d1d201da8a691e93b62523e22aca2e4b8a5d9502b6ff941851f8f46a203589e093539bab} +x{9873aa35cbb7235aba6dfb17cf8e046b84824b4598acb8cf63a20d26cb242087db54aa6d44270e948fa6a4bb3b1a4aa5} +x{929f1a544585afaa2df9f393129bdf3a0ad4e981146e9c9fd2ed89b1ca61e865c88e7347b07121684229e778b10be160} +x{97952ed5cb54d0451e8cf08cfc29a423a33a1ad84f7e02b00cc5de88a27b23f77428e4c3a49a022617229952dd27ba6f} +x{a49a6e47098ce9ed5d432aeed22a981c05dccda0eaeb25ddb2452d2c5c39f53f781b0840a894cf70804ed300e90c95b0} +x{ac3d8d959683d6db8298d6437aa22378375c5b3a4fdb416925b71646e6f7b0087d0c6e65d4f9beb0ae72358e99c1f5fa} +x{aaeae464badaba7da40bc59ac873e1f07fceedbcac915f33a94daaa72688c3c49e5717300abe2571446b010416b2dcd5} +x{a2f6caa19c0a43d3d6a3d1af5e8bc33a0bb3a54b8f5f6d434bd9b3328cd3b6b148168e554a0af10f47d43943dfc6566b} +x{85b48b545c089325b055d2b34666d091a07411272aa6ea074b550f9c9bffe118b1f2f9d4cb903e9caa39fe0cf1ffcfac} +x{8db60a483dbadfd71f08f6460027f9dc608df180ccded4a2677e88aba24e72a80d55b783ce84aa787049dc1ca9a53512} +x{b0888ba78b37c36f1c61064284aae6021722ae38ae8f64e6da2c3e13daa9396eb5520b910433d0ee25f36162a5d69ad6} +x{8e6777565dd463b41aba4c7a63df9d1ae5b6b5b1d7d336f67e86fca8280bfc90c1d7e95ace60c34e6b3bf55d28bb5d2c} +x{99275fbc4baac72896a20267af060f21ee65293b7cf8b27d6495d559886ca71a9572f6699e58b63ff2433d760ef2e31f} +x{85b16af1b7c62f0beb9c801fd36731dde735c9ef6bcf97bd29dfddf2ec438cd5cb568d914c7701080a7434841ccb0f1e} +x{a87a5cf9d7c130418cceb93f2b26747d3f70d8a6eb8ff0cec28ab969bc92addb4fc5dbcf5d18dbe21e150ec78a755e07} +x{abcc8ccb7854b76b80a0e6e4d390df544ed8d09daff0818a71e8adc7f4b6f2267769017db965a404665032c46766040c} +x{b90e7b9ca3237916f211c02cc1575d8b8df43313f54feae4c78944bca535ee95e37b63729910f9fc4dabb907f3e73acf} +x{aa968ee64eedc74b51ef13248e6ad4ed8fb96eb5e499602889b971c17fdb126335f59f98952de0e5243a0b486e2d4e62} +x{ac49aeaf558fcf8086def396c8db61cb80ad416a94af50a1b583c5814bc7d54bcaa72feae09a2e162ecb1d342608db10} +x{87ef03700a561ad0299e32ae473c1d724a391e3523636e64c75d8676eb2856ce8189a9e0279922fdd7a9365c2a483e7f} +x{b949a74107e0c6b93cfbbad5dd0a47f64364e5c7ecc55cca0fad5f85c4bbbac48555144b7f676603a5292fc1712c1bc0} +x{869029064f1a86ed00ea442bbc5d330c7a0ae4a06b5595c9a5b57934f6876f3bb96d9dfaef1e926428d14e450624be57} +x{8379963a7f188739e953a4e9799ab37d5c49e4b8f49dc9bcb686371219a04dfb6c55c441c59b2ad89d7145ab3dc08631} +x{8460a57fed7db8691ed1cc69c66116e1544e164478613ade64eea6b3926c99011d2cf386e0d8bb1681ef9a5d556f3c88} +x{8c9cb7d59ef8fe907074382c959b661665dfe9fe4fa328fc93752cc2cf80d431b8799868eaec777013e67966b37ee0f3} +x{85e7cba0dc3614869ccccee0ccd782554ec3821a8d6056daf10285973bbf548f7c7120627d1f0edab7bb29cccd4fd35a} +x{a2ed49318a58eaf3d2211f3c3573a522605481278c5d5aa06ae610928845f030b3455f8cff3cbe3afe08e8bcb5839b31} +x{898c2de6094e5d40e725faa69caae6ae499f2ce0d13ae0fac39291869852d5f9711d447de374942fe7281030cf6a4cab} +x{b59b4d2f3da26779b0c3c33493fd23d78d956e688eec9df7260e622e2228b6cf30b111088e6f1aec6e690a4ed80f086c} +x{af9916b2d4c305532662819fdafb544f4d4f74a3bf5738cc31cbfc0018310f0848ff749be71d797fa287a7b5e2774e88} +x{a6eb7c89dbc44ea9c7439d0ff2a7b302a5182020743d0e54d84048ea668a012c0a808aab9c7809ed9920f75bc1df04e3} +x{8da5e1e8173da17fb2527d049c746f216013a0cd3034be30fab97314d67f90638efeb394a504052f84dd3314f5a6e1fa} +x{a0a9dfea9829381a8a8be0dd6ae9df703da93546d93e5a8e2a0895d0149894fc424c5735b32517305a575936e6d0bd41} +x{afc39ec1ce487612179154f7e36e485626cdcde18593fabcf3a4fe5743abe57d6de0938d1e5b1f45e3861cbe9cff2107} +x{8182fbea440a49e1f1deda5b1973eb1a6555d8bef74271ea56489508ae847db9f2a2e0064c8f25c6acad4893ea3556a7} +x{b3af977dad139de108294d0fab8dcc817abff1948f6ed746e7f6aae1ca15ff628c4d9ef871481b8a05f3e4eb060f1569} +x{b5b23529ae9ecdad0030248957d7135bb0f2212654f8b06b921f4f2e9f3374cf8314ed08a2020a2b81307db91bd31325} +x{a05aee8260d4e96ffdfc3764c6aeefee513255daf8abe5df1e455ad9dc1ff826d0e57eb59352d6f3ca805f854ced20f9} +x{b3d57b48afdd734921b826b6e165fc7ede42a6298d795cc3df87a659f2920e21f6dcac0fb1ec28e145e415cf1f75f15c} +x{b9f8e0bb3f230f87496e6ba4ac104106426c87e1e6732863c28f4ab8b6d752c00d3d935f5e33669045c38a61adc09b5f} +x{974e00079eec61f2c2dd74a02da965d367ac1f4cc1e128af147fcfd4c6940d2e0cc3babcbf5514b2beae2882e1ee3e14} +x{9539571783ff73f8b5172728c3c5af82a1f36562e06e4e79fa8aff690eb5e4e6cb53f0fdc2a1fc2161e638313d379b59} +x{af64b9701e9073ef4cf2b5345190791b0704dbd28b3f626042e1a0c4fa215fda937e996dfbaa07c65d9ca5f96b8f61fa} +x{a10eeb3b9cfc1761bf9a22bfdeac39fa830cf2c52e2b70b82d99c005467243948821eafec82310ff761171ca4a5eccc4} +x{86a5d807681cb61dead165e7b6dc51fbc4d87a3e1ba203d0850a88fa6abc8c443f6e59d0367375e04166319bbbd70653} +x{b68306c1ee04db2c41a5599e8c65b36f27e2ef2dbf74153f50f3ef5a8566a8522268b7131ed8f6244a8e9285b8cc1363} +x{b168681033750e64fab4b54121fbf6dc64bb214eb9bacc00d199745354517e3e1b110ad882264862a91990400eb0d832} +x{a5a41fa143084456fc9240f4a0b0959c6ab2e4c77a8d0fbacbaebaa7762d19cf25e2bc9bf642c3dc4bd52ef9a125d9b6} +x{aa1044716ac0ce361fe3d7cf79cda8ac7bfd5d75c4c9a6385e54c0f058dc37bba7a98066419340119ea957835e3f5062} +x{8316f76c23a66cac4d13966c641a8b1fddec4a8a1da95e418d3c29a318123dcf62680b3d7d7fe989200f087da31f2d47} +x{b3520e472e5206768de36f16c59537ba7a5f548f1e606ef341be57c61c69b36dd3e9b6fe7fe8a36269e9f6b3b6860612} +x{8e58d534f013ab17fe6602ee4a477187367ef4c48dbabedbf81433243dd5138aad25aa483a34cc96665f6fc2b0c43ce1} +x{a53b58680e5397ace528cc8b2fe5e9bb72c40eb450c3fc5ca787cb95b1da733b6ba759b2df4e9e2b774e0423b57aa0c3} +x{9124081b9f24d896cb79f75a667e58bd97e7150bf245f9f50d3556e1108c47c73bdd153e65d75641b324ca75ef2ac4b8} +x{a6f86b395f56ea59ddb0f2329aaf2fb7ad7779b83e9284f9303dee3b29dfb318196f836f8abddcb8368a8e7c2cdaa462} +x{993bf4eaf832dcbff2f8c1e8009f779189ad123bcb12ea9c46e0db23dc0772fb0cbb89796d9dff8cc3c96a35efe33b6e} +x{b3fc6d1fbf08661dd9912d9f6badde61cfd6ad5a8be87024d9b25724f51d0fdf746b1c7ef08fdccb7453563c32d3b753} +x{875284793bc53f9b3483e59ee05094e45c9aaf49d7a94d9d541e1426fd02a0271f6619c1d5dd2ef2ffbcb1429ac65ffd} +x{87b53aec91540ea4bc0d89fb7f863a095e4fe9644b433810e75316c0015663cd0087b5044bcd6483b6de46052439ec61} +x{926d4637eea543e4d3a97963c9644632bcbce065cabffe5af044c73f43294446dff6f502b0d43978121f8783e789b784} +x{83709d410068ec4da081a18cb1000401741e652d2db38fecfc33c2b124cfbc2497ad490fae22c460905066eea5a0295f} +x{805b9aef8db25a170b664495fe825c92c737c2c2d42c982a0a87161d3e7ded0a75603946fb68e2dc05525a2617c950da} +x{844f8360c42e3472b8e1f0515be2b17c57263569f40e931e96ec456338f89f75f41998eae7d78a32d97f532dedd93ef0} +x{8f8a897dcd7a5a3c7bd68c3e9d7a742c116273337c15c7518264dd403467a26647573837ddf08644165fe87dccc8a263} +x{8b83e1982cf15c9910075224a8009c3363f2c1c914f2d74dc88f2d1acdb17bac75a838a56d123f8a6746b611b1105b18} +x{b27d8d49ee63ecf6f4ced8bd515c4fa27478e8a70344ce098e9cd3e6dc4712f3d24e47f93d23242571444cc726511865} +x{a3252f0ad64cfcdfe330f8939f8c9ffa477ff17cd45929f7fa62ba25a9cb5af0f83a070d141f315cc6e82f4564ec4f64} +x{a6bb5240fd5ab570f4b5bf8836c34241933973f0fcb537a124dff7ec545c1c55d13cc6c23ba1b7452b9aabb2b66dcb37} +x{af83b1ffb771e0698f9b2a7ffef6df235b18e89aa90a5358abe82a2d9b077e08dc1ee11869a53b95519072adff5d26c2} +x{a990df005e123fc73bc97c79ecf34dc029265a66b530687192230689aa6a96d4fd8d71d5d6292566d195b968184cc3b9} +x{b6db2b4d155a26adc7667e84a98ee9e2f57ba6eaa1a0b1d966502890a060ef5acb1b9ac0e0cd488b0d70f356d4ab1575} +x{ad23f7a128230b69b92156de6d12f3d7adca4f17e4fa95ad1e4aba93afeeffa1aec73cfa543b2e9387823c95db18eecc} +x{b021626d7882e973d1dc142833d7b78b6ea7ce6d7c5abc4e5c4232d61d9b8d540a01d0c3726f253702335a80d4873e3d} +x{97d37b022828ccbf20450561fcba5febcbf2627e2e0e61f0259d9aae32b63efd795e83b247da7828ab3a52f40b055c8d} +x{972829ba8a181be19333088f332b80506d5519853ae22d50a1be9fde4eb181f1a00b3e695da8872381d8c663e53ea752} +x{a97af7a23e81bc3b56d245bdfe656ddfbb30b00dd05b358b29c442c6d440c84c46a87b9df9bc7706ad6eb004091783a6} +x{b062a29f4441bd839232e2dde99e329f7e6c043f7f315ee156852fd557987b04ee911ad8130cca4c49cc8e09ceec6e81} +x{a8239d71d680131c1d3857e13d53f11858d81a92217b37aeeb19b634a02441082d966f0b66e7d3f330402f8436ff8424} +x{a1ffe6c1a89d5368bdd0366d4f780288d7856fa6085e85945c382d40931d2748b2db1cd443f574847649376487bdfebc} +x{aeb35f89d305071d9aff9cce962a0320f84e206dbd927a5a9554d5b4e18e65734b1e7c81d02e245b120c1f7566d9fefd} +x{9935f67ac4d2ee6a4c513e46220d67d5bd6bce78e73fc3acd7e4b4ea895f7c99d44c3ca6400b07c925e7c43c97805301} +x{b606626eee2ca8f22eafcc063b961b78ad58e67295f9e81ebebd07834d5fb7b094476d4cf4ba977fdbcee58b8ff91ec2} +x{aa4f50a49daa01f4e2b92fd58f30029c0692fd529cb2db3aa3a6ea0f990df5020ba8c09992cced29d50e88e77b9eef1f} +x{90cb4f0932eafe48081aaeaed46c608fc0d5aaf7cac29a0f28e7a1826882983136f5e91f3d47b147b1f1d252a68cdfa4} +x{8610084a4b1884b49f1a4785c8a485cda8d0719510e0013b31810db813dfd115b31eb1c3e29ac17233e903e102f2f858} +x{84c5434f9c4554d0d5ac44dc84142ff00c3fd20ddcf207c1b899399d71fd2104d408fced0b58d3673bccc56123e7a62b} +x{a186868726ea2fe22049b1dfaa62abdb42ec46ae860d2f60546ece07db5402383b6bf3c7d26781c393a611512c250b38} +x{b2fb64b19612176bc0961538c8ef5d5d167c98f3d43747d95fe2f030449efb61a962fb0562bf4c42e7bca96cca4bcd17} +x{907261678b16023479eac3a3d921374a6a42ea163a09e7a975627b23043011711f7729efa8a39eaa8b97f9d19a336b4c} +x{872246987efe00f8f6a24bd2eeeb6b431204c01f3dcd4ece3b680f2bc164fb65f671551c9611d892a313ffae8ad35bfa} +x{8a35e8cf02a79ebeece284cd83d4499b10e8f3bc31f0bb058dab05fed30c7ec1d790a1dcf22685189853d3c8de81fdba} +x{8c4251af4d9e27118bd59aaaaf2fedbc6a8d5dde289f66345ac64bd566d094e961a6860238f669e2b4170a85022619f1} +x{9798f348d7bdabbbdfb6d2345083ff74ac58db16a6f3d68cd972d8c58380f3f455e589a91beb866d6ee379b8801ca20f} +x{932552f80b6a1a93dfddde98362f1f6f2b753f3fa05d6a21feeb47caa44548c8052143e3f71a21189e22bede3b700fbe} +x{82a0a28b240899e36db8f0ea641a01225c0e148dda443929f1f4dcc7020dc64a56532c0e0c754f458a78292e67b01098} +x{8d131e2eaf0f6ed5b293f22a8523d3c99f7456a5ad866100ad11141148e4825bd041d6870af2db4b6068841ec306181e} +x{a7281362b96c621f04e4f9dad0f09f2fd880171649efb46381ac5846a18376018cc43734971f977d18d1751cfb05c709} +x{a5d0207289aa93655aff791bdd430ea25ab93076d4295a256529411b4a55aa25ed8b3433086512dedd0255ded3ff9a63} +x{8703dcb528278c5c65b410dad0047bf2257f988bac6a11f1fe55e9d9794fc2515ced5f9863262d13daebc487ba4d4cc7} +x{b080c720c764ee177c35cbd1e29096e54feb2c29c68c7b5279916fc32177eae21bdf8328c7477e7a7feab59182f79c8d} +x{a98842b26ac4212e620a9a09018ce4573c02bb64a37c4e6ba91f972744e73b37a5c2d3f84c6cb59bc00ee25915e8a918} +x{aaaff1c584152dbec4fb2b50b91abd0703853a8051cbd3bc536e90aa3b65637ce77ab3dad4e9d6e8f3def0ffc920f9ea} +x{8069968e491d68b934b27e91a355249b2b1999cfa2534b7cafb7adb7a333af7920ec7b628cc5725630c39462b967cb7b} +x{b4da9f8378e711b30c3dbdaea9dd95e0d43420f7498f12181fb5bd3743392d5b5c0f12c1121bc2dd6f325a3d4c268bf4} +x{ad234661b80488143b93ee2efc1ab8eacc50a137aec333ea8141b426e5fd4070c968618d1a875947e08c8428e68c88e5} +x{823f71e9139e4982ce3b5dc7242a61876fbff25b3e9c1b532b82895e8624942f5e57af2c7819a80d716e06e04b921250} +x{b8f32a192438bf2dd454d0d9b831630c54b65a79a79611ab00386dfd99a80e58101df5455084d91a9a9939e108670214} +x{a5f68bd025fe3fb4921de2a081381870d41c85999f1754ff66d270a74794c7ad50d2fc01cf01ae1ef63247528e02aab4} +x{b7bb0c34c3e5da34302e937ca40a53c980de5f43fab50299f98b6d44fde2603355845030a29c1593cf342f85a8955450} +x{83077ae296c08c8aae4bcfc79b2828403433cb6ac48bb5c48eb85d4ce09f61d916947354e49183fe675c0d7f5905fc4f} +x{ade42daf027ed0939428a379dee1d6f52d4d69bd8854a5a5b166b8b1715a5f51b3d7281a0a2a4fa86524775c694deca3} +x{a327e8017149cbc021a583348c07edf1699b26bca4e07b6f79b1bc9dc030b77512f2968ceb47c8f72c81a55dbc293a58} +x{8dfec6255438a25f4076480b80786a58eb3e5d63d76e30f95e39e590a39d82c405b33bae71001ce647a1e3b3822aa2d3} +x{b744e6c66ac37ad90bbdaa8daef65b66a41d6f7c7662e86db742a7d0e52e8b3e6de185c99b53d1d75bd120763d260e1e} +x{9954e4646ef602352b8316de9b9186ebfada10718712d0d7ce646dec72a4840e288f6029aa0c2a0a4cd1749a3fc49760} +x{aeab8fcafa3f35b2b6585c7b573ab001cb1b1eca62839430010d69bd626bd522a1f64eba0a72d7e35e6081f8f24f9ecf} +x{a92ec41af6d290cd87c272d30a9c6afbde28daead0266987cbc24ebb3d68793b3e3030ef1c0fd6162e5d6067189e5b0f} +x{870da719fb854d4e97bce5f84994ca582a7973f85abd172682083bf974e07ab0217c56dbc8668d978510ea6d49ebf0e8} +x{971389abf73b0b18a85a9bf1a5b78ae54af0e9dab2810ff2a235b106ec2093f31c6144b6267e143b79ffcd4c79ee4902} +x{94f929a1834d477597f4d1047d09298676948e7136cd7554de2a5bb89d947c476fb3a9391adb0d804f0304bdefb2c074} +x{b250f0c4310487e162a6e8a8b2362a6fc643772838b4fd0b330502f913b04ea55c88212e83ec5a8103408511905807f1} +x{a3b0ac5a206ed5a9032ed5cd213751fcee66c7d629fe473f09457b9da9142c957ed39c0090c7eef24397669b0d361f72} +x{a151ed4066eafd3ea7eb646def1ffb5d14258a603045e37cf2c8cd7248700e7e1ac3e7f852e2fe7ad5691eccce99252c} +x{9711657a493af933761b8b848b5f09fc872d4ec85e3567c40bb48cb4f922627c0088d173d835b115811aa095dd8e5d82} +x{a5e7f4a06080b860d376871ce0798aa7677e7a4b117a5bd0909f15fee02f28a62388496982c133fef1eba087d8a06005} +x{a821c289f883e5a9bc76076a7cf6f6ee4dcac14d064c76d4a98756f6bfff85cc1fd45f5031b0c491d393869dbf37ab58} +x{b5716cb955ab67df3f9035325e00ddf29855f010fdff09c708726aa08196807ba946cfb70a710dea25ee8209af5cd38e} +x{b370f1a22a538bce3680d8094eed26372201563713ded3308c2fb4998a4b30f92f57616f2d859ef0806dd6da776fb12c} +x{b144c4ec1624abdb6b0b006091ef941f4f785751d114d6bc39269da9431da9991fa23e2b0f333efeb38770a25523e820} +x{919a189511a5d7590ecf34fbe5f36d1f92244db0c06aeaf6fb56569abf30ffb71cfc7c42913a6483a57a39a0ccc90b9b} +x{b9f830b001c45c07a83d1ef9dfab49a9c76fa9c13d62a0678e4336c6031bda6e07223b87a7302decf09e72364121ebeb} +x{af014b3be9c6b92fe8ca07cdcc900c86611e35e32a758ec468c1afc13ce640a7375482d51b8681903372f9c5ecca7687} +x{90293185a10067becf6f10dd5bbcdad97e189504d6a1df630a1028103613c80c15b29e0fb4109337b611c66e09ffb050} +x{b23acf81c4f787dd63e3f8c31ba9dde38b44a455a18b04f7325e09bbd439e5ee2a75b67d3673aea6d2476998bb2adb0c} +x{916efb4510ac05f36693e543384a54c875c5ce811f3bce476a294d00e4d64f27b5ab4d4c4ac7ca9a2c46a2c317326f72} +x{b76b06907a9876f8538e3f58758aa95d23d449dad3caab1e9708ebf51b09b9edc29d04d60ca3d6fda0e2823a4437b0d8} +x{83c0ccbf5f94afa5a7f5e7b3fcf7eb2afd2e656ff327f357f1a4b36f630fa2856ad1f07e51b38ee3a6e92d8075f85fbf} +x{9527d6a088468097aee5e3f0c7cd469aa666f096f054432d2a34df504217dcc5f70b45c7a4923b84b8cc9ca9f4a4b23a} +x{aea834fd07048eb22116cee9cdcc04c0ca900b5ae15e297091ce64c5631cae870ca13323241cad96c81d1ffdee43495e} +x{a9973ccb82ca88c7862fb684abd6814420b5f763416527a4020ed15adb1d5280b71d21e2ef280bfeb3f64104fd0410df} +x{a85b5024a160e7ed3467930b62c1346166e3bc35be18c9a891f19c5f343d2ee3c2702e987a4a1b7c9980dc4e099a7494} +x{a279c5583f81d1b1c1c7f6652bef822f0ccd3364de4dd56132a70fc4dbde3e006c17bbaeba9ca3dff6ea0e0d7dc0bede} +x{929713e325fbc3b9012896497213781dcd7edcf1122ac2e71bf0eeedd4b72090f9b61780f159f89066aeeb29d1b0bbb1} +x{8da3be746b47660268d2f6d74236047f5cc5291b68799ac8bf1bf34e1d035e4c203ecc415e71381a11b3b5884c0f263f} +x{98742a51f3d4581ae8f22b8c8e195d4e87017f502c54862d7b76039958d90bff3c02c07cde47d3e0d6877b4937648bef} +x{aada0601bff5813cec1b849893e2e6db5fff6a1beb605517c77e4dd2722aaa41510e528ccd072a76c10f12744d2ae172} +x{84ea9203ef391535d61ba30fa3a2104acbc59318c2da90d5e6f5df40ae8e234eeee714af1b4715200c471b00682ec3c1} +x{899047e4fb2a2824bf3c612a1040fb6c71c1f7ef0654177144d236455b198ddef003b64bb7c9d3f0da78c32800956e2a} +x{a10af4ddc28d1794167cef4b751022d8f57860a2618ce05a8b5d87c95584f1c6ee2774df7de60768ff81db4f432a3211} +x{93614feda46e422eefb30a724bb519c7c2ca3f743546806d4ba6ed59bf8862fcbc1f3fdae9f17c127255eb6968780b17} +x{a1f5e841c97a4ff52b2fdd00164fb77d6960cba1ddb3477d6b522a05521aac0796dee38c7a43e10d9c1681b38d8892ff} +x{92d976efe2f26ed00fe867c789407dbdeec51921e718b1a18461f795460d3bbe7cd74ea817d1bdea149423f0ad8393b2} +x{a2a3e37af6c9bed27e8f5d7dce5eeb9cfcadd2a942e7e9f84c9d95fed6b1dc7ad7d079f3f5bafe2d44dbbc67d99b0a5f} +x{81ffe874b2efce3625b5cd61e0d276d4f460597907a7f862a0a17a8bdd6778f7884e6a9d80e4c5cda224b2701655c96e} +x{8d2a05160ae509264ae64e056518a0f3ae2c197c2450d14d7f30a85db58bc7c562b5d450fef287d7355e63cbe558f44b} +x{aa3284d0d6e875fdca38b4009f57d7185dd3d8fe58aeda254de4e7df3932d446b80706f754c6d36ac230877a14da2160} +x{85a98b4ee6889d0fcec0fb23025e62a0bd6acc92df9cc2ab0cce430d7b096dc3de22f79a22ed727a5d15ab9257216469} +x{874bf95557e0094df23d3f1cd41155862b755482d3ef1a2707f341a1c66cf5f3a9edf52df5b167940eb1c1bfc565fc67} +x{a8324649bb016038ab07cd91879772df95a952973a19c34cae6af7e4fefbc545a94d12b23a2159e48bd049f57f5e93fb} +x{83b8c1dfeb31c8b61d8ffebf44d54a36af52f75aaa0531322020a6b04df9adb53d9a8099c521844f4e88d6e6aa36ab3c} +x{abbd6ff3a89fb9e14ad17978f088eb4b60af37b02835eb8d3fb7e7ef59342b68ca6ed1fce928446806015951caabcd6b} +x{8e9e5baad181d47ace0c6b5e9d639a2dac88cf667c0a90610267046563afe28b7be6ccd887940b5261579f7dbe6f920d} +x{b3f0b8cf0b5a7c6ebcf0c46c1be6d3c1d0a5a8ff5639188b3854206f2912f384e9f4d13fe1de35da1ec0ff4cc0917c56} +x{88132b949584e6d96043228bd589ca6438c594fdf91b6640f65932f379cc94a02e294e9d27be40e3d889b63d33c388cd} +x{b449cff96f2dfe5bfa879b713b081d48898aebbcae376c5cb32ab1f2bf3a8eb329f3aafcf6c251b55c3622b6a5036a10} +x{b705ff85c991b76587c7ddf9dfa144b25d985aa85b0b79e3c21d2229542388d9e66bfe8aadd511229e64c3c14e06bcc3} +x{a1f5ecdb881f30fab751b45c17326d04aeb3b67db946b37c3ec9ca8c5608d949437612e0d08e96c393d5b8110d9fb619} +x{b53734cdc0454bb8e02789f20b414e3461a73a347aeed2c331ec625f35cf07b44e676d568cdfb1fda7e3bb46fec2a179} +x{a496d69daf6ffeb228106883ddc885a7df0290527b4d9ff902b446df6b220842c0040f9be9fd08d8e2c17731d9d6355f} +x{89532cb6bdaf72d8e430e8baf25a94ea475328edf876659039e71b1804b407964ec043508a81fc23659ac5d225f81752} +x{aadb20736b040c633d4790e0d30748ae047e86efcb5008b586f1bf2dceb99ff4659711b2933f50096ff2af89d9bc187c} +x{b1d65445dad3417235c059d084f23fa5407c2de501f3501729bfe9952e7a03c2699f7115130bf3cd7f3878be3bae963e} +x{89cbb614041988d7f15e4a2cd7552b4c54afa224839dbe5076ddaf4ef8c30e661af4080f64245588fa4ff71314a1ddb2} +x{8dec221ff90b27567f53bea9638293624c5fc64572a32ad93651fa2172b37132195b3d4de8b89df6809d312ddec8e554} +x{89700f819dcd49c6c2c6586333773f3a67043bbc780c9ca4f05dd1a48035138c47560dac51c7e0e58cf238b910bb8001} +x{96abae0439028d53d35dd4c0ec26dc8a4ba1545726e25620f0a5ce3901ac7b185c9315ef46c0a29e10d8f0a0c7c0fac3} +x{9799c21074cd36b98d1e37f66dc05741c13f463b893ed5bce9fd2db604fe95c65f23d07015eb53e2ed9fcece39a2145a} +x{9119782077a9ae2564124374d877b0835e79d9de524c6cea6b1f3be2e1f33417040bf62bf064200c74c086d9dde10084} +x{8075657d35885a8a3bf5ba8087a5cd5b089f894da72ba7150302a0e6adcdac186eac45cb79385445e3785278711f2091} +x{8e2f9aa48d8a3693c73995113ef3d2bb8c2c37f8b528b2e8a9e52104940b1fe3bb6df234eeee099d445c59dab9eefdd4} +x{99107a6ba9b0717141c9c36c8a2f99a293e0909da65f20c44594b7bfa519de73c2c93e3ae651f94ee7e7787d16e39ccb} +x{8239a6c06392fa5854bdb4a3703b7a38a6ee44df66d498e0c3afc7381a61f335012835bef8a0b558630f1e1863a11731} +x{8fdf5c3d7bd9d5d46a0714ba236ebe0f9f8f04db756bcb30f49e87f758a28f7833aef65dfb1bac9963ec945c9ae69314} +x{911419794a5b85e78452a8e156eef297d5f10a0b5488b215e8f7d9a757f88eb7f2febcbc4f2b135181f9a0ec5c8943a7} +x{acfcbcea8262c87fa851c4d6601cbe9cca7a597425f9aa4f002b518153fe6e9a84da7185a67bfef2be7d07de638617ad} +x{b89acb00a4d16e56460ea0bab3f3f5754eb79220d60fa8ea6d5588f20d21a4d16f38107994b2e350d52d5565924c88c5} +x{b0bc0f14093c805453040cabb3a58530b5c7d75dd8ed2499c3c089ae6eaff8cb90fee7f4fb4a9a18ab43567eb06b8bdc} +x{ad3d2da3b07f0d9688fddc6e19ba30dfcebbd3a5af3a3f791e056d89650bf98d931ebabeda4338d8196e86856b3d109a} +x{b501ede6b849057031006a356607df1690206fbd337cd6ef1db764aa257ab09818af900587a751bbfa7d384c0d542c35} +x{abc8caaffa2225d3df4aa9020e517f57561efce8bf1628d1a75ad7e5c1efb4469ef56e2fb44427c740aba293e1896992} +x{a166cccc21273eca781e33dce0769633296101a1e43515a120f027d06e110e5eb7ff1f6a1a324b9c9315bd89e12819e3} +x{81c29db55f0957d889ea8db8e2a15c2d7020d50f1f9f60ccbe74d4a58a9aa5579955d4a73065614d556e443551f1ec51} +x{89024f719d34cc220acc52d4075eb1d69a7d3624d6eeca3c0e6ceea66975c94947f6001189b21f72980f883a9f73eb2d} +x{b53fddc5ad0059ee152c0d096efef63293999e54a66c867432de3dce6043f7811dc58111480e184a99e4c7f360a3de70} +x{a21b66e7db725312ef2929a65d18cf60ad3f595d8d3a76cbbe419430e4e6a83b489343edd714f774ef69b07771ed57cb} +x{b177aede11e8288c8d2c7e274cf1b5d9cd17f0b965455d42d014bc4645b93572467c96f47c8d6a0c98c12730f323170a} +497 +x{30cfebbd980ea1c8f157a34abed82f3a34ecc2594e626265063e565230ae9193} +x{91cdca080f9cd6ae0bd15b427455053e12025843c2f602e0af70fa0e5d18dceb0bc46aa1a7652b54cfd8ea97c7210c5808d7cc72b82be7a2feb57f69d3511e4590a665e45e0e0f9bb43b05d8a0167fe2a91f4aa54c0a095c864be6e2b1e43081} +-1 +test-fast-aggregate-verify \ No newline at end of file diff --git a/crypto/test/fift/bls_ops.fif b/crypto/test/fift/bls_ops.fif new file mode 100644 index 00000000..c7f76d76 --- /dev/null +++ b/crypto/test/fift/bls_ops.fif @@ -0,0 +1,215 @@ +"Asm.fif" include +"FiftExt.fif" include + +{ { drop } depth 1- times } : clear-stack + +// Map to g1 +."G1 Points:" cr +x{7abd13983c76661a98659da83066c71bd6581baf20c82c825b007bf8057a258dc53f7a6d44fb6fdecb63d9586e845d92} +<{ BLS_MAP_TO_G1 }>s 0 runvmx abort"Exitcode != 0" dup ."a1 = " csr. constant a1 +x{7a6990b38d5a7bfc47b38c5adeec60680637e8a5030dddd796e7befbec3585c54c378472daadd7756ce7a52adbea507c} +<{ BLS_MAP_TO_G1 }>s 0 runvmx abort"Exitcode != 0" dup ."a2 = " csr. constant a2 +x{4e51f1317a8d7981f7bb061488b6e6528978209226ded49b02fd45fcb9b5ff8d33c360cd6db9661143a77edb34aac125} +<{ BLS_MAP_TO_G1 }>s 0 runvmx abort"Exitcode != 0" dup ."a3 = " csr. constant a3 +x{0ca4a2a9a055367caa8c41facaae4c1f28360e2bfc70182904ff966011de9c02e6744bad6b0096e7ef3f21bd972386af} +<{ BLS_MAP_TO_G1 }>s 0 runvmx abort"Exitcode != 0" dup ."a4 = " csr. constant a4 +x{1473aa897a1a166ce6c1b1d11e2401ad719b9c03f3a86d8dd63158d389667d66917d3845414a23c69ccef01762ec78d4} +<{ BLS_MAP_TO_G1 }>s 0 runvmx abort"Exitcode != 0" dup ."a5 = " csr. constant a5 + +// Validate points +a1 a2 a3 a4 a5 +<{ { BLS_G1_INGROUP 33 THROWIFNOT } 5 times }>s 0 runvmx abort"Exitcode != 0" + +// Invalid point +x{1d549908b5eb3c16f91174abe436c1a91442a57f922da813cb3dbc55de9e62bd63eac19a664eb8c3ea34b5a5c176d844} +<{ BLS_G1_INGROUP }>s 0 runvmx abort"Exitcode != 0" abort"0 expected" + +// Zero +."Zero:" cr +a1 a2 a3 a4 a5 +<{ { BLS_G1_ISZERO 33 THROWIF } 5 times }>s 0 runvmx abort"Exitcode != 0" +<{ BLS_G1_ZERO }>s 0 runvmx abort"Exitcode != 0" dup csr. constant zero +zero <{ BLS_G1_INGROUP }>s 0 runvmx abort"Exitcode != 0" not abort"-1 expected" +zero <{ BLS_G1_ISZERO }>s 0 runvmx abort"Exitcode != 0" not abort"-1 expected" + +// Addition +."a1 + a2 + a3:" cr +a1 a2 a3 <{ { BLS_G1_ADD } 2 times }>s 0 runvmx abort"Exitcode != 0" csr. +a3 a2 a1 <{ { BLS_G1_ADD } 2 times }>s 0 runvmx abort"Exitcode != 0" csr. +a2 a3 a1 <{ { BLS_G1_ADD } 2 times }>s 0 runvmx abort"Exitcode != 0" csr. +a1 zero a2 a3 zero <{ { BLS_G1_ADD } 4 times }>s 0 runvmx abort"Exitcode != 0" csr. + +// Subtraction +."a1 - a2:" cr +a1 a2 <{ BLS_G1_SUB }>s 0 runvmx abort"Exitcode != 0" csr. +a1 zero a2 <{ BLS_G1_SUB BLS_G1_ADD }>s 0 runvmx abort"Exitcode != 0" csr. + +// Negation +."-a1:" cr +a1 <{ BLS_G1_NEG }>s 0 runvmx abort"Exitcode != 0" csr. +zero a1 <{ BLS_G1_SUB }>s 0 runvmx abort"Exitcode != 0" csr. +."0:" cr +a1 a1 <{ BLS_G1_NEG BLS_G1_ADD }>s 0 runvmx abort"Exitcode != 0" csr. +a1 a1 <{ BLS_G1_SUB }>s 0 runvmx abort"Exitcode != 0" csr. + +// Multiplication: +."a1 * 1:" cr +a1 csr. +a1 1 <{ BLS_G1_MUL }>s 0 runvmx abort"Exitcode != 0" csr. +."a1 * 0:" cr +zero csr. +a1 0 <{ BLS_G1_MUL }>s 0 runvmx abort"Exitcode != 0" csr. +."a1 * (-1):" cr +a1 -1 <{ BLS_G1_MUL }>s 0 runvmx abort"Exitcode != 0" csr. +a1 <{ BLS_G1_NEG }>s 0 runvmx abort"Exitcode != 0" csr. +."a1 * 3:" cr +a1 3 <{ BLS_G1_MUL }>s 0 runvmx abort"Exitcode != 0" csr. +a1 a1 a1 <{ { BLS_G1_ADD } 2 times }>s 0 runvmx abort"Exitcode != 0" csr. +."a1 * 123:" cr +a1 123 <{ BLS_G1_MUL }>s 0 runvmx abort"Exitcode != 0" csr. +<{ a1 SLICE 100 INT BLS_G1_MUL a1 SLICE 23 INT BLS_G1_MUL BLS_G1_ADD }>s 0 runvmx abort"Exitcode != 0" csr. +a1 -123 <{ BLS_G1_MUL BLS_G1_NEG }>s 0 runvmx abort"Exitcode != 0" csr. + +// Multiexp +."a1*111 + a2*222 + a3*(-333) + a4*0 + a5*1:" cr +a1 111 a2 222 a3 -333 a4 0 a5 1 5 <{ BLS_G1_MULTIEXP }>s 0 runvmx abort"Exitcode != 0" csr. +a1 111 a2 222 a3 -333 a5 1 4 <{ BLS_G1_MULTIEXP }>s 0 runvmx abort"Exitcode != 0" csr. +<{ + a1 SLICE 111 INT BLS_G1_MUL + a2 SLICE 222 INT BLS_G1_MUL + a3 SLICE -333 INT BLS_G1_MUL + a5 SLICE + { BLS_G1_ADD } 3 times +}>s 0 runvmx abort"Exitcode != 0" csr. +."0:" cr +zero csr. +0 <{ BLS_G1_MULTIEXP }>s 0 runvmx abort"Exitcode != 0" csr. +a1 0 1 <{ BLS_G1_MULTIEXP }>s 0 runvmx abort"Exitcode != 0" csr. + +// Map to g2 +."G2 Points:" cr +x{cce34c6322b8f3b455617a975aff8b6eaedf04fbae74a8890db6bc3fab0475b94cd8fbde0e1182ce6993afd56ed6e71919cae59c891923b4014ed9e42d9f0e1a779d9a7edb64f5e2fd600012805fc773b5092af5d2f0c6c0946ee9ad8394bf19} +<{ BLS_MAP_TO_G2 }>s 0 runvmx abort"Exitcode != 0" dup ."b1 = " csr. constant b1 +x{2faa65f3431da8f04b8d029f7699b6426eb31feb06b3429b13b99fde35d5c0ab17e67943802313a96b2252a69dfdcc6e56f5671d905984940f4b9ce3b410042457dff7ae5fd4be6a0b73cad5d0390ed379d658cb24e11973d80f98bd7ff64f19} +<{ BLS_MAP_TO_G2 }>s 0 runvmx abort"Exitcode != 0" dup ."b2 = " csr. constant b2 +x{28619564e5cbb27c9e709d80b654f2eb1fd2c3ab435d7b97b4bd80638dbfe5b47e52df0e5be0b2c328357c5ddd8018acc6e739c4d132cc6f2b9797c210051acef9513ae54bb66de2a9ea8d02cbca7e96ce8193be1557d3128906e12f37913887} +<{ BLS_MAP_TO_G2 }>s 0 runvmx abort"Exitcode != 0" dup ."b3 = " csr. constant b3 +x{66f14fc1bb199ece07fde0a7af3cb3d2719acd4bb5186ab4ddda7de6a9f96557df44f3d14264eb0fed79f53d972ddc4517e362a001c5e7c7217169a05d9e3cd82b521236737f5d564f5860139d027018d3b33605d51e48c77b51554bf1d5b24a} +<{ BLS_MAP_TO_G2 }>s 0 runvmx abort"Exitcode != 0" dup ."b4 = " csr. constant b4 +x{a9e68db711778adb0bcee53ae4fd2d31605c1eff02ae38279eebfb45fc319964d33cb45ee32bbcb13663fe2131f79120af2d8ce26400ece9a7fb57ef9666c5b1b6f1856cb121b1c618b2dcfb359ffa63a08989c1f457b355958f589e7314610a} +<{ BLS_MAP_TO_G2 }>s 0 runvmx abort"Exitcode != 0" dup ."b5 = " csr. constant b5 + +// Validate points +b1 b2 b3 b4 b5 +<{ { BLS_G2_INGROUP 33 THROWIFNOT } 5 times }>s 0 runvmx abort"Exitcode != 0" + +// Invalid point +x{090069862cb1b1ac4241c4b1ed5f98edb95413db77f534bba7e85d9cb54d953c61416c0eeb5c65c6f0b494e9f59b2c9dfe8b4a9af75e1114b45ec60f6b5d2327cc05a6d9d6e76d7a9efd947302966d4f357bd48e5c3f950101c88c65b13bd5c7} +<{ BLS_G2_INGROUP }>s 0 runvmx abort"Exitcode != 0" abort"0 expected" + +// Zero +."Zero:" cr +b1 b2 b3 b4 b5 +<{ { BLS_G2_ISZERO 33 THROWIF } 5 times }>s 0 runvmx abort"Exitcode != 0" +<{ BLS_G2_ZERO }>s 0 runvmx abort"Exitcode != 0" dup csr. constant zero +zero <{ BLS_G2_INGROUP }>s 0 runvmx abort"Exitcode != 0" not abort"-1 expected" +zero <{ BLS_G2_ISZERO }>s 0 runvmx abort"Exitcode != 0" not abort"-1 expected" + +// Addition +."b1 + b2 + b3:" cr +b1 b2 b3 <{ { BLS_G2_ADD } 2 times }>s 0 runvmx abort"Exitcode != 0" csr. +b3 b2 b1 <{ { BLS_G2_ADD } 2 times }>s 0 runvmx abort"Exitcode != 0" csr. +b2 b3 b1 <{ { BLS_G2_ADD } 2 times }>s 0 runvmx abort"Exitcode != 0" csr. +b1 zero b2 b3 zero <{ { BLS_G2_ADD } 4 times }>s 0 runvmx abort"Exitcode != 0" csr. + +// Subtraction +."b1 - b2:" cr +b1 b2 <{ BLS_G2_SUB }>s 0 runvmx abort"Exitcode != 0" csr. +b1 zero b2 <{ BLS_G2_SUB BLS_G2_ADD }>s 0 runvmx abort"Exitcode != 0" csr. + +// Negation +."-b1:" cr +b1 <{ BLS_G2_NEG }>s 0 runvmx abort"Exitcode != 0" csr. +zero b1 <{ BLS_G2_SUB }>s 0 runvmx abort"Exitcode != 0" csr. +."0:" cr +b1 b1 <{ BLS_G2_NEG BLS_G2_ADD }>s 0 runvmx abort"Exitcode != 0" csr. +b1 b1 <{ BLS_G2_SUB }>s 0 runvmx abort"Exitcode != 0" csr. + +// Multiplication: +."b1 * 1:" cr +b1 csr. +b1 1 <{ BLS_G2_MUL }>s 0 runvmx abort"Exitcode != 0" csr. +."b1 * 0:" cr +zero csr. +b1 0 <{ BLS_G2_MUL }>s 0 runvmx abort"Exitcode != 0" csr. +."b1 * (-1):" cr +b1 -1 <{ BLS_G2_MUL }>s 0 runvmx abort"Exitcode != 0" csr. +b1 <{ BLS_G2_NEG }>s 0 runvmx abort"Exitcode != 0" csr. +."b1 * 3:" cr +b1 3 <{ BLS_G2_MUL }>s 0 runvmx abort"Exitcode != 0" csr. +b1 b1 b1 <{ { BLS_G2_ADD } 2 times }>s 0 runvmx abort"Exitcode != 0" csr. +."b1 * 123:" cr +b1 123 <{ BLS_G2_MUL }>s 0 runvmx abort"Exitcode != 0" csr. +<{ b1 SLICE 100 INT BLS_G2_MUL b1 SLICE 23 INT BLS_G2_MUL BLS_G2_ADD }>s 0 runvmx abort"Exitcode != 0" csr. +b1 -123 <{ BLS_G2_MUL BLS_G2_NEG }>s 0 runvmx abort"Exitcode != 0" csr. + +// Multiexp +."b1*111 + b2*222 + b3*(-333) + b4*0 + b5*1:" cr +b1 111 b2 222 b3 -333 b4 0 b5 1 5 <{ BLS_G2_MULTIEXP }>s 0 runvmx abort"Exitcode != 0" csr. +b1 111 b2 222 b3 -333 b5 1 4 <{ BLS_G2_MULTIEXP }>s 0 runvmx abort"Exitcode != 0" csr. +<{ + b1 SLICE 111 INT BLS_G2_MUL + b2 SLICE 222 INT BLS_G2_MUL + b3 SLICE -333 INT BLS_G2_MUL + b5 SLICE + { BLS_G2_ADD } 3 times +}>s 0 runvmx abort"Exitcode != 0" csr. +."0:" cr +zero csr. +0 <{ BLS_G2_MULTIEXP }>s 0 runvmx abort"Exitcode != 0" csr. +b1 0 1 <{ BLS_G2_MULTIEXP }>s 0 runvmx abort"Exitcode != 0" csr. + +// r +<{ BLS_PUSHR }>s 0 runvmx abort"Exitcode != 0" cr ."r = " . cr +b1 <{ BLS_PUSHR BLS_G2_MUL }>s 0 runvmx abort"Exitcode != 0" csr. +zero csr. +b1 <{ BLS_PUSHR INC BLS_G2_MUL }>s 0 runvmx abort"Exitcode != 0" csr. +b1 csr. + +// Pairings +{ [[ <{ BLS_G1_MUL }>s ]] 0 runvmx abort"Exitcode != -1" } : bls_g1_mul +{ [[ <{ BLS_G2_MUL }>s ]] 0 runvmx abort"Exitcode != -1" } : bls_g2_mul +75634785643785634785634876232423354534 constant x +."a1*x,b1 a1,b1*(-x) : " +a1 x bls_g1_mul b1 +a1 b1 x negate bls_g2_mul +2 <{ BLS_PAIRING }>s 0 runvmx abort"Exitcode != 0" .s not abort"-1 expected" +."a1*x,b1 a1,b1*(-x-1) : " +a1 x bls_g1_mul b1 +a1 b1 x negate 1 - bls_g2_mul +2 <{ BLS_PAIRING }>s 0 runvmx abort"Exitcode != 0" .s abort"0 expected" + +08036758068232723862526737758751120353935980577994643429668638941492109432519 constant x1 +76720311667788346189068792441910584335982488547394926476426087533015880449318 constant x2 +73698677644295053147826041647629389417255852559045739853199261775689421644183 constant x3 +00651749128863148819911470689106677724299434569675211711456038250594316760143 constant x4 +."a1*x1,b1 a2*x2,b2 a3*x3,b3 a4*x4,b4 a1,b1*(-x1) a2,b2*(-x2) a3,b3*(-x3) a4,b4*(-x4) : " +a1 x1 bls_g1_mul b1 +a2 x2 bls_g1_mul b2 +a3 x3 bls_g1_mul b3 +a4 x4 bls_g1_mul b4 +a1 b1 x1 negate bls_g2_mul +a2 b2 x2 negate bls_g2_mul +a3 b3 x3 negate bls_g2_mul +a4 b4 x4 negate bls_g2_mul +8 <{ BLS_PAIRING }>s 0 runvmx abort"Exitcode != 0" .s not abort"-1 expected" +."a1*x1,b1 a2*x2,b2 a3*x3,b3 a4*x4,b4 a1,b1*(-x1) a2,b2*(-x2) a3,b3*(-x4) a4,b4*(-x3) : " +a1 x1 bls_g1_mul b1 +a2 x2 bls_g1_mul b2 +a3 x3 bls_g1_mul b3 +a4 x4 bls_g1_mul b4 +a1 b1 x1 negate bls_g2_mul +a2 b2 x2 negate bls_g2_mul +a3 b3 x4 negate bls_g2_mul +a4 b4 x3 negate bls_g2_mul +8 <{ BLS_PAIRING }>s 0 runvmx abort"Exitcode != 0" .s abort"0 expected" diff --git a/crypto/test/fift/deep_stack_ops.fif b/crypto/test/fift/deep_stack_ops.fif new file mode 100644 index 00000000..460ecdd7 --- /dev/null +++ b/crypto/test/fift/deep_stack_ops.fif @@ -0,0 +1,27 @@ +"Asm.fif" include + +{ { drop } depth 1- times } : clear-stack + +0 { dup 1+ } 500 times +<{ 400 INT XCHGX 300 INT PICK 450 INT CHKDEPTH }>s 1000000 64 8 + runvmx .s clear-stack + +0 { dup 1+ } 500 times +<{ 400 INT ROLLX }>s 1000000 64 8 + runvmx .s clear-stack + +0 { dup 1+ } 500 times +<{ 400 INT -ROLLX }>s 1000000 64 8 + runvmx .s clear-stack + +0 { dup 1+ } 700 times +<{ 350 INT 300 INT BLKSWX }>s 1000000 64 8 + runvmx .s clear-stack + +0 { dup 1+ } 500 times +<{ 400 INT 5 INT REVX }>s 1000000 64 8 + runvmx .s clear-stack + +0 { dup 1+ } 500 times +<{ 400 INT DROPX }>s 1000000 64 8 + runvmx .s clear-stack + +0 { dup 1+ } 500 times +<{ 400 INT ONLYTOPX }>s 1000000 64 8 + runvmx .s clear-stack + +0 { dup 1+ } 500 times +<{ 400 INT ONLYX }>s 1000000 64 8 + runvmx .s clear-stack diff --git a/crypto/test/fift/disasm.fif b/crypto/test/fift/disasm.fif new file mode 100644 index 00000000..8d9f31ac --- /dev/null +++ b/crypto/test/fift/disasm.fif @@ -0,0 +1,70 @@ +"Disasm.fif" include +"Asm.fif" include + +<{ + IF:<{ + 123456789 PUSHINT + }>ELSE<{ + x{12345} PUSHSLICE + }> + WHILE:<{ ADD }>DO<{ + 10 PUSHINT REPEAT:<{ + CONT:<{ NOP }> + CONT:<{ }> + }> + }> +}>s +disasm cr + +x{007A7} disasm cr // Cannot disassemble: x{7} + +<{ + SWAP + s0 s10 XCHG s0 100 s() XCHG + s5 PUSH s6 POP + s4 s10 PUSH2 + 5 10 BLKSWAP + c4 PUSH c5 POP +}>s dup dup +disasm cr +std-disasm disasm cr +stack-disasm show-vm-code disasm cr +hide-vm-code + +<{ + 1 INT 123456789 INT 123456789123456789123456789 INT + PUSHREF + x{567} PUSHSLICE + 10 ADDCONST + DIV DIVR DIVC + RSHIFTC 10 RSHIFTC# + 20 MODPOW2# + 30 MULRSHIFTR# + LSHIFTDIVC 40 LSHIFT#DIVR +}>s +disasm cr + +PROGRAM{ + 11 DECLMETHOD foo1 + 12 DECLMETHOD foo2 + 13 DECLMETHOD foo3 + DECLPROC main + foo1 PROC:<{ + 123 ADDCONST + }> + foo2 PROC:<{ + OVER + 5 EQINT + IFJMP:<{ + NIP + }> + MUL + }> + foo3 PROC:<{ + ADD + foo2 CALLDICT + }> + main PROC:<{ + }> +}END>s +disasm \ No newline at end of file diff --git a/crypto/test/fift/fift-ext.fif b/crypto/test/fift/fift-ext.fif new file mode 100644 index 00000000..b0b08c12 --- /dev/null +++ b/crypto/test/fift/fift-ext.fif @@ -0,0 +1,107 @@ +"FiftExt.fif" include + +// if then +{ + if{ 0> }then{ "Yes" } +} : run +5 run type cr // Yes +-5 run .s // nothing + +// if then else +{ + if{ 0> }then{ "Yes" }else{ "No" } +} : run +5 run type ." " // Yes +-5 run type cr // No + +// if then elseif else +{ + dup dup if{ 20 > }then{ 2drop "AAA" }elseif{ 10 > }then{ drop "BBB" }elseif{ 0> }then{ "CCC" }else{ "DDD" } +} : run +25 run type ." " // AAA +15 run type ." " // BBB +5 run type ." " // CCC +-5 run type cr // DDD + +// while do +1 +while{ dup 100 < }do{ + dup . + 2 * +} +cr // 1 2 4 8 16 32 64 +drop + +// repeat until +1 +repeat{ + dup . + 3 * +}until{ dup 500 > } +cr // 1 3 9 27 81 243 +drop + +// def +def foo1 { * + } +5 10 20 foo1 . // 205 +6 100 100 foo1 . cr // 10006 + +// defrec +defrec fib { + if{ dup 2 < }then{ + drop 1 + }else{ + dup 1- fib swap 2- fib + + } +} +8 fib . // 34 +9 fib . // 55 +20 fib . cr // 10946 + +// [[ ... ]] +def foo2 { [[ ."Exec" cr 5 5 * ]] + } +."Calling foo2: " +100 foo2 . 200 foo2 . cr + +// Nested blocks +def sqr { dup * } +def power { + 1 -rot + while{ dup 0> }do{ + if{ dup 1 and }then{ + -rot tuck * swap rot + } + 2/ + if{ dup 0> }then{ + swap sqr swap + } + } + 2drop +} + +3 0 power . // 1 +3 1 power . // 3 +3 4 power . // 81 +3 12 power . // 531441 +3 50 power . // 717897987691852588770249 +cr + +0 while{ dup 2 < }do{ + ."A" + 0 while{ dup 2 < }do{ + ."B" + 0 while{ dup 2 < }do{ + ."C" + 0 while{ dup 2 < }do{ + ."D" + 0 while{ dup 2 < }do{ + ."E" + 0 while{ dup 2 < }do{ + ."F" + 1+ } drop + 1+ } drop + 1+ } drop + 1+ } drop + 1+ } drop +1+ } drop +cr // ABCDEFFEFFDEFFEFFCDEFFEFFDEFFEFFBCDEFFEFFDEFFEFFCDEFFEFFDEFFEFFABCDEFFEFFDEFFEFFCDEFFEFFDEFFEFFBCDEFFEFFDEFFEFFCDEFFEFFDEFFEFF \ No newline at end of file diff --git a/crypto/test/fift/hash_ext.fif b/crypto/test/fift/hash_ext.fif new file mode 100644 index 00000000..3f009410 --- /dev/null +++ b/crypto/test/fift/hash_ext.fif @@ -0,0 +1,98 @@ +"Asm.fif" include + +{ { drop } depth 1- times } : clear-stack + +// Compare HASHEXT_SHA256 with SHA256U +<{ + x{0123456789abcdef} PUSHSLICE SHA256U + + x{0123456789abcdef} PUSHSLICE 1 PUSHINT HASHEXT_SHA256 + + x{01} PUSHSLICE + x{2} PUSHSLICE + b{001101} PUSHSLICE NEWC STSLICE + b{0} PUSHSLICE + b{00101} PUSHSLICE NEWC STSLICE + x{6789a} PUSHSLICE + b{1} PUSHSLICE + b{0111100} PUSHSLICE + x{def} PUSHSLICE + 9 PUSHINT HASHEXT_SHA256 + + x{01} PUSHSLICE + x{2} PUSHSLICE + b{001101} PUSHSLICE NEWC STSLICE + b{0} PUSHSLICE + b{00101} PUSHSLICE NEWC STSLICE + x{6789a} PUSHSLICE + b{1} PUSHSLICE + b{0111100} PUSHSLICE + x{def} PUSHSLICE + 9 0 REVERSE + 9 PUSHINT HASHEXTR_SHA256 +}>s +0 runvmx abort"runvmx finished with exitcode != 0" +. cr . cr . cr . cr .s + +// HASHEXTA +<{ + NEWC x{ff} PUSHSLICE STSLICER x{01234567} PUSHSLICE SHA256U 256 STUR ENDC CTOS + NEWC x{ff} PUSHSLICE STSLICER x{0123} PUSHSLICE x{4567} PUSHSLICE 2 PUSHINT HASHEXTA_SHA256 ENDC CTOS + NEWC x{ff} PUSHSLICE STSLICER x{4567} PUSHSLICE x{0123} PUSHSLICE 2 PUSHINT HASHEXTAR_SHA256 ENDC CTOS +}>s +0 runvmx abort"runvmx finished with exitcode != 0" +csr. csr. csr. .s + +// Exceptions on errors +<{ x{001122334455667} PUSHSLICE 1 PUSHINT HASHEXT_SHA256 }>s 0 runvmx .s 9 <> abort"exitcode != 9" clear-stack +<{ x{00} PUSHSLICE x{11} PUSHSLICE 3 PUSHINT HASHEXT_SHA256 }>s 0 runvmx .s 5 <> abort"exitcode != 5" clear-stack +<{ x{00} PUSHSLICE 1 PUSHINT 2 PUSHINT HASHEXT_SHA256 }>s 0 runvmx .s 7 <> abort"exitcode != 7" clear-stack +<{ x{1234} PUSHSLICE 1 PUSHINT 100 HASHEXT }>s 0 runvmx .s 5 <> abort"exitcode != 5" clear-stack + +// Other hash functions + s 0 runvmx .s abort"runvmx finished with exitcode != 0" clear-stack +<{ + str PUSHSLICE 1 PUSHINT 0 HASHEXT + str PUSHSLICE 1 PUSHINT 1 HASHEXT + str PUSHSLICE 1 PUSHINT 2 HASHEXT + str PUSHSLICE 1 PUSHINT 3 HASHEXT + str PUSHSLICE 1 PUSHINT 4 HASHEXT +}>s 0 runvmx .s abort"runvmx finished with exitcode != 0" clear-stack +<{ + NEWC str PUSHSLICE 1 PUSHINT 4 HASHEXTA ENDC CTOS + NEWC str PUSHSLICE 1 PUSHINT 3 HASHEXTA ENDC CTOS + NEWC str PUSHSLICE 1 PUSHINT 2 HASHEXTA ENDC CTOS + NEWC str PUSHSLICE 1 PUSHINT 1 HASHEXTA ENDC CTOS + NEWC str PUSHSLICE 1 PUSHINT 0 HASHEXTA ENDC CTOS +}>s 0 runvmx abort"runvmx finished with exitcode != 0" +{ csr. } 5 times .s + +// Long string +0 { + =: hash-idx + 0 { + dup =: len + s ]] 0 runvmx abort"exitcode != 0" ."Level = " . cr + dup [[ <{ CLEVELMASK }>s ]] 0 runvmx abort"exitcode != 0" ."Level mask = 0b" b. cr + dup dup [[ <{ 0 CHASHI DUP ROT 0 INT CHASHIX EQUAL 55 THROWIFNOT }>s ]] 0 runvmx abort"exitcode != 0" ."Hash_0 = " X. cr + dup dup [[ <{ 1 CHASHI DUP ROT 1 INT CHASHIX EQUAL 55 THROWIFNOT }>s ]] 0 runvmx abort"exitcode != 0" ."Hash_1 = " X. cr + dup dup [[ <{ 2 CHASHI DUP ROT 2 INT CHASHIX EQUAL 55 THROWIFNOT }>s ]] 0 runvmx abort"exitcode != 0" ."Hash_2 = " X. cr + dup dup [[ <{ 3 CHASHI DUP ROT 3 INT CHASHIX EQUAL 55 THROWIFNOT }>s ]] 0 runvmx abort"exitcode != 0" ."Hash_3 = " X. cr + dup dup [[ <{ 0 CDEPTHI DUP ROT 0 INT CDEPTHIX EQUAL 55 THROWIFNOT }>s ]] 0 runvmx abort"exitcode != 0" ."Depth_0 = " . cr + dup dup [[ <{ 1 CDEPTHI DUP ROT 1 INT CDEPTHIX EQUAL 55 THROWIFNOT }>s ]] 0 runvmx abort"exitcode != 0" ."Depth_1 = " . cr + dup dup [[ <{ 2 CDEPTHI DUP ROT 2 INT CDEPTHIX EQUAL 55 THROWIFNOT }>s ]] 0 runvmx abort"exitcode != 0" ."Depth_2 = " . cr + dup dup [[ <{ 3 CDEPTHI DUP ROT 3 INT CDEPTHIX EQUAL 55 THROWIFNOT }>s ]] 0 runvmx abort"exitcode != 0" ."Depth_3 = " . cr + drop + cr +} : print-all + +// Ordinary cell of level 0 + ref, b> ref, + ref, +b> +print-all + +// Prunned branch of level 1 +spec +print-all + +// Prunned branch of level 3 +spec +print-all + +// Prunned branch of level 3, mask 0b101 +spec +print-all + +// Tree with the previous cell inside +spec ref, + b> ref, +b> +print-all diff --git a/crypto/test/fift/namespaces.fif b/crypto/test/fift/namespaces.fif new file mode 100644 index 00000000..3f10f608 --- /dev/null +++ b/crypto/test/fift/namespaces.fif @@ -0,0 +1,29 @@ +namespace My + +"a" constant a +"b" constant b +"c" constant c + +a b c .s { drop } 3 times // "a" "b" "c" + +My definitions +"b-my" constant b +"c-my" constant c +"d-my" constant d + +a b c d .s { drop } 4 times // "a" "b-my" "c-my" "d-my" + +Fift definitions +a b c .s { drop } 3 times // "a" "b-my" "c-my" "d-my" + +My b My c My d .s { drop } 3 times // "b-my" "c-my" "d-my" +a b c .s { drop } 3 times // "a" "b" "c" "d" + +My definitions +a b c d .s { drop } 4 times // "a" "b-my" "c-my" "d-my" +Fift a Fift b Fift c d .s { drop } 4 times // "a" "b" "c" "d-my" + +Fift definitions +cr +My-wordlist @ +{ drop type -1 } hmapforeach drop cr // "b " "d " "c " \ No newline at end of file diff --git a/crypto/test/fift/rist255.fif b/crypto/test/fift/rist255.fif new file mode 100644 index 00000000..ddd9b619 --- /dev/null +++ b/crypto/test/fift/rist255.fif @@ -0,0 +1,92 @@ +// Test data: https://ristretto.group/test_vectors/ristretto255.html +"Asm.fif" include +"FiftExt.fif" include + +."Basepoint multiples:" cr +{ + =: ans =: n + @' n + [[ <{ RIST255_MULBASE DUP RIST255_VALIDATE }>s ]] 0 runvmx abort"Exitcode != 0" + @' n . dup (x.) type cr + @' ans <> abort"Invalid result" + @' n + [[ <{ 1 INT RIST255_MULBASE SWAP RIST255_MUL DUP RIST255_VALIDATE }>s ]] 0 runvmx abort"Exitcode != 0" + @' ans <> abort"Invalid result" +} : test-basepoint + + 0 0x0000000000000000000000000000000000000000000000000000000000000000 test-basepoint + 1 0xe2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76 test-basepoint + 2 0x6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919 test-basepoint + 3 0x94741f5d5d52755ece4f23f044ee27d5d1ea1e2bd196b462166b16152a9d0259 test-basepoint + 4 0xda80862773358b466ffadfe0b3293ab3d9fd53c5ea6c955358f568322daf6a57 test-basepoint + 5 0xe882b131016b52c1d3337080187cf768423efccbb517bb495ab812c4160ff44e test-basepoint + 6 0xf64746d3c92b13050ed8d80236a7f0007c3b3f962f5ba793d19a601ebb1df403 test-basepoint + 7 0x44f53520926ec81fbd5a387845beb7df85a96a24ece18738bdcfa6a7822a176d test-basepoint + 8 0x903293d8f2287ebe10e2374dc1a53e0bc887e592699f02d077d5263cdd55601c test-basepoint + 9 0x02622ace8f7303a31cafc63f8fc48fdc16e1c8c8d234b2f0d6685282a9076031 test-basepoint +10 0x20706fd788b2720a1ed2a5dad4952b01f413bcf0e7564de8cdc816689e2db95f test-basepoint +11 0xbce83f8ba5dd2fa572864c24ba1810f9522bc6004afe95877ac73241cafdab42 test-basepoint +12 0xe4549ee16b9aa03099ca208c67adafcafa4c3f3e4e5303de6026e3ca8ff84460 test-basepoint +13 0xaa52e000df2e16f55fb1032fc33bc42742dad6bd5a8fc0be0167436c5948501f test-basepoint +14 0x46376b80f409b29dc2b5f6f0c52591990896e5716f41477cd30085ab7f10301e test-basepoint +15 0xe0c418f7c8d9c4cdd7395b93ea124f3ad99021bb681dfc3302a9d99a2e53e64e test-basepoint + +cr ."Invalid points:" cr +{ + =: x + @' x (x.) type cr + @' x + [[ <{ RIST255_QVALIDATE }>s ]] 0 runvmx abort"Exitcode != 0" + abort"Invalid result" +} : test-invalid +// These are all bad because they're non-canonical field encodings. +0x00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff test-invalid +0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f test-invalid +0xf3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f test-invalid +0xedffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f test-invalid +// These are all bad because they're negative field elements. +0x0100000000000000000000000000000000000000000000000000000000000000 test-invalid +0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f test-invalid +0xed57ffd8c914fb201471d1c3d245ce3c746fcbe63a3679d51b6a516ebebe0e20 test-invalid +0xc34c4e1826e5d403b78e246e88aa051c36ccf0aafebffe137d148a2bf9104562 test-invalid +0xc940e5a4404157cfb1628b108db051a8d439e1a421394ec4ebccb9ec92a8ac78 test-invalid +0x47cfc5497c53dc8e61c91d17fd626ffb1c49e2bca94eed052281b510b1117a24 test-invalid +0xf1c6165d33367351b0da8f6e4511010c68174a03b6581212c71c0e1d026c3c72 test-invalid +0x87260f7a2f12495118360f02c26a470f450dadf34a413d21042b43b9d93e1309 test-invalid +// These are all bad because they give a nonsquare x^2. +0x26948d35ca62e643e26a83177332e6b6afeb9d08e4268b650f1f5bbd8d81d371 test-invalid +0x4eac077a713c57b4f4397629a4145982c661f48044dd3f96427d40b147d9742f test-invalid +0xde6a7b00deadc788eb6b6c8d20c0ae96c2f2019078fa604fee5b87d6e989ad7b test-invalid +0xbcab477be20861e01e4a0e295284146a510150d9817763caf1a6f4b422d67042 test-invalid +0x2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08 test-invalid +0xf4a9e534fc0d216c44b218fa0c42d99635a0127ee2e53c712f70609649fdff22 test-invalid +0x8268436f8c4126196cf64b3c7ddbda90746a378625f9813dd9b8457077256731 test-invalid +0x2810e5cbc2cc4d4eece54f61c6f69758e289aa7ab440b3cbeaa21995c2f4232b test-invalid +// These are all bad because they give a negative xy value. +0x3eb858e78f5a7254d8c9731174a94f76755fd3941c0ac93735c07ba14579630e test-invalid +0xa45fdc55c76448c049a1ab33f17023edfb2be3581e9c7aade8a6125215e04220 test-invalid +0xd483fe813c6ba647ebbfd3ec41adca1c6130c2beeee9d9bf065c8d151c5f396e test-invalid +0x8a2e1d30050198c65a54483123960ccc38aef6848e1ec8f5f780e8523769ba32 test-invalid +0x32888462f8b486c68ad7dd9610be5192bbeaf3b443951ac1a8118419d9fa097b test-invalid +0x227142501b9d4355ccba290404bde41575b037693cef1f438c47f8fbf35d1165 test-invalid +0x5c37cc491da847cfeb9281d407efc41e15144c876e0170b499a96a22ed31e01e test-invalid +0x445425117cb8c90edcbc7c1cc0e74f747f2c1efa5630a967c64f287792a48a4b test-invalid +// This is s = -1, which causes y = 0. +0xecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f test-invalid + +cr ."Hash to point:" cr +{ + =: text =: ans + @' text $>s + [[ <{ 1 INT HASHEXT_SHA512 2 UNTUPLE RIST255_FROMHASH }>s ]] 0 runvmx abort"Exitcode != 0" + @' ans <> abort"Invalid result" + @' ans (x.) type ." " @' text type cr +} : test-hash + +0x3066f82a1a747d45120d1740f14358531a8f04bbffe6a819f86dfe50f44a0a46 "Ristretto is traditionally a short shot of espresso coffee" test-hash +0xf26e5b6f7d362d2d2a94c5d0e7602cb4773c95a2e5c31a64f133189fa76ed61b "made with the normal amount of ground coffee but extracted with" test-hash +0x006ccd2a9e6867e6a2c5cea83d3302cc9de128dd2a9a57dd8ee7b9d7ffe02826 "about half the amount of water in the same amount of time" test-hash +0xf8f0c87cf237953c5890aec3998169005dae3eca1fbb04548c635953c817f92a "by using a finer grind." test-hash +0xae81e7dedf20a497e10c304a765c1767a42d6e06029758d2d7e8ef7cc4c41179 "This produces a concentrated shot of coffee per volume." test-hash +0xe2705652ff9f5e44d3e841bf1c251cf7dddb77d140870d1ab2ed64f1a9ce8628 "Just pulling a normal shot short will produce a weaker shot" test-hash +0x80bd07262511cdde4863f8a7434cef696750681cb9510eea557088f76d9e5065 "and is not a Ristretto as some believe." test-hash \ No newline at end of file diff --git a/crypto/test/fift/test-stack-copy.fif b/crypto/test/fift/test-stack-copy.fif new file mode 100644 index 00000000..4fb10a34 --- /dev/null +++ b/crypto/test/fift/test-stack-copy.fif @@ -0,0 +1,20 @@ +#!/usr/bin/fift -s +"Asm.fif" include +2500 =: N +{ 5 * } : *K +N 100 / =: N/100 +N 10 / =: N/10 +{ { EXECUTE } 100 times } : 100EXECUTE +{ DUP { 2DUP } 7 times } : 15DUP +{ { 2DUP } 50 times } : 100DUP +{ { 15 -1 SETCONTARGS 15DUP } 10 times } : 10SET&DUP +<{ + CONT:<{ }> + 15DUP + N/10 INT REPEAT:<{ 10SET&DUP }> + N/100 *K INT REPEAT:<{ 100DUP }> + N/100 *K INT REPEAT:<{ 100EXECUTE }> +}>s =: Code + +Code csr. +Code 1000000 gasrunvmcode diff --git a/crypto/test/fift/testcc.fif b/crypto/test/fift/testcc.fif new file mode 100644 index 00000000..4cbd781c --- /dev/null +++ b/crypto/test/fift/testcc.fif @@ -0,0 +1,32 @@ +#!/usr/bin/create-state -s +{ dup tlb-type-lookup { nip } { "unknown TLB type " swap $+ abort } cond } : $>tlb +{ bl word $>tlb 1 'nop } ::_ tlb: +{ dup null? { drop true } { + " } cond cr } : cshow +{ ."X = " over dup .cc space .cc-valid cr + ."Y = " dup dup .cc space .cc-valid cr + ."X + Y = " 2dup CC+? cshow + ."X - Y = " 2dup CC-? cshow + ."Y - X = " 2dup swap CC-? cshow + ."X + X = " over dup CC+? cshow + ."Y + Y = " dup dup CC+? cshow + ."X - X = " over dup CC-? cshow + ."Y - Y = " dup dup CC-? cshow + 2drop ."********************" cr +} : one-test +CX{666666666666*$239+1000000000000*$-17} =: X +X CX{666666666666*$239+4444*$-17} one-test +X CX{666666666665*$239+4444*$-17} one-test +X CX{666666666667*$239+4444*$-17} one-test +X CX{666666666666*$239} one-test +X CX{666666666665*$239} one-test +X CX{666666666667*$239} one-test +X CX{1111*$1} one-test +X CX{0*$-17} one-test +X cc0 1 0 +newccpair one-test +X cc0 239 0 +newccpair one-test diff --git a/crypto/test/fift/testdict.fif b/crypto/test/fift/testdict.fif index 307bd2e8..28d711ad 100644 --- a/crypto/test/fift/testdict.fif +++ b/crypto/test/fift/testdict.fif @@ -1,4 +1,4 @@ -"Lisp.fif" include +"Lists.fif" include 16 constant key-bits 16 constant val-bits { val-bits u, } : val, @@ -7,8 +7,14 @@ { dictnew { over null? not } { swap uncons swap rot +dictpair } while nip } : mkdict { dup null? { ."(null) " drop } { val@ . } cond } : .val { key-bits { swap . ."-> " .val ."; " true } dictforeach drop cr } : show-dict +{ key-bits { swap . ."-> " .val ."; " true } idictforeach drop cr } : show-idict +{ key-bits { swap . ."-> " .val ."; " true } dictforeachrev drop cr } : show-rev-dict +{ key-bits { swap . ."-> " .val ."; " true } idictforeachrev drop cr } : show-rev-idict { key-bits { rot . ."-> " swap .val .val ."; " true } dictdiff drop cr } : show-dict-diff { key-bits { val@ swap val@ + val, true } dictmerge } : dict-sum +{ key-bits { val@ 2* val, true } dictmap } : dict-twice +{ key-bits { val@ 2 /mod { 2drop false } { val, true } cond } dictmap } : dict-half +{ key-bits { val@ + val, true } dictmapext } : dict-add-x { null swap key-bits { val@ pair swap cons true } dictforeach drop } : dict>list-rev { dict>list-rev list-reverse } : dict>list ( _( 13 169 ) _( 17 289 ) _( 10 100 ) ) mkdict =: Dict @@ -22,3 +28,7 @@ Dict2 dict>list .l cr Dict2 " .val ."; " true } dictforeach drop cr } : show-dict +{ key-bits { swap . ."-> " .val ."; " true } idictforeach drop cr } : show-idict +{ key-bits { swap . ."-> " .val ."; " true } dictforeachrev drop cr } : show-rev-dict +{ key-bits { swap . ."-> " .val ."; " true } idictforeachrev drop cr } : show-rev-idict +{ key-bits { rot . ."-> " swap .val .val ."; " true } dictdiff drop cr } : show-dict-diff +{ key-bits { swap . ."-> " .val ."; " true } rot dictforeachfromx drop cr } : show-dict-from +{ key-bits { val@ swap val@ + val, true } dictmerge } : dict-sum +{ key-bits { val@ 2* val, true } dictmap } : dict-twice +{ key-bits { val@ 2 /mod { 2drop false } { val, true } cond } dictmap } : dict-half +{ key-bits { val@ + val, true } dictmapext } : dict-add-x +{ null swap key-bits { val@ pair swap cons true } dictforeach drop } : dict>list-rev +{ dict>list-rev list-reverse } : dict>list +( _( 13 169 ) _( 17 289 ) _( 10 100 ) ) mkdict =: Dict +_( 4 16 ) _( 9 81 ) Dict +dictpair +dictpair =: Dict1 +_( 4 20 ) _( 101 10201 ) Dict +dictpair +dictpair =: Dict2 +_( 65533 9 ) Dict2 +dictpair =: Dict3 +."Dict1 = " Dict1 show-idict +."Dict2 = " Dict2 show-idict +."Dict3 = " Dict3 show-idict +variable D +{ D ! } : D! +{ ."D = " D @ show-idict } : show-D +{ D @ 2 show-dict-from } : show-D-from +{ D @ 6 show-dict-from } : show-D-from+ +{ D @ 18 show-dict-from } : show-D-from-hint +{ D @ 22 show-dict-from } : show-D-from-hint+ +variable hint hint 0! +{ ."D[" dup ._ ."..] = " show-D-from } : test-from +{ ."D[" dup ._ ."+..] = " show-D-from+ } : test-from+ +{ hint @ ."(hint=" dup ._ .") D[" over ._ ."..] = " show-D-from-hint } : test-from-hint +{ hint @ ."(hint=" dup ._ .") D[" over ._ ."+..] = " show-D-from-hint+ } : test-from-hint+ +{ -16 { 2dup swap execute 1+ } 120 times 2drop } : range-run +{ hint ! ' test-from-hint range-run ' test-from-hint+ range-run } : range-test-hint + +{ ."------- BIG TEST -------" cr show-D + ' test-from range-run ' test-from+ range-run + 0 range-test-hint 13 range-test-hint 7 range-test-hint 100 range-test-hint +} : big-test +Dict1 D! +big-test +Dict2 D! +big-test +Dict3 D! +big-test diff --git a/crypto/test/fift/tvm_runvm.fif b/crypto/test/fift/tvm_runvm.fif new file mode 100644 index 00000000..1037156b --- /dev/null +++ b/crypto/test/fift/tvm_runvm.fif @@ -0,0 +1,228 @@ +"Asm.fif" include + +// Just run +111 10 20 2 +<{ + ADD // Ensure that stack was passed to runvmx + DEPTH // Ensure that only 2 stack entries were passed + c4 PUSH CTOS SBITREFS // Ensure that c4 is empty + c5 PUSH CTOS SBITREFS // Ensure that c5 is empty + c7 PUSH // Ensure that c7 is empty + PUSHREF c4 POP // Ensure that it does not affect c4, c5, c7 in parent vm + PUSHREF c5 POP + NIL 100 PUSHINT TPUSH 200 PUSHINT TPUSH c7 POP + 123 PUSHINT +}>s +<{ + PUSHREF c4 POP + PUSHREF c5 POP + NIL 5 PUSHINT TPUSH 6 PUSHINT TPUSH c7 POP + 0 RUNVM + c4 PUSH CTOS c5 PUSH CTOS c7 PUSH // Ensure that c4, c5, c7 are unchanged +}>s 1000000 8 runvmx // Show gas usage +.s { drop } depth 1- times // 111 30 1 0 0 0 0 [] 123 0 x{1234} x{5678} [ 5 6 ] 0 1197 + +// Exception +111 10 20 2 +<{ 22 PUSHINT 55 PUSHINT 66 THROWARG }>s +<{ 0 RUNVM }>s 0 runvmx +.s { drop } depth 1- times // 111 55 66 0 + +// Mode +1 - set c3 to code +PROGRAM{ + 22 DECLMETHOD foo + DECLPROC main + foo PROC:<{ + MUL + }> + main PROC:<{ + DUP + foo CALLDICT + INC + }> +}END>s constant prog +<{ + 10 PUSHINT 0 PUSHINT 2 PUSHINT prog PUSHSLICE 1 RUNVM + 10 PUSHINT 0 PUSHINT 2 PUSHINT prog PUSHSLICE 0 RUNVM +}>s 0 runvmx +.s { drop } depth 1- times // 101 0 10 10 22 11 0 + +// Mode +2 - push 0 +<{ 10 PUSHINT 1 PUSHINT prog PUSHSLICE 3 RUNVM }>s 0 runvmx +.s { drop } depth 1- times // 101 0 0 + +// Mode +4 - load and return c4 +0 +<{ + c4 PUSHCTR CTOS // Ensure that this is x{5678} + PUSHREF c4 POPCTR // This should be returned from RUNVM + 1000 PUSHINT +}>s + +<{ + PUSHREF c4 POP // Ensure that this does not change + 4 RUNVM + CTOS + c4 PUSH CTOS // x{1234} +}>s 0 runvmx +.s { drop } depth 1- times // x{5678} 1000 0 x{abcd} x{1234} 0 + +// Mode +16 - load c7 +0 +<{ + c7 PUSH // Ensure that this is [ 10 15 20 ] + NIL 111 PUSHINT TPUSH 222 PUSHINT TPUSH 3333 PUSHINT TPUSH c7 POP + 1000 PUSHINT +}>s +10 15 20 3 tuple +<{ + NIL 1 PUSHINT TPUSH 2 PUSHINT TPUSH 3 PUSHINT TPUSH c7 POP // Ensure that this does not change + 16 RUNVM + c7 PUSH // [ 1 2 3 ] +}>s 0 runvmx +.s { drop } depth 1- times // [ 10 15 20 ] 1000 0 [ 1 2 3 ] 0 + +// Mode +32 - return c5 +0 +<{ + c5 PUSH CTOS SBITREFS // Ensure that this is empty + PUSHREF c5 POP // Ensure that this is returned from RUNVM + 1000 PUSHINT +}>s +<{ + PUSHREF c5 POP // Ensure that this does not change + 32 RUNVM + CTOS + c5 PUSH CTOS // x{1234} +}>s 0 runvmx +.s { drop } depth 1- times // 0 0 1000 0 x{5678} x{1234} 0 + +// c4, c5 with exception +0 +<{ + PUSHREF c4 POP + PUSHREF c5 POP + 55 THROW +}>s + // c4 for RUNVM +<{ + PUSHREF c4 POP // Ensure that this does not change + PUSHREF c5 POP // Ensure that this does not change + 32 4 + RUNVM + c4 PUSH CTOS // x{1234aaaa} + c5 PUSH CTOS // x{1234bbbb} +}>s 0 runvmx +.s { drop } depth 1- times // 0 55 null null x{1234aaaa} x{1234bbbb} 0 + +// c4, c5 with exception and commit +0 +<{ + PUSHREF c4 POP + PUSHREF c5 POP + COMMIT + PUSHREF c4 POP + PUSHREF c5 POP + 55 THROW +}>s + // c4 for RUNVM +<{ + PUSHREF c4 POP // Ensure that this does not change + PUSHREF c5 POP // Ensure that this does not change + 32 4 + RUNVM + CTOS SWAP CTOS SWAP + c4 PUSH CTOS // x{1234aaaa} + c5 PUSH CTOS // x{1234bbbb} +}>s 0 runvmx +.s { drop } depth 1- times // 0 55 x{abcdaaaa} x{abcdbbbb} x{1234aaaa} x{1234bbbb} 0 + +// Mode +8 - gas limit +0 +<{ AGAIN:<{ NOP }> }>s +200 +<{ 8 RUNVM 1234 PUSHINT }>s 0 runvmx +.s { drop } depth 1- times // 215 -14 215 1234 0 + +// Gas limit of parent vm is too low +0 +<{ AGAIN:<{ NOP }> }>s +1000000 +<{ 8 RUNVM 1234 PUSHINT }>s 300 8 runvmx +.s { drop } depth 1- times // 301 -14 301 + +// Mode +64 - hard gas limit +0 <{ AGAIN:<{ NOP }> }>s 200 500 +<{ 8 64 + RUNVM 1234 PUSHINT }>s 0 runvmx +.s { drop } depth 1- times // 215 -14 215 1234 0 +0 <{ ACCEPT AGAIN:<{ NOP }> }>s 200 500 +<{ 8 64 + RUNVM 1234 PUSHINT }>s 0 runvmx +.s { drop } depth 1- times // 517 -14 517 1234 0 + +// 10000 nested VMs (recursively calculating 1+...+10000) +<{ + DUP + 0 EQINT + IFJMP:<{ + DROP DROP + 0 PUSHINT + }> + OVER OVER DEC + 2 PUSHINT + s2 PUSH + 0 RUNVM + 11 THROWIF + ADD + NIP +}>s constant code1 +<{ code1 PUSHSLICE 10000 PUSHINT 2 PUSHINT code1 PUSHSLICE 0 RUNVM }>s 10000000 8 runvmx // Show gas +.s { drop } depth 1- times // 50005000 0 0 2710286 + +// Same thing, but out of gas +<{ code1 PUSHSLICE 10000 PUSHINT 2 PUSHINT code1 PUSHSLICE 0 RUNVM }>s 100000 8 runvmx // Show gas +.s { drop } depth 1- times // 100001 -14 100001 + +// RUNVMX +0 +<{ AGAIN:<{ NOP }> }>s +200 +<{ 8 PUSHINT RUNVMX 1234 PUSHINT }>s 0 runvmx +.s { drop } depth 1- times // 215 -14 215 1234 0 + +// +128 - separate loaded_cells + +<{ DUP CTOS DROP 2 INT <{ CTOS DROP CTOS DROP }>s SLICE 10000 INT 8 RUNVM }>s 1000000 8 runvmx +.s { drop } depth 1- times // 0 202 0 509 + +<{ DUP CTOS DROP 2 INT <{ CTOS DROP CTOS DROP }>s SLICE 10000 INT 8 128 + RUNVM }>s 1000000 8 runvmx +.s { drop } depth 1- times // 0 277 0 584 + +// +256 - fixed number of return values +11 22 33 3 +<{ 1 INT 2 INT 3 INT 4 INT 5 INT }>s +3 +<{ 256 RUNVM }>s 0 runvmx +.s { drop } depth 1- times // 3 4 5 0 0 + +11 22 33 3 +<{ 1 INT 2 INT 3 INT 4 INT 5 INT }>s +0 +<{ 256 RUNVM }>s 0 runvmx +.s { drop } depth 1- times // 0 0 + +11 22 33 3 +<{ 1 INT 2 INT 3 INT 4 INT 5 INT 77 THROW }>s +3 +<{ 256 RUNVM }>s 0 runvmx +.s { drop } depth 1- times // 0 77 0 + +11 22 33 3 +<{ 1 INT 2 INT 3 INT 4 INT 5 INT }>s +20 +<{ 256 RUNVM }>s 0 runvmx +.s { drop } depth 1- times // 0 -3 0 + +// GASCONSUMED +<{ 10 INT 20 INT ADD DROP GASCONSUMED }>s 0 runvmx +.s { drop } depth 1- times // 106 0 +0 <{ 10 INT 20 INT ADD DROP GASCONSUMED }>s +<{ 100 INT 200 INT 300 INT MUL DIV DROP 0 RUNVM GASCONSUMED }>s 0 runvmx +.s { drop } depth 1- times // 106 0 367 0 diff --git a/crypto/test/modbigint.cpp b/crypto/test/modbigint.cpp new file mode 100644 index 00000000..75051fa6 --- /dev/null +++ b/crypto/test/modbigint.cpp @@ -0,0 +1,1074 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#include +#include +#include +#include +#include +#include + +namespace modint { + +enum { mod_cnt = 32 }; + +// mod_cnt = 9 => integers -2^268 .. 2^268 +// mod_cnt = 18 => integers -2^537 .. 2^537 +// mod_cnt = 32 => integers -2^955 .. 2^955 +constexpr int mod[mod_cnt] = {999999937, 999999929, 999999893, 999999883, 999999797, 999999761, 999999757, 999999751, + 999999739, 999999733, 999999677, 999999667, 999999613, 999999607, 999999599, 999999587, + 999999541, 999999527, 999999503, 999999491, 999999487, 999999433, 999999391, 999999353, + 99999337, 999999323, 999999229, 999999223, 999999197, 999999193, 999999191, 999999181}; + +// invm[i][j] = mod[i]^(-1) modulo mod[j] +int invm[mod_cnt][mod_cnt]; + +int gcdx(int a, int b, int& u, int& v); + +template +struct ModArray; + +template +struct MixedRadix; + +template +struct ArrayRawDumpRef; + +template +std::ostream& raw_dump_array(std::ostream& os, const std::array& arr) { + os << '['; + for (auto x : arr) { + os << ' ' << x; + } + return os << " ]"; +} + +template +struct MixedRadix { + enum { n = N }; + int a[N]; + MixedRadix(int v) { + set_int(v); + } + MixedRadix() = default; + MixedRadix(const MixedRadix&) = default; + MixedRadix(std::initializer_list l) { + auto sz = std::min(l.size(), (std::size_t)N); + std::copy(l.begin(), l.begin() + sz, a); + std::fill(a + sz, a + N, 0); + } + MixedRadix(const std::array& arr) { + std::copy(arr.begin(), arr.end(), a); + } + template + MixedRadix(const MixedRadix& other) { + static_assert(M >= N); + std::copy(other.a, other.a + N, a); + } + MixedRadix(const ModArray& other); + MixedRadix(const ModArray& other, bool sgnd); + + MixedRadix& set_zero() { + std::fill(a, a + N, 0); + return *this; + } + MixedRadix& set_one() { + a[0] = 1; + std::fill(a + 1, a + N, 0); + return *this; + } + MixedRadix& set_int(int v) { + a[0] = v; + std::fill(a + 1, a + N, 0); + return *this; + } + + MixedRadix copy() const { + return MixedRadix{*this}; + } + + static const int* mod_array() { + return mod; + } + + static int modulus(int i) { + return mod[i]; + } + + int sgn() const { + int i = N - 1; + while (i >= 0 && !a[i]) { + --i; + } + return i < 0 ? 0 : (a[i] > 0 ? 1 : -1); + } + + int cmp(const MixedRadix& other) const { + int i = N - 1; + while (i >= 0 && a[i] == other.a[i]) { + --i; + } + return i < 0 ? 0 : (a[i] > other.a[i] ? 1 : -1); + } + + bool is_small() const { + return !a[N - 1] || a[N - 1] == -1; + } + + bool operator==(const MixedRadix& other) const { + return std::equal(a, a + N, other.a); + } + + bool operator!=(const MixedRadix& other) const { + return !std::equal(a, a + N, other.a); + } + + bool operator<(const MixedRadix& other) const { + return cmp(other) < 0; + } + + bool operator<=(const MixedRadix& other) const { + return cmp(other) <= 0; + } + + bool operator>(const MixedRadix& other) const { + return cmp(other) > 0; + } + + bool operator>=(const MixedRadix& other) const { + return cmp(other) >= 0; + } + + explicit operator bool() const { + return sgn(); + } + + bool operator!() const { + return !sgn(); + } + + MixedRadix& negate() { + int i = 0; + while (i < N - 1 && !a[i]) { + i++; + } + a[i]--; + for (; i < N; i++) { + a[i] = mod[i] - a[i] - 1; + } + a[N - 1] -= mod[N - 1]; + return *this; + } + + static const MixedRadix& pow2(int power); + static MixedRadix negpow2(int power) { + return -pow2(power); + } + + template + const MixedRadix& as_shorter() const { + static_assert(M <= N,"error"); + return *reinterpret_cast*>(this); + } + + MixedRadix& import_mod_array(const int* data, bool sgnd = true) { + for (int i = 0; i < N; i++) { + a[i] = data[i] % mod[i]; + } + for (int i = 0; i < N; i++) { + if (a[i] < 0) { + a[i] += mod[i]; + } + for (int j = i + 1; j < N; j++) { + a[j] = (int)((long long)(a[j] - a[i]) * invm[i][j] % mod[j]); + } + } + if (sgnd && a[N - 1] > (mod[N - 1] >> 1)) { + a[N - 1] -= mod[N - 1]; + } + return *this; + } + + MixedRadix& operator=(const MixedRadix&) = default; + + template + MixedRadix& operator=(const MixedRadix& other) { + static_assert(M >= N); + std::copy(other.a, other.a + N, a); + } + + MixedRadix& import_mod_array(const ModArray& other, bool sgnd = true); + + MixedRadix& operator=(const ModArray& other) { + return import_mod_array(other); + } + + MixedRadix& set_sum(const MixedRadix& x, const MixedRadix& y, int factor = 1) { + long long carry = 0; + for (int i = 0; i < N; i++) { + long long acc = x.a[i] + carry + (long long)factor * y.a[i]; + carry = acc / mod[i]; + a[i] = (int)(acc - carry * mod[i]); + if (a[i] < 0) { + a[i] += mod[i]; + --carry; + } + } + if (a[N - 1] >= 0 && carry == -1) { + a[N - 1] -= mod[N - 1]; + } + return *this; + } + + MixedRadix& operator+=(const MixedRadix& other) { + return set_sum(*this, other); + } + + MixedRadix& operator-=(const MixedRadix& other) { + return set_sum(*this, other, -1); + } + + static const MixedRadix& zero(); + static const MixedRadix& one(); + + MixedRadix& operator*=(int factor) { + return set_sum(zero(), *this, factor); + } + + MixedRadix operator-() const { + MixedRadix copy{*this}; + copy.negate(); + return copy; + } + + MixedRadix operator+(const MixedRadix& other) const { + MixedRadix res; + res.set_sum(*this, other); + return res; + } + + MixedRadix operator-(const MixedRadix& other) const { + MixedRadix res; + res.set_sum(*this, other, -1); + return res; + } + + MixedRadix operator*(int factor) const { + MixedRadix res; + res.set_sum(zero(), *this, factor); + return res; + } + + int operator%(int b) const { + int x = a[N - 1] % b; + for (int i = N - 2; i >= 0; --i) { + x = ((long long)x * mod[i] + a[i]) % b; + } + return ((x ^ b) < 0 && x) ? x + b : x; + } + + explicit operator double() const { + double acc = 0.; + for (int i = N - 1; i >= 0; --i) { + acc = acc * mod[i] + a[i]; + } + return acc; + } + + explicit operator long long() const { + unsigned long long acc = 0; + for (int i = N - 1; i >= 0; --i) { + acc = acc * mod[i] + a[i]; + } + return acc; + } + + MixedRadix& to_base(int base) { + int k = N - 1; + while (k > 0 && !a[k]) { + --k; + } + if (k <= 0) { + return *this; + } + for (int i = k - 1; i >= 0; --i) { + // a[i..k] := a[i+1..k] * mod[i] + a[i] + long long carry = a[i]; + for (int j = i; j < k; j++) { + long long t = (long long)a[j + 1] * mod[i] + carry; + carry = t / base; + a[j] = (int)(t - carry * base); + } + a[k] = (int)carry; + } + return *this; + } + + std::ostream& print_dec_destroy(std::ostream& os) { + int s = sgn(); + if (s < 0) { + os << '-'; + negate(); + } else if (!s) { + os << '0'; + return os; + } + to_base(1000000000); + int i = N - 1; + while (!a[i] && i > 0) { + --i; + } + os << a[i]; + while (--i >= 0) { + char buff[12]; + sprintf(buff, "%09d", a[i]); + os << buff; + } + return os; + } + + std::ostream& print_dec(std::ostream& os) const& { + MixedRadix copy{*this}; + return copy.print_dec_destroy(os); + } + + std::ostream& print_dec(std::ostream& os) && { + return print_dec_destroy(os); + } + + std::string to_dec_string_destroy() { + std::ostringstream os; + print_dec_destroy(os); + return std::move(os).str(); + } + + std::string to_dec_string() const& { + MixedRadix copy{*this}; + return copy.to_dec_string_destroy(); + } + + std::string to_dec_string() && { + return to_dec_string_destroy(); + } + + bool to_binary_destroy(unsigned char* arr, int size, bool sgnd = true) { + if (size <= 0) { + return false; + } + int s = (sgnd ? sgn() : 1); + memset(arr, 0, size); + if (s < 0) { + negate(); + } else if (!s) { + return true; + } + to_base(1 << 30); + long long acc = 0; + int bits = 0, j = size; + for (int i = 0; i < N; i++) { + if (!j && a[i]) { + return false; + } + acc += ((long long)a[i] << bits); + bits += 30; + while (bits >= 8 && j > 0) { + arr[--j] = (unsigned char)(acc & 0xff); + bits -= 8; + acc >>= 8; + } + } + while (j > 0) { + arr[--j] = (unsigned char)(acc & 0xff); + acc >>= 8; + } + if (acc) { + return false; + } + if (!sgnd) { + return true; + } + if (s >= 0) { + return arr[0] <= 0x7f; + } + j = size - 1; + while (j >= 0 && !arr[j]) { + --j; + } + assert(j >= 0); + arr[j] = (unsigned char)(-arr[j]); + while (--j >= 0) { + arr[j] = (unsigned char)~arr[j]; + } + return arr[0] >= 0x80; + } + + bool to_binary(unsigned char* arr, int size, bool sgnd = true) const& { + MixedRadix copy{*this}; + return copy.to_binary_destroy(arr, size, sgnd); + } + + bool to_binary(unsigned char* arr, int size, bool sgnd = true) && { + return to_binary_destroy(arr, size, sgnd); + } + + std::ostream& raw_dump(std::ostream& os) const { + return raw_dump_array(os, a); + } + + ArrayRawDumpRef dump() const { + return {a}; + } +}; + +template +struct ModArray { + enum { n = N }; + int a[N]; + ModArray(int v) { + set_int(v); + } + ModArray(long long v) { + set_long(v); + } + ModArray(long v) { + set_long(v); + } + ModArray() = default; + ModArray(const ModArray&) = default; + ModArray(std::initializer_list l) { + auto sz = std::min(l.size(), (std::size_t)N); + std::copy(l.begin(), l.begin() + sz, a); + std::fill(a + sz, a + N, 0); + } + ModArray(const std::array& arr) { + std::copy(arr.begin(), arr.end(), a); + } + template + ModArray(const ModArray& other) { + static_assert(M >= N,"error"); + std::copy(other.a, other.a + N, a); + } + ModArray(const int* p) : a(p) { + } + ModArray(std::string str) { + assert(from_dec_string(str) && "not a decimal number"); + } + + ModArray& set_zero() { + std::fill(a, a + N, 0); + return *this; + } + ModArray& set_one() { + std::fill(a, a + N, 1); + return *this; + } + + ModArray& set_int(int v) { + if (v >= 0) { + std::fill(a, a + N, v); + } else { + for (int i = 0; i < N; i++) { + a[i] = mod[i] + v; + } + } + return *this; + } + + ModArray& set_long(long long v) { + for (int i = 0; i < N; i++) { + a[i] = v % mod[i]; + if (a[i] < 0) { + a[i] += mod[i]; + } + } + return *this; + } + + ModArray copy() const { + return ModArray{*this}; + } + + static const int* mod_array() { + return mod; + } + + static int modulus(int i) { + return mod[i]; + } + + static const ModArray& zero(); + static const ModArray& one(); + + ModArray& operator=(const ModArray&) = default; + + template + ModArray& operator=(const ModArray& other) { + static_assert(M >= N); + std::copy(other.a, other.a + N, a); + return *this; + } + + ModArray& negate() { + for (int i = 0; i < N; i++) { + a[i] = (a[i] ? mod[i] - a[i] : 0); + } + return *this; + } + + ModArray& norm_neg() { + for (int i = 0; i < N; i++) { + if (a[i] < 0) { + a[i] += mod[i]; + } + } + return *this; + } + + ModArray& normalize() { + for (int i = 0; i < N; i++) { + a[i] %= mod[i]; + if (a[i] < 0) { + a[i] += mod[i]; + } + } + return *this; + } + + bool is_zero() const { + for (int i = 0; i < N; i++) { + if (a[i]) { + return false; + } + } + return true; + } + + explicit operator bool() const { + return !is_zero(); + } + + bool operator!() const { + return is_zero(); + } + + bool operator==(const ModArray& other) const { + return std::equal(a, a + N, other.a); + } + + bool operator!=(const ModArray& other) const { + return !std::equal(a, a + N, other.a); + } + + bool operator==(long long val) const { + for (int i = 0; i < N; i++) { + int r = (int)(val % mod[i]); + if (a[i] != (r < 0 ? r + mod[i] : r)) { + return false; + } + } + return true; + } + + bool operator!=(long long val) const { + return !operator==(val); + } + + long long try_get_long() const { + return (long long)(MixedRadix<3>(*this)); + } + + bool fits_long() const { + return operator==(try_get_long()); + } + + explicit operator long long() const { + auto v = try_get_long(); + return operator==(v) ? v : -0x8000000000000000; + } + + ModArray& set_sum(const ModArray& x, const ModArray& y) { + for (int i = 0; i < N; i++) { + a[i] = x.a[i] + y.a[i]; + if (a[i] >= mod[i]) { + a[i] -= mod[i]; + } + } + return *this; + } + + ModArray& operator+=(const ModArray& other) { + for (int i = 0; i < N; i++) { + a[i] += other.a[i]; + if (a[i] >= mod[i]) { + a[i] -= mod[i]; + } + } + return *this; + } + + ModArray& operator+=(long long v) { + for (int i = 0; i < N; i++) { + a[i] = (int)((a[i] + v) % mod[i]); + if (a[i] < 0) { + a[i] += mod[i]; + } + } + return *this; + } + + ModArray& operator-=(const ModArray& other) { + for (int i = 0; i < N; i++) { + a[i] -= other.a[i]; + if (a[i] < 0) { + a[i] += mod[i]; + } + } + return *this; + } + + ModArray& operator-=(long long v) { + return (operator+=)(-v); + } + + ModArray& mul_arr(const int other[]) { + for (int i = 0; i < N; i++) { + a[i] = (int)(((long long)a[i] * other[i]) % mod[i]); + } + return *this; + } + + ModArray& operator*=(const ModArray& other) { + return mul_arr(other.a); + } + + template + ModArray& operator*=(const ModArray& other) { + static_assert(M >= N); + return mul_arr(other.a); + } + + ModArray& operator*=(int v) { + for (int i = 0; i < N; i++) { + a[i] = (int)(((long long)a[i] * v) % mod[i]); + } + return (v >= 0 ? *this : norm_neg()); + } + + ModArray& operator*=(long long v) { + for (int i = 0; i < N; i++) { + a[i] = (int)(((long long)a[i] * (v % mod[i])) % mod[i]); + } + return (v >= 0 ? *this : norm_neg()); + } + + ModArray& mul_add(int v, long long w) { + for (int i = 0; i < N; i++) { + a[i] = (int)(((long long)a[i] * v + w) % mod[i]); + if (a[i] < 0) { + a[i] += mod[i]; + } + } + return *this; + } + + // *this = (*this * other) + w + ModArray& mul_add(const ModArray& other, long long w) { + for (int i = 0; i < N; i++) { + a[i] = (int)(((long long)a[i] * other.a[i] + w) % mod[i]); + if (a[i] < 0) { + a[i] += mod[i]; + } + } + return *this; + } + + // *this = (*this << shift) + w + ModArray& lshift_add(int shift, long long w) { + return mul_add(pow2(shift), w); + } + + // *this = *this + other * w + ModArray& add_mul(const ModArray& other, long long w) { + for (int i = 0; i < N; i++) { + a[i] = (int)((a[i] + other.a[i] * w) % mod[i]); + if (a[i] < 0) { + a[i] += mod[i]; + } + } + return *this; + } + + // *this += w << shift + ModArray& add_lshift(int shift, long long w) { + return add_mul(pow2(shift), w); + } + + ModArray operator+(const ModArray& other) const { + ModArray copy{*this}; + copy += other; + return copy; + } + + ModArray operator-(const ModArray& other) const { + ModArray copy{*this}; + copy -= other; + return copy; + } + + ModArray operator+(long long other) const { + ModArray copy{*this}; + copy += other; + return copy; + } + + ModArray operator-(long long other) const { + ModArray copy{*this}; + copy += -other; + return copy; + } + + ModArray operator-() const { + ModArray copy{*this}; + copy.negate(); + return copy; + } + + ModArray operator*(const ModArray& other) const { + ModArray copy{*this}; + copy *= other; + return copy; + } + + ModArray operator*(long long other) const { + ModArray copy{*this}; + copy *= other; + return copy; + } + + bool invert() { + for (int i = 0; i < N; i++) { + int t; + if (gcdx(a[i], mod[i], a[i], t) != 1) { + return false; + } + if (a[i] < 0) { + a[i] += mod[i]; + } + } + return true; + } + + bool try_divide(const ModArray& other) { + for (int i = 0; i < N; i++) { + int q, t; + if (gcdx(other.a[i], mod[i], q, t) != 1) { + return false; + } + a[i] = (int)((long long)a[i] * q % mod[i]); + if (a[i] < 0) { + a[i] += mod[i]; + } + } + return true; + } + + ModArray& operator/=(const ModArray& other) { + if (!try_divide(other)) { + assert(false); // division by zero? + } + return *this; + } + + ModArray operator/(const ModArray& other) { + ModArray copy{*this}; + copy /= other; + return copy; + } + + static const ModArray& pow2(int power); + static const ModArray& negpow2(int power); + + ModArray& operator<<=(int lshift) { + return operator*=(pow2(lshift)); + } + + ModArray operator<<(int lshift) const { + return operator*(pow2(lshift)); + } + + ModArray& operator>>=(int rshift) { + return operator/=(pow2(rshift)); + } + + ModArray operator>>(int rshift) const { + return operator/(pow2(rshift)); + } + + template + const ModArray& as_shorter() const { + static_assert(M <= N,"error"); + return *reinterpret_cast*>(this); + } + + MixedRadix& to_mixed_radix(MixedRadix& dest, bool sgnd = true) const { + return dest.import_mod_array(a, sgnd); + } + + MixedRadix to_mixed_radix(bool sgnd = true) const { + return MixedRadix(*this, sgnd); + } + + int operator%(int div) const { + return to_mixed_radix() % div; + } + + explicit operator double() const { + return (double)to_mixed_radix(); + } + + std::string to_dec_string() const { + return MixedRadix(*this).to_dec_string(); + } + + std::ostream& print_dec(std::ostream& os, bool sgnd = true) const { + return MixedRadix(*this, sgnd).print_dec(os); + } + + bool to_binary(unsigned char* arr, int size, bool sgnd = true) const { + return MixedRadix(*this, sgnd).to_binary(arr, size, sgnd); + } + + template + bool to_binary(std::array& arr, bool sgnd = true) const { + return to_binary(arr.data(), M, sgnd); + } + + bool from_dec_string(const char* start, const char* end) { + set_zero(); + if (start >= end) { + return false; + } + bool sgn = (*start == '-'); + if (sgn && ++start == end) { + return false; + } + int acc = 0, pow = 1; + while (start < end) { + if (*start < '0' || *start > '9') { + return false; + } + acc = acc * 10 + (*start++ - '0'); + pow *= 10; + if (pow >= 1000000000) { + mul_add(pow, acc); + pow = 1; + acc = 0; + } + } + if (pow > 1) { + mul_add(pow, acc); + } + if (sgn) { + negate(); + } + return true; + } + + bool from_dec_string(std::string str) { + return from_dec_string(str.data(), str.data() + str.size()); + } + + ModArray& from_binary(const unsigned char* arr, int size, bool sgnd = true) { + set_zero(); + if (size <= 0) { + return *this; + } + int i = 0, pow = 0; + long long acc = (sgnd && arr[0] >= 0x80 ? -1 : 0); + while (i < size && arr[i] == (unsigned char)acc) { + i++; + } + for (; i < size; i++) { + pow += 8; + acc = (acc * 256) + arr[i]; + if (pow >= 56) { + lshift_add(pow, acc); + acc = pow = 0; + } + } + if (pow || acc) { + lshift_add(pow, acc); + } + return *this; + } + + template + ModArray& from_binary(const std::array& arr, bool sgnd = true) { + return from_binary(arr.data(), M, sgnd); + } + + std::ostream& raw_dump(std::ostream& os) const { + return raw_dump_array(os, a); + } + + ArrayRawDumpRef dump() const { + return {a}; + } +}; + +template +MixedRadix::MixedRadix(const ModArray& other) { + import_mod_array(other.a); +} + +template +MixedRadix::MixedRadix(const ModArray& other, bool sgnd) { + import_mod_array(other.a, sgnd); +} + +template +MixedRadix& MixedRadix::import_mod_array(const ModArray& other, bool sgnd) { + return import_mod_array(other.a, sgnd); +} + +template +std::ostream& operator<<(std::ostream& os, const ModArray& x) { + return x.print_dec(os); +} + +template +std::ostream& operator<<(std::ostream& os, const MixedRadix& x) { + return x.print_dec(os); +} + +template +std::ostream& operator<<(std::ostream& os, MixedRadix&& x) { + return x.print_dec_destroy(os); +} + +template +struct ArrayRawDumpRef { + const std::array& ref; + ArrayRawDumpRef(const std::array& _ref) : ref(_ref){}; +}; + +template +std::ostream& operator<<(std::ostream& os, ArrayRawDumpRef rd_ref) { + return raw_dump_array(os, rd_ref.ref); +}; + +constexpr int pow2_cnt = 1001; + +ModArray Zero(0), One(1), Pow2[pow2_cnt], NegPow2[pow2_cnt]; +MixedRadix Zero_mr(0), One_mr(1), Pow2_mr[pow2_cnt], NegPow2_mr[pow2_cnt]; + +template +const MixedRadix& MixedRadix::pow2(int power) { + return Pow2_mr[power].as_shorter(); +} + +/* +template +const MixedRadix& MixedRadix::negpow2(int power) { + return NegPow2_mr[power].as_shorter(); +} +*/ + +template +const ModArray& ModArray::pow2(int power) { + return Pow2[power].as_shorter(); +} + +template +const ModArray& ModArray::negpow2(int power) { + return NegPow2[power].as_shorter(); +} + +template +const ModArray& ModArray::zero() { + return Zero.as_shorter(); +} + +template +const ModArray& ModArray::one() { + return One.as_shorter(); +} + +template +const MixedRadix& MixedRadix::zero() { + return Zero_mr.as_shorter(); +} + +template +const MixedRadix& MixedRadix::one() { + return One_mr.as_shorter(); +} + +void init_pow2() { + Pow2[0].set_one(); + Pow2_mr[0].set_one(); + for (int i = 1; i < pow2_cnt; i++) { + Pow2[i].set_sum(Pow2[i - 1], Pow2[i - 1]); + Pow2_mr[i].set_sum(Pow2_mr[i - 1], Pow2_mr[i - 1]); + } + for (int i = 0; i < pow2_cnt; i++) { + NegPow2[i] = -Pow2[i]; + NegPow2_mr[i] = -Pow2_mr[i]; + } +} + +int gcdx(int a, int b, int& u, int& v) { + int a1 = 1, a2 = 0, b1 = 0, b2 = 1; + while (b) { + int q = a / b; + int t = a - q * b; + a = b; + b = t; + t = a1 - q * b1; + a1 = b1; + b1 = t; + t = a2 - q * b2; + a2 = b2; + b2 = t; + } + u = a1; + v = a2; + return a; +} + +void init_invm() { + for (int i = 0; i < mod_cnt; i++) { + assert(mod[i] > 0 && mod[i] <= (1 << 30)); + for (int j = 0; j < i; j++) { + if (gcdx(mod[i], mod[j], invm[i][j], invm[j][i]) != 1) { + assert(false); + } + if (invm[i][j] < 0) { + invm[i][j] += mod[j]; + } + if (invm[j][i] < 0) { + invm[j][i] += mod[i]; + } + } + } +} + +void init() { + init_invm(); + init_pow2(); +} + +} // namespace modint diff --git a/crypto/test/test-bigint.cpp b/crypto/test/test-bigint.cpp new file mode 100644 index 00000000..a6f6e8d6 --- /dev/null +++ b/crypto/test/test-bigint.cpp @@ -0,0 +1,876 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/refcnt.hpp" +#include "common/bigint.hpp" +#include "common/refint.h" +#include "modbigint.cpp" + +#include "td/utils/tests.h" + +int mkint_chk_mode = -1, res_chk_mode = 0; +long long iterations = 100000, cur_iteration = -1, debug_iteration = -2; +#define IFDEBUG if (cur_iteration == debug_iteration || debug_iteration == -3) + +using BInt = modint::ModArray<18>; // integers up to 2^537 +using MRInt = modint::MixedRadix<18>; // auxiliary integer representation for printing, comparing etc + +MRInt p2_256, np2_256, p2_63, np2_63; +constexpr long long ll_min = -2 * (1LL << 62), ll_max = ~ll_min; +constexpr double dbl_pow256 = 1.1579208923731619542e77 /* 0x1p256 */; // 2^256 + +std::mt19937_64 Random(666); + +template +bool equal(td::RefInt256 x, T y) { + return !td::cmp(x, y); +} + +bool equal_or_nan(td::RefInt256 x, td::RefInt256 y) { + return equal(x, y) || (!x->is_valid() && !y->fits_bits(257)) || (!y->is_valid() && !x->fits_bits(257)); +} + +#define CHECK_EQ(__x, __y) CHECK(equal(__x, __y)) +#define CHECK_EQ_NAN(__x, __y) CHECK(equal_or_nan(__x, __y)) + +bool mr_in_range(const MRInt& x) { + return x < p2_256 && x >= np2_256; +} + +bool mr_is_small(const MRInt& x) { + return x < p2_63 && x >= np2_63; +} + +bool mr_fits_bits(const MRInt& x, int bits) { + if (bits > 0) { + return x < MRInt::pow2(bits - 1) && x >= MRInt::negpow2(bits - 1); + } else { + return !bits && !x.sgn(); + } +} + +bool mr_ufits_bits(const MRInt& x, int bits) { + return bits >= 0 && x.sgn() >= 0 && x < MRInt::pow2(bits); +} + +struct ShowBin { + unsigned char* data; + ShowBin(unsigned char _data[64]) : data(_data) { + } +}; + +std::ostream& operator<<(std::ostream& os, ShowBin bin) { + int i = 0, s = bin.data[0]; + if (s == 0 || s == 0xff) { + while (i < 64 && bin.data[i] == s) { + i++; + } + } + if (i >= 3) { + os << (s ? "ff..ff" : "00..00"); + } else { + i = 0; + } + constexpr static char hex_digits[] = "0123456789abcdef"; + while (i < 64) { + int t = bin.data[i++]; + os << hex_digits[t >> 4] << hex_digits[t & 15]; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, const td::AnyIntView& x) { + os << '['; + for (int i = 0; i < x.size(); i++) { + os << ' ' << x.digits[i]; + } + os << " ]"; + return os; +} + +template +bool extract_value_any_bool(BInt& val, const td::AnyIntView& x, bool chk_norm = true) { + int n = x.size(); + if (n <= 0 || n > x.max_size() || (!x.digits[n - 1] && n > 1)) { + return false; + } + assert(n == 1 || x.digits[n - 1] != 0); + val.set_zero(); + for (int i = n - 1; i >= 0; --i) { + val.lshift_add(T::word_shift, x.digits[i]); + if (chk_norm && (x.digits[i] < -T::Half || x.digits[i] >= T::Half)) { + return false; // unnormalized + } + } + return true; +} + +template +bool extract_value_bool(BInt& val, const T& x, bool chk_norm = true) { + return extract_value_any_bool(val, x.as_any_int(), chk_norm); +} + +BInt extract_value_any(const td::AnyIntView& x, bool chk_norm = true) { + BInt res; + CHECK(extract_value_any_bool(res, x, chk_norm)); + return res; +} + +template +BInt extract_value(const T& x, bool chk_norm = true) { + return extract_value_any(x.as_any_int(), chk_norm); +} + +template +BInt extract_value_alt(const T& x) { + BInt res; + const int* md = res.mod_array(); + for (int i = 0; i < res.n / 2; i++) { + T copy{x}; + int m1 = md[2 * i], m2 = md[2 * i + 1]; + long long rem = copy.divmod_short((long long)m1 * m2); + res.a[2 * i] = (int)(rem % m1); + res.a[2 * i + 1] = (int)(rem % m2); + } + if (res.n & 1) { + T copy{x}; + res.a[res.n - 1] = (int)copy.divmod_short(md[res.n - 1]); + } + return res; +} + +constexpr int min_spec_int = -0xfd08, max_spec_int = 0xfd07; +// x = sgn*(ord*256+a*16+b) => sgn*((32+a)*2^(ord-2) + b - 8) +// x = -0xfd08 => -2^256 ... x = 0xfd07 => 2^256 - 1 +td::RefInt256 make_special_int(int x, BInt* ptr = nullptr, unsigned char bin[64] = nullptr) { + bool sgn = (x < 0); + if (sgn) { + x = -x; + } + int ord = (x >> 8) - 2, a = 32 + ((x >> 4) & 15), b = (x & 15) - 8; + if (ord < 0) { + a >>= -ord; + ord = 0; + } + if (sgn) { + a = -a; + b = -b; + } + if (ptr) { + ptr->set_int(a); + *ptr <<= ord; + *ptr += b; + } + if (bin) { + int acc = b, r = ord; + for (int i = 63; i >= 0; --i) { + if (r < 8) { + acc += ((unsigned)a << r); + r = 1024; + } + r -= 8; + bin[i] = (unsigned char)(acc & 0xff); + acc >>= 8; + } + } + return (td::make_refint(a) << ord) + b; +} + +int rand_int(int min, int max) { + return min + (int)(Random() % (max - min + 1)); +} + +unsigned randu() { + return (unsigned)(Random() << 16); +} + +bool coin() { + return Random() & (1 << 28); +} + +// returns 0 with probability 1/2, 1 with prob. 1/4, ..., k with prob. 1/2^(k+1) +int randexp(int max = 63, int min = 0) { + return min + td::count_leading_zeroes64(Random() | (1ULL << (63 - max + min))); +} + +void bin_add_small(unsigned char bin[64], long long val, int shift = 0) { + val *= (1 << (shift & 7)); + for (int i = 63 - (shift >> 3); i >= 0 && val; --i) { + val += bin[i]; + bin[i] = (unsigned char)val; + val >>= 8; + } +} + +// adds sgn * (random number less than 2^(ord - ord2)) * 2^ord2 +td::RefInt256 add_random_bits(td::RefInt256 x, BInt& val, unsigned char bin[64], int ord2, int ord, int sgn = 1) { + int t; + do { + t = std::max((ord - 1) & -16, ord2); + int a = sgn * rand_int(0, (1 << (ord - t)) - 1); + // add a << t + val.add_lshift(t, a); + x += td::make_refint(a) << t; + bin_add_small(bin, a, t); + ord = t; + } while (t > ord2); + return x; +} + +// generates a random integer in range -2^256 .. 2^256-1 (and sometimes outside) +// distribution is skewed towards +/- 2^n +/- 2^n +/- smallint, but completely random integers are also generated +td::RefInt256 make_random_int0(BInt& val, unsigned char bin[64]) { + memset(bin, 0, 64); + int ord = rand_int(-257, 257); + if (ord <= 2 && ord >= -2) { + // -2..2 represent themselves + val.set_int(ord); + bin_add_small(bin, ord); + return td::make_refint(ord); + } + int sgn = (ord < 0 ? -1 : 1); + ord = sgn * ord - 1; + int f = std::min(ord, randexp(15)), a = sgn * rand_int(1 << f, (2 << f) - 1); + ord -= f; + // first summand is a << ord + auto res = td::make_refint(a) << ord; + val.set_int(a); + val <<= ord; + bin_add_small(bin, a, ord); + if (!ord) { + // all bits ready + return res; + } + for (int s = 0; s < 2 && ord; s++) { + // decide whether we want an intermediate order (50%), and whether we want randomness above/below that order + int ord2 = (s ? 0 : std::max(0, rand_int(~ord, ord - 1))); + if (!rand_int(0, 4)) { // 20% + // random bits between ord2 and ord + res = add_random_bits(std::move(res), val, bin, ord2, ord, sgn); + } + if (rand_int(0, 4)) { // 80% + // non-zero adjustment + f = randexp(15); + a = rand_int(-(2 << f) + 1, (2 << f) - 1); + ord = std::max(ord2 - f, 0); + // add a << ord + val.add_lshift(ord, a); + res += (td::make_refint(a) << ord); + bin_add_small(bin, a, ord); + } + } + return res; +} + +td::RefInt256 make_random_int(BInt& val, unsigned char bin[64]) { + while (true) { + auto res = make_random_int0(val, bin); + if (res->fits_bits(257)) { + return res; + } + } +} + +void check_one_int_repr(td::RefInt256 x, int mode, int in_range, const BInt* valptr = nullptr, + const unsigned char bin[64] = nullptr) { + CHECK(x.not_null() && (in_range <= -2 || x->is_valid())); + if (!x->is_valid()) { + // not much to check when x is a NaN + unsigned char bytes[64]; + if (valptr) { + // check that the true answer at `valptr` is out of range + CHECK(!mr_in_range(valptr->to_mixed_radix())); + if (mode & 0x200) { + // check BInt binary export + valptr->to_binary(bytes, 64); + if (bin) { + // check that the two true answers match + CHECK(!memcmp(bin, bytes, 64)); + } else { + bin = bytes; + } + } + } + if (bin) { + // check that the true answer in `bin` is out of range + int i = 0, sgn = (bin[0] >= 0x80 ? -1 : 0); + while (i < 32 && bin[i] == (unsigned char)sgn) + ; + CHECK(i < 32); + if (valptr && (mode & 0x100)) { + // check BInt binary export + BInt val2; + val2.from_binary(bin, 64); + CHECK(*valptr == val2); + } + } + return; + } + unsigned char bytes[64]; + CHECK(x->export_bytes(bytes, 64)); + if (bin) { + CHECK(!memcmp(bytes, bin, 64)); + } + BInt val = extract_value(*x); + if (valptr) { + if (val != *valptr) { + std::cerr << "extracted " << val << " from " << x << ' ' << x->as_any_int() << ", expected " << *valptr + << std::endl; + } + CHECK(val == *valptr); + } + if (mode & 1) { + BInt val2 = extract_value_alt(*x); + CHECK(val == val2); + } + if (mode & 2) { + // check binary import + td::BigInt256 y; + y.import_bytes(bytes, 64); + CHECK(y == *x); + } + if (mode & 0x100) { + // check binary import for BInt + BInt val2; + val2.from_binary(bytes, 64); + CHECK(val == val2); + } + // check if small (fits into 64 bits) + long long xval = (long long)val; + bool is_small = (xval != ll_min || val == xval); + CHECK(is_small == x->fits_bits(64)); + if (is_small) { + // special check for small (64-bit) values + CHECK(x->to_long() == xval); + CHECK((long long)td::bswap64(*(long long*)(bytes + 64 - 8)) == xval); + CHECK(in_range); + // check sign + CHECK(x->sgn() == (xval > 0 ? 1 : (xval < 0 ? -1 : 0))); + // check comparison with long long + CHECK(x == xval); + CHECK(!cmp(x, xval)); + if (mode & 4) { + // check constructor from long long + CHECK(!cmp(x, td::make_refint(xval))); + if (xval != ll_min) { + CHECK(x > xval - 1); + CHECK(x > td::make_refint(xval - 1)); + } + if (xval != ll_max) { + CHECK(x < xval + 1); + CHECK(x < td::make_refint(xval + 1)); + } + } + if (!(mode & ~0x107)) { + return; // fast check for small ints in this case + } + } + + MRInt mval(val); // somewhat slow + bool val_in_range = mr_in_range(mval); + CHECK(x->fits_bits(257) == val_in_range); + if (in_range >= 0) { + CHECK((int)val_in_range == in_range); + } + if (mode & 0x200) { + // check binary export for BInt + unsigned char bytes2[64]; + mval.to_binary(bytes2, 64); + CHECK(!memcmp(bytes, bytes2, 64)); + } + // check sign + int sgn = mval.sgn(); + CHECK(x->sgn() == sgn); + CHECK(is_small == mr_is_small(mval)); + if (is_small) { + CHECK((long long)mval == xval); + } + if (mode & 0x10) { + // check decimal export + std::string dec = mval.to_dec_string(); + CHECK(x->to_dec_string() == dec); + // check decimal import + td::BigInt256 y; + int l = y.parse_dec(dec); + CHECK((std::size_t)l == dec.size() && y == *x); + if (mode & 0x1000) { + // check decimal import for BInt + BInt val2; + CHECK(val2.from_dec_string(dec) && val2 == val); + } + } + if (mode & 0x20) { + // check binary bit size + int sz = x->bit_size(); + CHECK(sz >= 0 && sz <= 300); + CHECK(x->fits_bits(sz) && (!sz || !x->fits_bits(sz - 1))); + CHECK(mr_fits_bits(mval, sz) && !mr_fits_bits(mval, sz - 1)); + int usz = x->bit_size(false); + CHECK(sgn >= 0 || usz == 0x7fffffff); + if (sgn >= 0) { + CHECK(x->unsigned_fits_bits(usz) && (!usz || !x->unsigned_fits_bits(usz - 1))); + CHECK(mr_ufits_bits(mval, usz) && !mr_ufits_bits(mval, usz - 1)); + } else { + CHECK(!x->unsigned_fits_bits(256) && !x->unsigned_fits_bits(300)); + } + } +} + +void init_aux() { + np2_256 = p2_256 = MRInt::pow2(256); + np2_256.negate(); + CHECK(np2_256 == MRInt::negpow2(256)); + p2_63 = np2_63 = MRInt::pow2(63); + np2_63.negate(); + CHECK(np2_63 == MRInt::negpow2(63)); +} + +std::vector SpecInt; +BInt SpecIntB[max_spec_int - min_spec_int + 1]; + +void init_check_special_ints() { + std::cerr << "check special ints" << std::endl; + BInt b; + unsigned char binary[64]; + for (int idx = min_spec_int - 512; idx <= max_spec_int + 512; idx++) { + td::RefInt256 x = make_special_int(idx, &b, binary); + check_one_int_repr(x, mkint_chk_mode, idx >= min_spec_int && idx <= max_spec_int, &b, binary); + if (idx >= min_spec_int && idx <= max_spec_int) { + SpecIntB[idx - min_spec_int] = b; + SpecInt.push_back(std::move(x)); + } + } +} + +void check_res(td::RefInt256 y, const BInt& yv) { + check_one_int_repr(std::move(y), res_chk_mode, -2, &yv); +} + +void check_unary_ops_on(td::RefInt256 x, const BInt& xv) { + // NEGATE + BInt yv = -xv; + check_res(-x, yv); + // NOT + check_res(~x, yv -= 1); +} + +void check_unary_ops() { + std::cerr << "check unary ops" << std::endl; + for (int idx = min_spec_int; idx <= max_spec_int; idx++) { + check_unary_ops_on(SpecInt[idx - min_spec_int], SpecIntB[idx - min_spec_int]); + } +} + +void check_pow2_ops(int shift) { + // POW2 + td::RefInt256 r{true}; + r.unique_write().set_pow2(shift); + check_res(r, BInt::pow2(shift)); + // POW2DEC + r.unique_write().set_pow2(shift).add_tiny(-1).normalize(); + check_res(r, BInt::pow2(shift) - 1); + // NEGPOW2 + r.unique_write().set_pow2(shift).negate().normalize(); + check_res(r, -BInt::pow2(shift)); +} + +void check_pow2_ops() { + std::cerr << "check power-2 ops" << std::endl; + for (int i = 0; i <= 256; i++) { + check_pow2_ops(i); + } +} + +void check_shift_ops_on(int shift, td::RefInt256 x, const BInt& xv, const MRInt& mval) { + // LSHIFT + check_res(x << shift, xv << shift); + // FITS + CHECK(x->fits_bits(shift) == mr_fits_bits(mval, shift)); + // UFITS + CHECK(x->unsigned_fits_bits(shift) == mr_ufits_bits(mval, shift)); + // ADDPOW2 / SUBPOW2 + auto y = x; + y.write().add_pow2(shift).normalize(); + check_res(std::move(y), xv + BInt::pow2(shift)); + y = x; + y.write().sub_pow2(shift).normalize(); + check_res(std::move(y), xv - BInt::pow2(shift)); + // RSHIFT, MODPOW2 + for (int round_mode = -1; round_mode <= 1; round_mode++) { + auto r = x, q = td::rshift(x, shift, round_mode); // RSHIFT + CHECK(q.not_null() && q->is_valid()); + r.write().mod_pow2(shift, round_mode).normalize(); // MODPOW2 + CHECK(r.not_null() && r->is_valid()); + if (round_mode < 0) { + CHECK(!cmp(x >> shift, q)); // operator>> should be equivalent to td::rshift + } + BInt qv = extract_value(*q), rv = extract_value(*r); + // check main division equality (q << shift) + r == x + CHECK((qv << shift) + rv == xv); + MRInt rval(rv); + // check remainder range + switch (round_mode) { + case 1: + rval.negate(); // fallthrough + case -1: + CHECK(mr_ufits_bits(rval, shift)); + break; + case 0: + CHECK(mr_fits_bits(rval, shift)); + } + } +} + +void check_shift_ops() { + std::cerr << "check left/right shift ops" << std::endl; + for (int idx = min_spec_int; idx <= max_spec_int; idx++) { + //for (int idx : {-52240, -52239, -52238, -3, -2, -1, 0, 1, 2, 3, 52238, 52239, 52240}) { + const auto& xv = SpecIntB[idx - min_spec_int]; + MRInt mval(xv); + if (!(idx % 1000)) { + std::cerr << "# " << idx << " : " << mval << std::endl; + } + for (int i = 0; i <= 256; i++) { + check_shift_ops_on(i, SpecInt[idx - min_spec_int], xv, mval); + } + } +} + +void check_remainder_range(BInt& rv, const BInt& dv, int rmode = -1) { + if (rmode > 0) { + rv.negate(); + } else if (!rmode) { + rv *= 2; + } + MRInt d(dv), r(rv); + int ds = d.sgn(), rs = r.sgn(); + //std::cerr << "rmode=" << rmode << " ds=" << ds << " rs=" << rs << " d=" << d << " r=" << r << std::endl; + if (!rs) { + return; + } + if (rmode) { + // must have 0 < r < d or 0 > r > d + //if (rs != ds) std::cerr << "iter=" << cur_iteration << " : rmode=" << rmode << " ds=" << ds << " rs=" << rs << " d=" << d << " r=" << r << std::endl; + CHECK(rs == ds); + CHECK(ds * r.cmp(d) < 0); + } else { + // must have -d <= r < d or -d >= r > d + if (rs == -ds) { + r.negate(); + CHECK(ds * r.cmp(d) <= 0); + } else { + CHECK(ds * r.cmp(d) < 0); + } + } +} + +void check_divmod(td::RefInt256 x, const BInt& xv, long long xl, td::RefInt256 y, const BInt& yv, long long yl, + int rmode = -2) { + if (rmode < -1) { + //IFDEBUG std::cerr << " divide " << x << " / " << y << std::endl; + for (rmode = -1; rmode <= 1; rmode++) { + check_divmod(x, xv, xl, y, yv, yl, rmode); + } + return; + } + auto dm = td::divmod(x, y, rmode); + auto q = std::move(dm.first), r = std::move(dm.second); + if (!yl) { + // division by zero + CHECK(q.not_null() && !q->is_valid() && r.not_null() && !r->is_valid()); + return; + } + CHECK(q.not_null() && q->is_valid() && r.not_null() && r->is_valid()); + CHECK_EQ(x, y * q + r); + BInt qv = extract_value(*q), rv = extract_value(*r); + CHECK(xv == yv * qv + rv); + //IFDEBUG std::cerr << " quot=" << q << " rem=" << r << std::endl; + check_remainder_range(rv, yv, rmode); + if (yl != ll_min && rmode == -1) { + // check divmod_short() + auto qq = x; + auto rem = qq.write().divmod_short(yl); + qq.write().normalize(); + CHECK(qq->is_valid()); + CHECK_EQ(qq, q); + CHECK(r == rem); + if (xl != ll_min) { + auto dm = std::lldiv(xl, yl); + if (dm.rem && (dm.rem ^ yl) < 0) { + dm.rem += yl; + dm.quot--; + } + CHECK(q == dm.quot); + CHECK(r == dm.rem); + } + } +} + +void check_binary_ops_on(td::RefInt256 x, const BInt& xv, td::RefInt256 y, const BInt& yv) { + bool x_small = x->fits_bits(62), y_small = y->fits_bits(62); // not 63 + long long xl = x_small ? x->to_long() : ll_min, yl = y_small ? y->to_long() : ll_min; + if (x_small) { + CHECK(x == xl); + } + if (y_small) { + CHECK(y == yl); + } + // ADD, ADDR + auto z = x + y, w = y + x; + CHECK_EQ(z, w); + check_res(z, xv + yv); + // ADDCONST + if (y_small) { + CHECK_EQ(z, x + yl); + } + if (x_small) { + CHECK_EQ(z, y + xl); + } + if (x_small && y_small) { + CHECK_EQ(z, xl + yl); + } + // SUB + z = x - y; + check_res(z, xv - yv); + // SUBCONST + if (y_small) { + CHECK_EQ(z, x - yl); + if (x_small) { + CHECK_EQ(z, xl - yl); + } + } + // SUBR + z = y - x; + check_res(z, yv - xv); + if (x_small) { + CHECK_EQ(z, y - xl); + if (y_small) { + CHECK_EQ(z, yl - xl); + } + } + // CMP + MRInt xmr(xv), ymr(yv); + int cmpv = xmr.cmp(ymr); + CHECK(td::cmp(x, y) == cmpv); + CHECK(td::cmp(y, x) == -cmpv); + if (y_small) { + CHECK(td::cmp(x, yl) == cmpv); + } + if (x_small) { + CHECK(td::cmp(y, xl) == -cmpv); + } + if (x_small && y_small) { + CHECK(cmpv == (xl < yl ? -1 : (xl > yl ? 1 : 0))); + } + // MUL + z = x * y; + BInt zv = xv * yv; + check_res(z, zv); + CHECK_EQ(z, y * x); + // MULCONST + if (y_small) { + CHECK_EQ_NAN(z, x * yl); + } + if (x_small) { + CHECK_EQ_NAN(z, y * xl); + } + if (x_small && y_small && (!yl || std::abs(xl) <= ll_max / std::abs(yl))) { + CHECK_EQ(z, xl * yl); + } + // DIVMOD + if (z->fits_bits(257)) { + int adj = 2 * rand_int(-2, 2) - (int)z->is_odd(); + z += adj; + z >>= 1; + zv += adj; + zv >>= 1; + // z is approximately x * y / 2; divide by y + check_divmod(z, zv, z->fits_bits(62) ? z->to_long() : ll_min, y, yv, yl); + } + check_divmod(x, xv, xl, y, yv, yl); +} + +void finish_check_muldivmod(td::RefInt256 x, const BInt& xv, td::RefInt256 y, const BInt& yv, td::RefInt256 z, + const BInt& zv, td::RefInt256 q, td::RefInt256 r, int rmode) { + static constexpr double eps = 1e-14; + CHECK(q.not_null() && r.not_null()); + //std::cerr << " muldivmod: " << xv << " * " << yv << " / " << zv << " (round " << rmode << ") = " << q << " " << r << std::endl; + if (!zv) { + // division by zero + CHECK(!q->is_valid() && !r->is_valid()); + return; + } + CHECK(r->is_valid()); // remainder always exists if y != 0 + BInt xyv = xv * yv, rv = extract_value(*r); + MRInt xy_mr(xyv), z_mr(zv); + double q0 = (double)xy_mr / (double)z_mr; + if (std::abs(q0) < 1.01 * dbl_pow256) { + // result more or less in range + CHECK(q->is_valid()); + } else if (!q->is_valid()) { + // result out of range, NaN is an acceptable answer + // check that x * y - r is divisible by z + xyv -= rv; + xyv /= zv; + xy_mr = xyv; + double q1 = (double)xy_mr; + CHECK(std::abs(q1 - q0) < eps * std::abs(q0)); + } else { + BInt qv = extract_value(*q); + // must have x * y = z * q + r + CHECK(xv * yv == zv * qv + rv); + } + // check that r is in correct range [0, z) or [0, -z) or [-z/2, z/2) + check_remainder_range(rv, zv, rmode); +} + +void check_muldivmod_on(td::RefInt256 x, const BInt& xv, td::RefInt256 y, const BInt& yv, td::RefInt256 z, + const BInt& zv, int rmode = 2) { + if (rmode < -1) { + for (rmode = -1; rmode <= 1; rmode++) { + check_muldivmod_on(x, xv, y, yv, z, zv, rmode); + } + return; + } else if (rmode > 1) { + rmode = rand_int(-1, 1); + } + // MULDIVMOD + auto qr = td::muldivmod(x, y, z, rmode); + finish_check_muldivmod(std::move(x), xv, std::move(y), yv, std::move(z), zv, std::move(qr.first), + std::move(qr.second), rmode); +} + +void check_mul_rshift_on(td::RefInt256 x, const BInt& xv, td::RefInt256 y, const BInt& yv, int shift, int rmode = 2) { + if (rmode < -1) { + for (rmode = -1; rmode <= 1; rmode++) { + check_mul_rshift_on(x, xv, y, yv, shift, rmode); + } + return; + } else if (rmode > 1) { + rmode = rand_int(-1, 1); + } + // MULRSHIFTMOD + typename td::BigInt256::DoubleInt tmp{0}; + tmp.add_mul(*x, *y); + typename td::BigInt256::DoubleInt tmp2{tmp}; + tmp2.rshift(shift, rmode).normalize(); + tmp.normalize().mod_pow2(shift, rmode).normalize(); + finish_check_muldivmod(std::move(x), xv, std::move(y), yv, {}, BInt::pow2(shift), td::make_refint(tmp2), + td::make_refint(tmp), rmode); +} + +void check_lshift_div_on(td::RefInt256 x, const BInt& xv, td::RefInt256 y, const BInt& yv, int shift, int rmode = 2) { + if (rmode < -1) { + for (rmode = -1; rmode <= 1; rmode++) { + check_lshift_div_on(x, xv, y, yv, shift, rmode); + } + return; + } else if (rmode > 1) { + rmode = rand_int(-1, 1); + } + // LSHIFTDIV + typename td::BigInt256::DoubleInt tmp{*x}, quot; + tmp <<= shift; + tmp.mod_div(*y, quot, rmode); + quot.normalize(); + finish_check_muldivmod(std::move(x), xv, {}, BInt::pow2(shift), std::move(y), yv, td::make_refint(quot), + td::make_refint(tmp), rmode); +} + +void check_random_ops() { + constexpr long long chk_it = 100000; + std::cerr << "check random ops (" << iterations << " iterations)" << std::endl; + BInt xv, yv, zv; + unsigned char xbin[64], ybin[64], zbin[64]; + for (cur_iteration = 0; cur_iteration < iterations; cur_iteration++) { + auto x = make_random_int0(xv, xbin); + if (!(cur_iteration % 10000)) { + std::cerr << "#" << cur_iteration << ": check on " << xv << " = " << ShowBin(xbin) << " = " << x->as_any_int() + << std::endl; + } + check_one_int_repr(x, cur_iteration < chk_it ? -1 : 0, -1, &xv, xbin); + MRInt xmr(xv); + if (!x->fits_bits(257)) { + continue; + } + check_unary_ops_on(x, xv); + for (int j = 0; j < 10; j++) { + int shift = rand_int(0, 256); + //std::cerr << "check shift by " << shift << std::endl; + check_shift_ops_on(shift, x, xv, xmr); + auto y = make_random_int(yv, ybin); + //std::cerr << " y = " << y << " = " << yv << " = " << ShowBin(ybin) << " = " << y->as_any_int() << std::endl; + check_one_int_repr(y, 0, 1, &yv, ybin); + check_binary_ops_on(x, xv, y, yv); + //std::cerr << " *>> " << shift << std::endl; + check_mul_rshift_on(x, xv, y, yv, shift); + //std::cerr << " <as_any_int() << std::endl; + check_muldivmod_on(x, xv, y, yv, z, zv); + } + } +} + +void check_special() { + std::cerr << "run special tests" << std::endl; + check_divmod((td::make_refint(-1) << 207) - 1, BInt::negpow2(207) - 1, ll_min, (td::make_refint(1) << 207) - 1, + BInt::pow2(207) - 1, ll_min); +} + +int main(int argc, char* const argv[]) { + bool do_check_shift_ops = false; + int i; + while ((i = getopt(argc, argv, "hSs:i:")) != -1) { + switch (i) { + case 'S': + do_check_shift_ops = true; + break; + case 's': + Random.seed(atoll(optarg)); + break; + case 'i': + iterations = atoll(optarg); + break; + default: + std::cerr << "unknown option: " << (char)i << std::endl; + // fall through + case 'h': + std::cerr << "usage:\t" << argv[0] << " [-S] [-i] [-s]" << std::endl; + return 2; + } + } + modint::init(); + init_aux(); + init_check_special_ints(); + check_pow2_ops(); + check_unary_ops(); + if (do_check_shift_ops) { + check_shift_ops(); + } + check_special(); + check_random_ops(); + return 0; +} diff --git a/crypto/test/test-cells.cpp b/crypto/test/test-cells.cpp index bda9caa6..327f73c6 100644 --- a/crypto/test/test-cells.cpp +++ b/crypto/test/test-cells.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include @@ -580,7 +580,7 @@ bool check_exp(std::ostream& stream, const td::NegExpBinTable& tab, double x) { } double y = yy.to_double() * exp2(-252); double y0 = exp(x); - bool ok = (abs(y - y0) < 1e-15); + bool ok = (fabs(y - y0) < 1e-15); if (!ok) { stream << "exp(" << x << ") = exp(" << xx << " * 2^(-52)) = " << yy << " / 2^252 = " << y << " (correct value is " << y0 << ") " << (ok ? "match" : "incorrect") << std::endl; diff --git a/crypto/test/test-db.cpp b/crypto/test/test-db.cpp index 1b7e1900..35727ee3 100644 --- a/crypto/test/test-db.cpp +++ b/crypto/test/test-db.cpp @@ -14,15 +14,15 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/boc.h" #include "vm/cellslice.h" #include "vm/cells.h" #include "common/AtomicRef.h" +#include "vm/cells/CellString.h" #include "vm/cells/MerkleProof.h" #include "vm/cells/MerkleUpdate.h" -#include "vm/db/BlobView.h" #include "vm/db/CellStorage.h" #include "vm/db/CellHashTable.h" #include "vm/db/TonDb.h" @@ -33,6 +33,7 @@ #include "td/utils/crypto.h" #include "td/utils/Random.h" #include "td/utils/Slice.h" +#include "td/utils/Span.h" #include "td/utils/Status.h" #include "td/utils/Timer.h" #include "td/utils/filesystem.h" @@ -44,14 +45,20 @@ #include "td/utils/tl_parsers.h" #include "td/utils/tl_helpers.h" +#include "td/db/utils/BlobView.h" #include "td/db/RocksDb.h" #include "td/db/MemoryKeyValue.h" +#include "td/db/utils/CyclicBuffer.h" + +#include "td/fec/fec.h" #include #include #include +#include "openssl/digest.hpp" + namespace vm { std::vector do_get_serialization_modes() { @@ -120,12 +127,12 @@ class BenchSha256Low : public td::Benchmark { void run(int n) override { int res = 0; - SHA256_CTX ctx; + td::Sha256State ctx; for (int i = 0; i < n; i++) { - SHA256_Init(&ctx); - SHA256_Update(&ctx, "abcd", 4); + ctx.init(); + ctx.feed("abcd"); unsigned char buf[32]; - SHA256_Final(buf, &ctx); + ctx.extract(td::MutableSlice{buf, 32}); res += buf[0]; } td::do_not_optimize_away(res); @@ -431,17 +438,10 @@ class RandomBagOfCells { }; }; -template -void random_shuffle(td::MutableSpan v, td::Random::Xorshift128plus &rnd) { - for (std::size_t i = 1; i < v.size(); i++) { - auto pos = static_cast(rnd() % (i + 1)); - std::swap(v[i], v[pos]); - } -} Ref gen_random_cell(int size, td::Random::Xorshift128plus &rnd, bool with_prunned_branches = true, std::vector> cells = {}) { if (!cells.empty()) { - random_shuffle(td::MutableSpan>(cells), rnd); + td::random_shuffle(as_mutable_span(cells), rnd); cells.resize(cells.size() % rnd()); } return RandomBagOfCells(size, rnd, with_prunned_branches, std::move(cells)).get_root(); @@ -449,7 +449,7 @@ Ref gen_random_cell(int size, td::Random::Xorshift128plus &rnd, bool with_ std::vector> gen_random_cells(int roots, int size, td::Random::Xorshift128plus &rnd, bool with_prunned_branches = true, std::vector> cells = {}) { if (!cells.empty()) { - random_shuffle(td::MutableSpan>(cells), rnd); + td::random_shuffle(as_mutable_span(cells), rnd); cells.resize(cells.size() % rnd()); } return RandomBagOfCells(size, rnd, with_prunned_branches, std::move(cells)).get_random_roots(roots, rnd); @@ -694,6 +694,32 @@ TEST(TonDb, BenchCellBuilder3) { td::bench(BenchCellBuilder3()); } +TEST(TonDb, BocFuzz) { + vm::std_boc_deserialize(td::base64_decode("te6ccgEBAQEAAgAoAAA=").move_as_ok()).ensure_error(); + vm::std_boc_deserialize(td::base64_decode("te6ccgQBQQdQAAAAAAEAte6ccgQBB1BBAAAAAAEAAAAAAP/" + "wAACJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJicmJiYmJiYmJiYmJiQ0NDQ0NDQ0NDQ0NDQ0ND" + "Q0NiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiQAA//AAAO4=") + .move_as_ok()); + vm::std_boc_deserialize(td::base64_decode("SEkh/w==").move_as_ok()).ensure_error(); + vm::std_boc_deserialize( + td::base64_decode( + "te6ccqwBMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMAKCEAAAAgAQ==") + .move_as_ok()) + .ensure_error(); +} +void test_parse_prefix(td::Slice boc) { + for (size_t i = 0; i <= boc.size(); i++) { + auto prefix = boc.substr(0, i); + vm::BagOfCells::Info info; + auto res = info.parse_serialized_header(prefix); + if (res > 0) { + break; + } + CHECK(res != 0); + CHECK(-res > (int)i); + } +} + TEST(TonDb, Boc) { td::Random::Xorshift128plus rnd{123}; for (int t = 0; t < 1000; t++) { @@ -704,6 +730,8 @@ TEST(TonDb, Boc) { auto serialized = serialize_boc(std::move(cell), mode); CHECK(serialized.size() != 0); + test_parse_prefix(serialized); + auto loaded_cell = deserialize_boc(serialized); ASSERT_EQ(cell_hash, loaded_cell->get_hash()); @@ -758,7 +786,7 @@ TEST(TonDb, DynamicBoc) { old_root_serialization = serialize_boc(cell); // Check that DynamicBagOfCells properly loads cells - cell = vm::StaticBagOfCellsDbLazy::create(vm::BufferSliceBlobView::create(td::BufferSlice(old_root_serialization))) + cell = vm::StaticBagOfCellsDbLazy::create(td::BufferSliceBlobView::create(td::BufferSlice(old_root_serialization))) .move_as_ok() ->get_root_cell(0) .move_as_ok(); @@ -1512,6 +1540,7 @@ template class BenchBocDeserializer : public td::Benchmark { public: BenchBocDeserializer(std::string name, BenchBocDeserializerConfig config) : name_(std::move(name)), config_(config) { + td::PerfWarningTimer perf("A", 1); fast_array_ = vm::FastCompactArray(array_size); td::Random::Xorshift128plus rnd{123}; for (td::uint32 i = 0; i < array_size; i++) { @@ -1568,11 +1597,11 @@ class BenchBocDeserializer : public td::Benchmark { auto blob = [&] { switch (config_.blob_type) { case BenchBocDeserializerConfig::File: - return vm::FileBlobView::create("serialization").move_as_ok(); + return td::FileBlobView::create("serialization").move_as_ok(); case BenchBocDeserializerConfig::Memory: - return vm::BufferSliceBlobView::create(serialization_.clone()); + return td::BufferSliceBlobView::create(serialization_.clone()); case BenchBocDeserializerConfig::FileMemoryMap: - return vm::FileMemoryMappingBlobView::create("serialization").move_as_ok(); + return td::FileMemoryMappingBlobView::create("serialization").move_as_ok(); default: UNREACHABLE(); } @@ -2052,222 +2081,6 @@ TEST(Ref, AtomicRef) { LOG(ERROR) << String::total_strings.sum(); } -class FileMerkleTree { - public: - FileMerkleTree(size_t chunks_count, td::Ref root = {}) { - log_n_ = 0; - while ((size_t(1) << log_n_) < chunks_count) { - log_n_++; - } - n_ = size_t(1) << log_n_; - mark_.resize(n_ * 2); - proof_.resize(n_ * 2); - - CHECK(n_ == chunks_count); // TODO: support other chunks_count - //auto x = vm::CellBuilder().finalize(); - root_ = std::move(root); - } - - struct Chunk { - td::size_t index{0}; - td::Slice hash; - }; - - void remove_chunk(td::size_t index) { - CHECK(index < n_); - index += n_; - while (proof_[index].not_null()) { - proof_[index] = {}; - index /= 2; - } - } - - bool has_chunk(td::size_t index) const { - CHECK(index < n_); - index += n_; - return proof_[index].not_null(); - } - - void add_chunk(td::size_t index, td::Slice hash) { - CHECK(hash.size() == 32); - CHECK(index < n_); - index += n_; - auto cell = vm::CellBuilder().store_bytes(hash).finalize(); - CHECK(proof_[index].is_null()); - proof_[index] = std::move(cell); - for (index /= 2; index != 0; index /= 2) { - CHECK(proof_[index].is_null()); - auto &left = proof_[index * 2]; - auto &right = proof_[index * 2 + 1]; - if (left.not_null() && right.not_null()) { - proof_[index] = vm::CellBuilder().store_ref(left).store_ref(right).finalize(); - } else { - mark_[index] = mark_id_; - } - } - } - - td::Status validate_proof(td::Ref new_root) { - // TODO: check structure - return td::Status::OK(); - } - - td::Status add_proof(td::Ref new_root) { - TRY_STATUS(validate_proof(new_root)); - auto combined = vm::MerkleProof::combine_fast_raw(root_, new_root); - if (combined.is_null()) { - return td::Status::Error("Can't combine proofs"); - } - root_ = std::move(combined); - return td::Status::OK(); - } - - td::Status try_add_chunks(td::Span chunks) { - for (auto chunk : chunks) { - if (has_chunk(chunk.index)) { - return td::Status::Error("Already has chunk"); - } - } - mark_id_++; - for (auto chunk : chunks) { - add_chunk(chunk.index, chunk.hash); - } - auto r_new_root = merge(root_, 1); - if (r_new_root.is_error()) { - for (auto chunk : chunks) { - remove_chunk(chunk.index); - } - return r_new_root.move_as_error(); - } - root_ = r_new_root.move_as_ok(); - return td::Status::OK(); - } - - td::Result> merge(td::Ref root, size_t index) { - const auto &down = proof_[index]; - if (down.not_null()) { - if (down->get_hash() != root->get_hash(0)) { - return td::Status::Error("Hash mismatch"); - } - return down; - } - - if (mark_[index] != mark_id_) { - return root; - } - - vm::CellSlice cs(vm::NoVm(), root); - if (cs.is_special()) { - return td::Status::Error("Proof is not enough to validate chunks"); - } - - CHECK(cs.size_refs() == 2); - vm::CellBuilder cb; - cb.store_bits(cs.fetch_bits(cs.size())); - TRY_RESULT(left, merge(cs.fetch_ref(), index * 2)); - TRY_RESULT(right, merge(cs.fetch_ref(), index * 2 + 1)); - cb.store_ref(std::move(left)).store_ref(std::move(right)); - return cb.finalize(); - } - - void init_proof() { - CHECK(proof_[1].not_null()); - root_ = proof_[1]; - } - - td::Result> gen_proof(size_t l, size_t r) { - auto usage_tree = std::make_shared(); - auto usage_cell = vm::UsageCell::create(root_, usage_tree->root_ptr()); - TRY_STATUS(do_gen_proof(std::move(usage_cell), 0, n_ - 1, l, r)); - auto res = vm::MerkleProof::generate_raw(root_, usage_tree.get()); - CHECK(res.not_null()); - return res; - } - - private: - td::size_t n_; // n = 2^log_n - td::size_t log_n_; - td::size_t mark_id_{0}; - std::vector mark_; // n_ * 2 - std::vector> proof_; // n_ * 2 - td::Ref root_; - - td::Status do_gen_proof(td::Ref node, size_t il, size_t ir, size_t l, size_t r) { - if (ir < l || il > r) { - return td::Status::OK(); - } - if (l <= il && ir <= r) { - return td::Status::OK(); - } - vm::CellSlice cs(vm::NoVm(), std::move(node)); - if (cs.is_special()) { - return td::Status::Error("Can't generate a proof"); - } - CHECK(cs.size_refs() == 2); - auto ic = (il + ir) / 2; - TRY_STATUS(do_gen_proof(cs.fetch_ref(), il, ic, l, r)); - TRY_STATUS(do_gen_proof(cs.fetch_ref(), ic + 1, ir, l, r)); - return td::Status::OK(); - } -}; - -TEST(FileMerkleTree, Manual) { - // create big random file - size_t chunk_size = 768; - // for simplicity numer of chunks in a file is a power of two - size_t chunks_count = 1 << 16; - size_t file_size = chunk_size * chunks_count; - td::Timer timer; - LOG(INFO) << "Generate random string"; - const auto file = td::rand_string('a', 'z', td::narrow_cast(file_size)); - LOG(INFO) << timer; - - timer = {}; - LOG(INFO) << "Calculate all hashes"; - std::vector hashes(chunks_count); - for (size_t i = 0; i < chunks_count; i++) { - td::sha256(td::Slice(file).substr(i * chunk_size, chunk_size), hashes[i].as_slice()); - } - LOG(INFO) << timer; - - timer = {}; - LOG(INFO) << "Init merkle tree"; - FileMerkleTree tree(chunks_count); - for (size_t i = 0; i < chunks_count; i++) { - tree.add_chunk(i, hashes[i].as_slice()); - } - tree.init_proof(); - LOG(INFO) << timer; - - auto root_proof = tree.gen_proof(0, chunks_count - 1).move_as_ok(); - - // first download each chunk one by one - - for (size_t stride : {1 << 6, 1}) { - timer = {}; - LOG(INFO) << "Gen all proofs, stride = " << stride; - for (size_t i = 0; i < chunks_count; i += stride) { - tree.gen_proof(i, i + stride - 1).move_as_ok(); - } - LOG(INFO) << timer; - timer = {}; - LOG(INFO) << "Proof size: " << vm::std_boc_serialize(tree.gen_proof(0, stride - 1).move_as_ok()).ok().size(); - LOG(INFO) << "Download file, stride = " << stride; - { - FileMerkleTree new_tree(chunks_count, root_proof); - for (size_t i = 0; i < chunks_count; i += stride) { - new_tree.add_proof(tree.gen_proof(i, i + stride - 1).move_as_ok()).ensure(); - std::vector chunks; - for (size_t j = 0; j < stride; j++) { - chunks.push_back({i + j, hashes[i + j].as_slice()}); - } - new_tree.try_add_chunks(chunks).ensure(); - } - } - LOG(INFO) << timer; - } -} - //TEST(Tmp, Boc) { //LOG(ERROR) << "A"; //auto data = td::read_file("boc"); diff --git a/crypto/test/test-ed25519-crypto.cpp b/crypto/test/test-ed25519-crypto.cpp index 85ae5149..3e3dab89 100644 --- a/crypto/test/test-ed25519-crypto.cpp +++ b/crypto/test/test-ed25519-crypto.cpp @@ -23,7 +23,7 @@ exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include diff --git a/crypto/test/test-smartcont.cpp b/crypto/test/test-smartcont.cpp index f129e0b3..7f512cea 100644 --- a/crypto/test/test-smartcont.cpp +++ b/crypto/test/test-smartcont.cpp @@ -14,28 +14,31 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/dict.h" #include "common/bigint.hpp" #include "Ed25519.h" +#include "block/block-auto.h" #include "block/block.h" +#include "block/block-parse.h" #include "fift/Fift.h" #include "fift/words.h" #include "fift/utils.h" #include "smc-envelope/GenericAccount.h" +#include "smc-envelope/ManualDns.h" #include "smc-envelope/MultisigWallet.h" #include "smc-envelope/SmartContract.h" #include "smc-envelope/SmartContractCode.h" -#include "smc-envelope/TestGiver.h" -#include "smc-envelope/TestWallet.h" -#include "smc-envelope/Wallet.h" #include "smc-envelope/WalletV3.h" +#include "smc-envelope/WalletV4.h" #include "smc-envelope/HighloadWallet.h" +#include "smc-envelope/HighloadWalletV2.h" +#include "smc-envelope/PaymentChannel.h" #include "td/utils/base64.h" #include "td/utils/crypto.h" @@ -47,6 +50,7 @@ #include "td/utils/PathView.h" #include "td/utils/filesystem.h" #include "td/utils/port/path.h" +#include "td/utils/Variant.h" #include #include @@ -60,67 +64,11 @@ std::string load_source(std::string name) { return td::read_file_str(current_dir() + "../../crypto/" + name).move_as_ok(); } -td::Ref get_test_wallet_source() { - std::string code = R"ABCD( -SETCP0 DUP IFNOTRET // return if recv_internal -DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method - DROP c4 PUSHCTR CTOS 32 PLDU // cnt -}> -INC 32 THROWIF // fail unless recv_external -512 INT LDSLICEX DUP 32 PLDU // sign cs cnt -c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS // sign cs cnt cnt' pubk -s1 s2 XCPU // sign cs cnt pubk cnt' cnt -EQUAL 33 THROWIFNOT // ( seqno mismatch? ) -s2 PUSH HASHSU // sign cs cnt pubk hash -s0 s4 s4 XC2PU // pubk cs cnt hash sign pubk -CHKSIGNU // pubk cs cnt ? -34 THROWIFNOT // signature mismatch -ACCEPT -SWAP 32 LDU NIP -DUP SREFS IF:<{ - // 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance - 8 LDU LDREF // pubk cnt mode msg cs - s0 s2 XCHG SENDRAWMSG // pubk cnt cs ; ( message sent ) -}> -ENDS -INC NEWC 32 STU 256 STU ENDC c4 POPCTR -)ABCD"; - return fift::compile_asm(code).move_as_ok(); -} - -td::Ref get_wallet_source() { - std::string code = R"ABCD( -SETCP0 DUP IFNOTRET // return if recv_internal - DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method - DROP c4 PUSHCTR CTOS 32 PLDU // cnt - }> - INC 32 THROWIF // fail unless recv_external - 9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU // signature in_msg msg_seqno valid_until cs - SWAP NOW LEQ 35 THROWIF // signature in_msg msg_seqno cs - c4 PUSH CTOS 32 LDU 256 LDU ENDS // signature in_msg msg_seqno cs stored_seqno public_key - s3 s1 XCPU // signature in_msg public_key cs stored_seqno msg_seqno stored_seqno - EQUAL 33 THROWIFNOT // signature in_msg public_key cs stored_seqno - s0 s3 XCHG HASHSU // signature stored_seqno public_key cs hash - s0 s4 s2 XC2PU CHKSIGNU 34 THROWIFNOT // cs stored_seqno public_key - ACCEPT - s0 s2 XCHG // public_key stored_seqno cs - WHILE:<{ - DUP SREFS // public_key stored_seqno cs _40 - }>DO<{ // public_key stored_seqno cs - // 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance - 8 LDU LDREF s0 s2 XCHG // public_key stored_seqno cs _45 mode - SENDRAWMSG // public_key stored_seqno cs - }> - ENDS INC // public_key seqno' - NEWC 32 STU 256 STU ENDC c4 POP -)ABCD"; - return fift::compile_asm(code).move_as_ok(); -} td::Ref get_wallet_v3_source() { std::string code = R"ABCD( SETCP0 DUP IFNOTRET // return if recv_internal - DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method - DROP c4 PUSHCTR CTOS 32 PLDU // cnt + DUP 85143 INT EQUAL OVER 78748 INT EQUAL OR IFJMP:<{ // "seqno" and "get_public_key" get-methods + 1 INT AND c4 PUSHCTR CTOS 32 LDU 32 LDU NIP 256 PLDU CONDSEL // cnt or pubk }> INC 32 THROWIF // fail unless recv_external 9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU 32 LDU // signature in_msg subwallet_id valid_until msg_seqno cs @@ -144,125 +92,37 @@ SETCP0 DUP IFNOTRET // return if recv_internal return fift::compile_asm(code).move_as_ok(); } -TEST(Tonlib, TestWallet) { - LOG(ERROR) << td::base64_encode(std_boc_serialize(get_test_wallet_source()).move_as_ok()); - CHECK(get_test_wallet_source()->get_hash() == ton::TestWallet::get_init_code()->get_hash()); - auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet.fif"), {"aba", "0"}).move_as_ok(); - - auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data; - auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data; - auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data; - - td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}}; - auto pub_key = priv_key.get_public_key().move_as_ok(); - auto init_state = ton::TestWallet::get_init_state(pub_key); - auto init_message = ton::TestWallet::get_init_message(priv_key); - auto address = ton::GenericAccount::get_address(0, init_state); - - CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); - - td::Ref res = ton::GenericAccount::create_ext_message(address, init_state, init_message); - - LOG(ERROR) << "-------"; - vm::load_cell_slice(res).print_rec(std::cerr); - LOG(ERROR) << "-------"; - vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr); - CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash()); - - fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet.fif")).ensure(); - auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok(); - fift_output = fift::mem_run_fift(std::move(fift_output.source_lookup), - {"aba", "new-wallet", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123", - "321", "-C", "TEST"}) - .move_as_ok(); - auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; - auto gift_message = ton::GenericAccount::create_ext_message( - address, {}, ton::TestWallet::make_a_gift_message(priv_key, 123, 321000000000ll, "TEST", dest)); - LOG(ERROR) << "-------"; - vm::load_cell_slice(gift_message).print_rec(std::cerr); - LOG(ERROR) << "-------"; - vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr); - CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash()); -} - -td::Ref get_wallet_source_fc() { - return fift::compile_asm(load_source("smartcont/wallet-code.fif"), "", false).move_as_ok(); -} - -TEST(Tonlib, Wallet) { - LOG(ERROR) << td::base64_encode(std_boc_serialize(get_wallet_source()).move_as_ok()); - CHECK(get_wallet_source()->get_hash() == ton::Wallet::get_init_code()->get_hash()); - - auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet-v2.fif"), {"aba", "0"}).move_as_ok(); - - auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data; - auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data; - auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data; - - td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}}; - auto pub_key = priv_key.get_public_key().move_as_ok(); - auto init_state = ton::Wallet::get_init_state(pub_key); - auto init_message = ton::Wallet::get_init_message(priv_key); - auto address = ton::GenericAccount::get_address(0, init_state); - - CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); - - td::Ref res = ton::GenericAccount::create_ext_message(address, init_state, init_message); - - LOG(ERROR) << "-------"; - vm::load_cell_slice(res).print_rec(std::cerr); - LOG(ERROR) << "-------"; - vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr); - CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash()); - - fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet-v2.fif")).ensure(); - class ZeroOsTime : public fift::OsTime { - public: - td::uint32 now() override { - return 0; - } - }; - fift_output.source_lookup.set_os_time(std::make_unique()); - auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok(); - fift_output = - fift::mem_run_fift(std::move(fift_output.source_lookup), - {"aba", "new-wallet", "-C", "TESTv2", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123", "321"}) - .move_as_ok(); - auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; - auto gift_message = ton::GenericAccount::create_ext_message( - address, {}, ton::Wallet::make_a_gift_message(priv_key, 123, 60, 321000000000ll, "TESTv2", dest)); - LOG(ERROR) << "-------"; - vm::load_cell_slice(gift_message).print_rec(std::cerr); - LOG(ERROR) << "-------"; - vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr); - CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash()); -} - TEST(Tonlib, WalletV3) { LOG(ERROR) << td::base64_encode(std_boc_serialize(get_wallet_v3_source()).move_as_ok()); - CHECK(get_wallet_v3_source()->get_hash() == ton::WalletV3::get_init_code()->get_hash()); + CHECK(get_wallet_v3_source()->get_hash() == ton::WalletV3::get_init_code(2)->get_hash()); auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet-v3.fif"), {"aba", "0", "239"}).move_as_ok(); - auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data; auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data; auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data; td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}}; auto pub_key = priv_key.get_public_key().move_as_ok(); - auto init_state = ton::WalletV3::get_init_state(pub_key, 239); - auto init_message = ton::WalletV3::get_init_message(priv_key, 239); - auto address = ton::GenericAccount::get_address(0, init_state); + ton::WalletV3::InitData init_data; + init_data.public_key = pub_key.as_octet_string(); + init_data.wallet_id = 239; + auto wallet = ton::WalletV3::create(init_data, 2); + ASSERT_EQ(239u, wallet->get_wallet_id().ok()); + ASSERT_EQ(0u, wallet->get_seqno().ok()); + auto address = wallet->get_address(); CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); - td::Ref res = ton::GenericAccount::create_ext_message(address, init_state, init_message); - + auto init_message = wallet->get_init_message(priv_key).move_as_ok(); + td::Ref ext_init_message = ton::GenericAccount::create_ext_message( + address, ton::GenericAccount::get_init_state(wallet->get_state()), init_message); LOG(ERROR) << "-------"; - vm::load_cell_slice(res).print_rec(std::cerr); + vm::load_cell_slice(ext_init_message).print_rec(std::cerr); LOG(ERROR) << "-------"; vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr); - CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash()); + CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == ext_init_message->get_hash()); + + CHECK(wallet.write().send_external_message(init_message).success); fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet-v3.fif")).ensure(); class ZeroOsTime : public fift::OsTime { @@ -273,13 +133,25 @@ TEST(Tonlib, WalletV3) { }; fift_output.source_lookup.set_os_time(std::make_unique()); auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok(); - fift_output = - fift::mem_run_fift(std::move(fift_output.source_lookup), - {"aba", "new-wallet", "-C", "TESTv3", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "239", "123", "321"}) - .move_as_ok(); + fift_output = fift::mem_run_fift(std::move(fift_output.source_lookup), + {"aba", "new-wallet", "-C", "TESTv3", + "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "239", "1", "321"}) + .move_as_ok(); auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; + + ton::WalletV3::Gift gift; + gift.destination = dest; + gift.message = "TESTv3"; + gift.gramms = 321000000000ll; + + ASSERT_EQ(239u, wallet->get_wallet_id().ok()); + ASSERT_EQ(1u, wallet->get_seqno().ok()); + CHECK(priv_key.get_public_key().ok().as_octet_string() == wallet->get_public_key().ok().as_octet_string()); + CHECK(priv_key.get_public_key().ok().as_octet_string() == + ton::GenericAccount::get_public_key(*wallet).ok().as_octet_string()); + auto gift_message = ton::GenericAccount::create_ext_message( - address, {}, ton::WalletV3::make_a_gift_message(priv_key, 239, 123, 60, 321000000000ll, "TESTv3", dest)); + address, {}, wallet->make_a_gift_message(priv_key, 60, {gift}).move_as_ok()); LOG(ERROR) << "-------"; vm::load_cell_slice(gift_message).print_rec(std::cerr); LOG(ERROR) << "-------"; @@ -300,13 +172,21 @@ TEST(Tonlib, HighloadWallet) { td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}}; auto pub_key = priv_key.get_public_key().move_as_ok(); - auto init_state = ton::HighloadWallet::get_init_state(pub_key, 239); - auto init_message = ton::HighloadWallet::get_init_message(priv_key, 239); - auto address = ton::GenericAccount::get_address(0, init_state); + ton::HighloadWallet::InitData init_data(pub_key.as_octet_string(), 239); + + auto wallet = ton::HighloadWallet::create(init_data, -1); + auto address = wallet->get_address(); + CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); + ASSERT_EQ(239u, wallet->get_wallet_id().ok()); + ASSERT_EQ(0u, wallet->get_seqno().ok()); + CHECK(pub_key.as_octet_string() == wallet->get_public_key().ok().as_octet_string()); + CHECK(pub_key.as_octet_string() == ton::GenericAccount::get_public_key(*wallet).ok().as_octet_string()); CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); - td::Ref res = ton::GenericAccount::create_ext_message(address, init_state, init_message); + auto init_message = wallet->get_init_message(priv_key).move_as_ok(); + td::Ref res = ton::GenericAccount::create_ext_message( + address, ton::GenericAccount::get_init_state(wallet->get_state()), init_message); LOG(ERROR) << "---smc-envelope----"; vm::load_cell_slice(res).print_rec(std::cerr); @@ -341,12 +221,14 @@ TEST(Tonlib, HighloadWallet) { return 0; } }; + init_data.seqno = 123; + wallet = ton::HighloadWallet::create(init_data, -1); fift_output.source_lookup.set_os_time(std::make_unique()); fift_output = fift::mem_run_fift(std::move(fift_output.source_lookup), {"aba", "new-wallet", "239", "123", "order"}) .move_as_ok(); auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; auto gift_message = ton::GenericAccount::create_ext_message( - address, {}, ton::HighloadWallet::make_a_gift_message(priv_key, 239, 123, 60, gifts)); + address, {}, wallet->make_a_gift_message(priv_key, 60, gifts).move_as_ok()); LOG(ERROR) << "---smc-envelope----"; vm::load_cell_slice(gift_message).print_rec(std::cerr); LOG(ERROR) << "---fift scripts----"; @@ -354,89 +236,301 @@ TEST(Tonlib, HighloadWallet) { CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash()); } -TEST(Tonlib, TestGiver) { - auto address = - block::StdAddress::parse("-1:60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0").move_as_ok(); - LOG(ERROR) << address.bounceable; - auto fift_output = fift::mem_run_fift(load_source("smartcont/testgiver.fif"), - {"aba", address.rserialize(), "0", "6.666", "wallet-query"}) - .move_as_ok(); +TEST(Tonlib, HighloadWalletV2) { + auto source_lookup = fift::create_mem_source_lookup(load_source("smartcont/new-highload-wallet-v2.fif")).move_as_ok(); + source_lookup + .write_file("/auto/highload-wallet-v2-code.fif", load_source("smartcont/auto/highload-wallet-v2-code.fif")) + .ensure(); + class ZeroOsTime : public fift::OsTime { + public: + td::uint32 now() override { + return 0; + } + }; + source_lookup.set_os_time(std::make_unique()); + auto fift_output = fift::mem_run_fift(std::move(source_lookup), {"aba", "0", "239"}).move_as_ok(); + LOG(ERROR) << fift_output.output; + auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data; + auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet239-query.boc").move_as_ok().data; + auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet239.addr").move_as_ok().data; + td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}}; + auto pub_key = priv_key.get_public_key().move_as_ok(); + ton::HighloadWalletV2::InitData init_data(pub_key.as_octet_string(), 239); + + auto wallet = ton::HighloadWalletV2::create(init_data, -1); + auto address = wallet->get_address(); + + ASSERT_EQ(239u, wallet->get_wallet_id().ok()); + wallet->get_seqno().ensure_error(); + CHECK(pub_key.as_octet_string() == wallet->get_public_key().ok().as_octet_string()); + CHECK(pub_key.as_octet_string() == ton::GenericAccount::get_public_key(*wallet).ok().as_octet_string()); + + CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); + + auto init_message = wallet->get_init_message(priv_key, 65535).move_as_ok(); + td::Ref res = ton::GenericAccount::create_ext_message( + address, ton::GenericAccount::get_init_state(wallet->get_state()), init_message); + + LOG(ERROR) << "---smc-envelope----"; + vm::load_cell_slice(res).print_rec(std::cerr); + LOG(ERROR) << "---fift scripts----"; + vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr); + CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash()); + + fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/highload-wallet-v2.fif")).ensure(); + std::string order; + std::vector gifts; + auto add_order = [&](td::Slice dest_str, td::int64 gramms) { + auto g = td::to_string(gramms); + if (g.size() < 10) { + g = std::string(10 - g.size(), '0') + g; + } + order += PSTRING() << "SEND " << dest_str << " " << g.substr(0, g.size() - 9) << "." << g.substr(g.size() - 9) + << "\n"; + + ton::HighloadWalletV2::Gift gift; + gift.destination = block::StdAddress::parse(dest_str).move_as_ok(); + gift.gramms = gramms; + gifts.push_back(gift); + }; + std::string dest_str = "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX"; + add_order(dest_str, 0); + add_order(dest_str, 321000000000ll); + add_order(dest_str, 321ll); + fift_output.source_lookup.write_file("/order", order).ensure(); + fift_output.source_lookup.set_os_time(std::make_unique()); + fift_output = + fift::mem_run_fift(std::move(fift_output.source_lookup), {"aba", "new-wallet", "239", "order"}).move_as_ok(); auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; - - auto res = ton::GenericAccount::create_ext_message( - ton::TestGiver::address(), {}, - ton::TestGiver::make_a_gift_message(0, 1000000000ll * 6666 / 1000, "GIFT", address)); - vm::CellSlice(vm::NoVm(), res).print_rec(std::cerr); - CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == res->get_hash()); + auto gift_message = ton::GenericAccount::create_ext_message( + address, {}, wallet->make_a_gift_message(priv_key, 60, gifts).move_as_ok()); + LOG(ERROR) << "---smc-envelope----"; + vm::load_cell_slice(gift_message).print_rec(std::cerr); + LOG(ERROR) << "---fift scripts----"; + vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr); + CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash()); } -class SimpleWallet : public ton::SmartContract { +TEST(Tonlib, RestrictedWallet) { + //auto source_lookup = fift::create_mem_source_lookup(load_source("smartcont/new-restricted-wallet2.fif")).move_as_ok(); + //source_lookup + //.write_file("/auto/restricted-wallet2-code.fif", load_source("smartcont/auto/restricted-wallet2-code.fif")) + //.ensure(); + //class ZeroOsTime : public fift::OsTime { + //public: + //td::uint32 now() override { + //return 0; + //} + //}; + //source_lookup.set_os_time(std::make_unique()); + //auto priv_key = td::Ed25519::generate_private_key().move_as_ok(); + //auto pub_key = priv_key.get_public_key().move_as_ok(); + //auto pub_key_serialized = block::PublicKey::from_bytes(pub_key.as_octet_string()).move_as_ok().serialize(true); + + //std::vector args = {"path", pub_key_serialized, std::string("100")}; + //auto fift_output = fift::mem_run_fift(std::move(source_lookup), args).move_as_ok(); + + //ton::RestrictedWallet::InitData init_data; + //td::uint64 x = 100 * 1000000000ull; + //init_data.key = &pub_key; + //init_data.start_at = 0; + //init_data.limits = {{-32768, x}, {92, x * 3 / 4}, {183, x * 1 / 2}, {366, x * 1 / 4}, {548, 0}}; + //auto wallet = ton::RestrictedWallet::create(init_data, -1); + + //ASSERT_EQ(0u, wallet->get_seqno().move_as_ok()); + //CHECK(pub_key.as_octet_string() == wallet->get_public_key().move_as_ok().as_octet_string()); + ////LOG(ERROR) << wallet->get_balance(x, 60 * 60 * 24 * 400).move_as_ok(); + + //auto new_wallet_query = fift_output.source_lookup.read_file("rwallet-query.boc").move_as_ok().data; + //auto new_wallet_addr = fift_output.source_lookup.read_file("rwallet.addr").move_as_ok().data; + + //auto address = wallet->get_address(-1); + ////CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); + //address.bounceable = false; + //auto res = ton::GenericAccount::create_ext_message(address, wallet->get_init_state(), + //wallet->get_init_message(priv_key).move_as_ok()); + //LOG(ERROR) << "-------"; + //vm::load_cell_slice(res).print_rec(std::cerr); + //LOG(ERROR) << "-------"; + //vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr); + //CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash()); + + //auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok(); + //fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet-v2.fif")).ensure(); + //fift_output.source_lookup.write_file("rwallet.pk", priv_key.as_octet_string().as_slice()).ensure(); + //fift_output = fift::mem_run_fift( + //std::move(fift_output.source_lookup), + //{"aba", "rwallet", "-C", "TESTv2", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "0", "321"}) + //.move_as_ok(); + //auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; + //ton::TestWallet::Gift gift; + //gift.destination = dest; + //gift.message = "TESTv2"; + //gift.gramms = 321000000000ll; + ////CHECK(priv_key.get_public_key().ok().as_octet_string() == wallet->get_public_key().ok().as_octet_string()); + //auto gift_message = ton::GenericAccount::create_ext_message( + //address, {}, wallet->make_a_gift_message(priv_key, 60, {gift}).move_as_ok()); + //LOG(ERROR) << "-------"; + //vm::load_cell_slice(gift_message).print_rec(std::cerr); + //LOG(ERROR) << "-------"; + //vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr); + //CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash()); +} +TEST(Tonlib, RestrictedWallet3) { + auto init_priv_key = td::Ed25519::generate_private_key().move_as_ok(); + auto init_pub_key = init_priv_key.get_public_key().move_as_ok(); + auto priv_key = td::Ed25519::generate_private_key().move_as_ok(); + auto pub_key = priv_key.get_public_key().move_as_ok(); + + ton::RestrictedWallet::InitData init_data; + init_data.init_key = init_pub_key.as_octet_string(); + init_data.main_key = pub_key.as_octet_string(); + init_data.wallet_id = 123; + auto wallet = ton::RestrictedWallet::create(init_data, 1); + + auto address = wallet->get_address(); + + td::uint64 x = 100 * 1000000000ull; + ton::RestrictedWallet::Config config; + config.start_at = 1; + config.limits = {{-32768, x}, {92, x * 3 / 4}, {183, x * 1 / 2}, {366, x * 1 / 4}, {548, 0}}; + CHECK(wallet.write().send_external_message(wallet->get_init_message(init_priv_key, 10, config).move_as_ok()).success); + CHECK(wallet->get_seqno().move_as_ok() == 1); + + ton::WalletInterface::Gift gift; + gift.destination = address; + gift.message = "hello"; + CHECK(wallet.write().send_external_message(wallet->make_a_gift_message(priv_key, 10, {gift}).move_as_ok()).success); + CHECK(wallet->get_seqno().move_as_ok() == 2); +} + +template +void check_wallet_seqno(td::Ref wallet, td::uint32 seqno) { + ASSERT_EQ(seqno, wallet->get_seqno().ok()); +} +void check_wallet_seqno(td::Ref wallet, td::uint32 seqno) { +} +void check_wallet_seqno(td::Ref wallet, td::uint32 seqno) { +} +template +void check_wallet_state(td::Ref wallet, td::uint32 seqno, td::uint32 wallet_id, td::Slice public_key) { + ASSERT_EQ(wallet_id, wallet->get_wallet_id().ok()); + ASSERT_EQ(public_key, wallet->get_public_key().ok().as_octet_string().as_slice()); + check_wallet_seqno(wallet, seqno); +} + +struct CreatedWallet { + td::optional priv_key; + block::StdAddress address; + td::Ref wallet; +}; + +template +class InitWallet { public: - SimpleWallet(State state) : SmartContract(std::move(state)) { - } + CreatedWallet operator()(int revision) const { + ton::WalletInterface::DefaultInitData init_data; + auto priv_key = td::Ed25519::generate_private_key().move_as_ok(); + auto pub_key = priv_key.get_public_key().move_as_ok(); - const State& get_state() const { - return state_; - } - SimpleWallet* make_copy() const override { - return new SimpleWallet{state_}; - } + init_data.seqno = 0; + init_data.wallet_id = 123; + init_data.public_key = pub_key.as_octet_string(); - static td::Ref create_empty() { - return td::Ref(true, State{ton::SmartContractCode::simple_wallet_ext(), {}}); - } - static td::Ref create(td::Ref data) { - return td::Ref(true, State{ton::SmartContractCode::simple_wallet_ext(), std::move(data)}); - } - static td::Ref create_fast(td::Ref data) { - return td::Ref(true, State{ton::SmartContractCode::simple_wallet(), std::move(data)}); - } + auto wallet = T::create(init_data, revision); + auto address = wallet->get_address(); + check_wallet_state(wallet, 0, 123, init_data.public_key); + CHECK(wallet.write().send_external_message(wallet->get_init_message(priv_key).move_as_ok()).success); - td::int32 seqno() const { - auto res = run_get_method("seqno"); - return res.stack.write().pop_smallint_range(1000000000); - } - - td::Ref create_init_state(td::Slice public_key) const { - td::RefInt256 pk{true}; - pk.write().import_bytes(public_key.ubegin(), public_key.size(), false); - auto res = run_get_method("create_init_state", {pk}); - return res.stack.write().pop_cell(); - } - - td::Ref prepare_send_message(td::Ref msg, td::int8 mode = 3) const { - auto res = run_get_method("prepare_send_message", {td::make_refint(mode), msg}); - return res.stack.write().pop_cell(); - } - - static td::Ref sign_message(vm::Ref body, const td::Ed25519::PrivateKey& pk) { - auto signature = pk.sign(body->get_hash().as_slice()).move_as_ok(); - return vm::CellBuilder().store_bytes(signature.as_slice()).append_cellslice(vm::load_cell_slice(body)).finalize(); + CreatedWallet res; + res.wallet = std::move(wallet); + res.address = std::move(address); + res.priv_key = std::move(priv_key); + return res; } }; -TEST(Smartcon, Simple) { - auto private_key = td::Ed25519::generate_private_key().move_as_ok(); - auto public_key = private_key.get_public_key().move_as_ok().as_octet_string(); +template <> +CreatedWallet InitWallet::operator()(int revision) const { + auto init_priv_key = td::Ed25519::generate_private_key().move_as_ok(); + auto init_pub_key = init_priv_key.get_public_key().move_as_ok(); + auto priv_key = td::Ed25519::generate_private_key().move_as_ok(); + auto pub_key = priv_key.get_public_key().move_as_ok(); - auto w_lib = SimpleWallet::create_empty(); - auto init_data = w_lib->create_init_state(public_key); + ton::RestrictedWallet::InitData init_data; + init_data.init_key = init_pub_key.as_octet_string(); + init_data.main_key = pub_key.as_octet_string(); + init_data.wallet_id = 123; + auto wallet = ton::RestrictedWallet::create(init_data, 1); + check_wallet_state(wallet, 0, 123, init_data.init_key); - auto w = SimpleWallet::create(init_data); - LOG(ERROR) << w->code_size(); - auto fw = SimpleWallet::create_fast(init_data); - LOG(ERROR) << fw->code_size(); - LOG(ERROR) << w->seqno(); + auto address = wallet->get_address(); - for (int i = 0; i < 20; i++) { - auto msg = w->sign_message(w->prepare_send_message(vm::CellBuilder().finalize()), private_key); - w.write().send_external_message(msg); - fw.write().send_external_message(msg); + td::uint64 x = 100 * 1000000000ull; + ton::RestrictedWallet::Config config; + config.start_at = 1; + config.limits = {{-32768, x}, {92, x * 3 / 4}, {183, x * 1 / 2}, {366, x * 1 / 4}, {548, 0}}; + CHECK(wallet.write().send_external_message(wallet->get_init_message(init_priv_key, 10, config).move_as_ok()).success); + CHECK(wallet->get_seqno().move_as_ok() == 1); + + CreatedWallet res; + res.wallet = std::move(wallet); + res.address = std::move(address); + res.priv_key = std::move(priv_key); + return res; +} + +template +void do_test_wallet(int revision) { + auto res = InitWallet()(revision); + auto priv_key = res.priv_key.unwrap(); + auto address = std::move(res.address); + auto iwallet = std::move(res.wallet); + auto public_key = priv_key.get_public_key().move_as_ok().as_octet_string(); + + check_wallet_state(iwallet, 1, 123, public_key); + + // lets send a lot of messages + std::vector gifts; + for (size_t i = 0; i < iwallet->get_max_gifts_size(); i++) { + ton::WalletInterface::Gift gift; + gift.gramms = 1; + gift.destination = address; + gift.message = std::string(iwallet->get_max_message_size(), 'z'); + gifts.push_back(gift); } - ASSERT_EQ(20, w->seqno()); - CHECK(w->get_state().data->get_hash() == fw->get_state().data->get_hash()); + + td::uint32 valid_until = 10000; + auto send_gifts = iwallet->make_a_gift_message(priv_key, valid_until, gifts).move_as_ok(); + + { + auto cwallet = iwallet; + CHECK(!cwallet.write() + .send_external_message(send_gifts, ton::SmartContract::Args().set_now(valid_until + 1)) + .success); + } + //TODO: make wallet work (or not) with now == valid_until + auto ans = iwallet.write().send_external_message(send_gifts, ton::SmartContract::Args().set_now(valid_until - 1)); + CHECK(ans.success); + CHECK((int)gifts.size() <= ans.output_actions_count(ans.actions)); + check_wallet_state(iwallet, 2, 123, public_key); +} + +template +void do_test_wallet() { + for (auto revision : T::get_revisions()) { + do_test_wallet(revision); + } +} + +TEST(Tonlib, Wallet) { + do_test_wallet(); + do_test_wallet(); + do_test_wallet(); + do_test_wallet(); + do_test_wallet(); } namespace std { // ouch @@ -464,16 +558,58 @@ TEST(Smartcon, Multisig) { wallet_id, td::transform(keys, [](auto& key) { return key.get_public_key().ok().as_octet_string(); }), k); auto ms = ton::MultisigWallet::create(init_state); - td::uint64 query_id = 123 | ((100 * 60ull) << 32); - ton::MultisigWallet::QueryBuilder qb(wallet_id, query_id, vm::CellBuilder().finalize()); - // first empty query (init) - CHECK(ms.write().send_external_message(vm::CellBuilder().finalize()).code == 0); - // first empty query - CHECK(ms.write().send_external_message(vm::CellBuilder().finalize()).code > 0); + td::uint32 now = 0; + auto args = [&now]() -> ton::SmartContract::Args { return ton::SmartContract::Args().set_now(now); }; + // first empty query (init) + CHECK(ms.write().send_external_message(vm::CellBuilder().finalize(), args()).code == 0); + // first empty query + CHECK(ms.write().send_external_message(vm::CellBuilder().finalize(), args()).code > 0); + + { + td::uint64 query_id = 123 | ((now + 10 * 60ull) << 32); + ton::MultisigWallet::QueryBuilder qb(wallet_id, query_id, vm::CellBuilder().finalize()); + auto query = qb.create(0, keys[0]); + auto res = ms.write().send_external_message(query, args()); + CHECK(!res.accepted); + CHECK(res.code == 41); + } + { + for (int i = 1; i <= 11; i++) { + td::uint64 query_id = i | ((now + 100 * 60ull) << 32); + ton::MultisigWallet::QueryBuilder qb(wallet_id, query_id, vm::CellBuilder().finalize()); + auto query = qb.create(5, keys[5]); + auto res = ms.write().send_external_message(query, args()); + if (i <= 10) { + CHECK(res.accepted); + } else { + CHECK(!res.accepted); + } + } + + now += 100 * 60 + 100; + { + td::uint64 query_id = 200 | ((now + 100 * 60ull) << 32); + ton::MultisigWallet::QueryBuilder qb(wallet_id, query_id, vm::CellBuilder().finalize()); + auto query = qb.create(6, keys[6]); + auto res = ms.write().send_external_message(query, args()); + CHECK(res.accepted); + } + + { + td::uint64 query_id = 300 | ((now + 100 * 60ull) << 32); + ton::MultisigWallet::QueryBuilder qb(wallet_id, query_id, vm::CellBuilder().finalize()); + auto query = qb.create(5, keys[5]); + auto res = ms.write().send_external_message(query, args()); + CHECK(res.accepted); + } + } + + td::uint64 query_id = 123 | ((now + 100 * 60ull) << 32); + ton::MultisigWallet::QueryBuilder qb(wallet_id, query_id, vm::CellBuilder().finalize()); for (int i = 0; i < 10; i++) { auto query = qb.create(i, keys[i]); - auto ans = ms.write().send_external_message(query); + auto ans = ms.write().send_external_message(query, args()); LOG(INFO) << "CODE: " << ans.code; LOG(INFO) << "GAS: " << ans.gas_used; } @@ -483,12 +619,12 @@ TEST(Smartcon, Multisig) { auto query = qb.create(49, keys[49]); CHECK(ms->get_n_k() == std::make_pair(n, k)); - auto ans = ms.write().send_external_message(query); + auto ans = ms.write().send_external_message(query, args()); LOG(INFO) << "CODE: " << ans.code; LOG(INFO) << "GAS: " << ans.gas_used; CHECK(ans.success); ASSERT_EQ(0, ms->processed(query_id)); - CHECK(ms.write().send_external_message(query).code > 0); + CHECK(ms.write().send_external_message(query, args()).code > 0); ASSERT_EQ(0, ms->processed(query_id)); { @@ -499,7 +635,7 @@ TEST(Smartcon, Multisig) { query = qb.create(99, keys[99]); } - ans = ms.write().send_external_message(query); + ans = ms.write().send_external_message(query, args()); LOG(INFO) << "CODE: " << ans.code; LOG(INFO) << "GAS: " << ans.gas_used; ASSERT_EQ(-1, ms->processed(query_id)); @@ -670,3 +806,860 @@ TEST(Smartcont, MultisigStress) { LOG(INFO) << "Final code size: " << ms->code_size(); LOG(INFO) << "Final data size: " << ms->data_size(); } + +class MapDns { + public: + using ManualDns = ton::ManualDns; + struct Entry { + std::string name; + td::Bits256 category = td::Bits256::zero(); + std::string text; + + auto key() const { + return std::tie(name, category); + } + bool operator<(const Entry& other) const { + return key() < other.key(); + } + bool operator==(const ton::DnsInterface::Entry& other) const { + return key() == other.key() && other.data.type == ManualDns::EntryData::Type::Text && + other.data.data.get().text == text; + } + bool operator==(const Entry& other) const { + return key() == other.key() && text == other.text; + } + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const Entry& entry) { + return sb << "[" << entry.name << ":" << entry.category.to_hex() << ":" << entry.text << "]"; + } + }; + struct Action { + std::string name; + td::Bits256 category = td::Bits256::zero(); + td::optional text; + + bool does_create_category() const { + CHECK(!name.empty()); + CHECK(!category.is_zero()); + return static_cast(text); + } + bool does_change_empty() const { + CHECK(!name.empty()); + CHECK(!category.is_zero()); + return static_cast(text) && !text.value().empty(); + } + void make_non_empty() { + CHECK(!name.empty()); + CHECK(!category.is_zero()); + if (!text) { + text = ""; + } + } + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const Action& entry) { + return sb << "[" << entry.name << ":" << entry.category.to_hex() << ":" + << (entry.text ? entry.text.value() : "") << "]"; + } + }; + void update(td::Span actions) { + for (auto& action : actions) { + do_update(action); + } + } + using CombinedActions = ton::ManualDns::CombinedActions; + void update_combined(td::Span actions) { + LOG(ERROR) << "BEGIN"; + LOG(ERROR) << td::format::as_array(actions); + auto combined_actions = ton::ManualDns::combine_actions(actions); + for (auto& c : combined_actions) { + LOG(ERROR) << c.name << ":" << c.category.to_hex(); + if (c.actions) { + LOG(ERROR) << td::format::as_array(c.actions.value()); + } + } + LOG(ERROR) << "END"; + for (auto& combined_action : combined_actions) { + do_update(combined_action); + } + } + + std::vector resolve(td::Slice name, td::Bits256 category) { + std::vector res; + if (name.empty()) { + for (auto& a : entries_) { + for (auto& b : a.second) { + res.push_back({a.first, b.first, b.second}); + } + } + } else { + auto it = entries_.find(name); + while (it == entries_.end()) { + auto sz = name.find('.'); + category = ton::DNS_NEXT_RESOLVER_CATEGORY; + if (sz != td::Slice::npos) { + name = name.substr(sz + 1); + } else { + break; + } + it = entries_.find(name); + } + if (it != entries_.end()) { + for (auto& b : it->second) { + if (category.is_zero() || category == b.first) { + res.push_back({name.str(), b.first, b.second}); + } + } + } + } + + std::sort(res.begin(), res.end()); + return res; + } + + private: + std::map, std::less<>> entries_; + void do_update(const Action& action) { + if (action.name.empty()) { + entries_.clear(); + return; + } + if (action.category.is_zero()) { + entries_.erase(action.name); + return; + } + if (action.text) { + if (action.text.value().empty()) { + entries_[action.name].erase(action.category); + } else { + entries_[action.name][action.category] = action.text.value(); + } + } else { + auto it = entries_.find(action.name); + if (it != entries_.end()) { + it->second.erase(action.category); + } + } + } + + void do_update(const CombinedActions& actions) { + if (actions.name.empty()) { + entries_.clear(); + LOG(ERROR) << "CLEAR"; + if (!actions.actions) { + return; + } + for (auto& action : actions.actions.value()) { + CHECK(!action.name.empty()); + CHECK(!action.category.is_zero()); + CHECK(action.text); + if (action.text.value().empty()) { + entries_[action.name]; + } else { + entries_[action.name][action.category] = action.text.value(); + } + } + return; + } + if (actions.category.is_zero()) { + entries_.erase(actions.name); + LOG(ERROR) << "CLEAR " << actions.name; + if (!actions.actions) { + return; + } + entries_[actions.name]; + for (auto& action : actions.actions.value()) { + CHECK(action.name == actions.name); + CHECK(!action.category.is_zero()); + CHECK(action.text); + if (action.text.value().empty()) { + entries_[action.name]; + } else { + entries_[action.name][action.category] = action.text.value(); + } + } + return; + } + CHECK(actions.actions); + CHECK(actions.actions.value().size() == 1); + for (auto& action : actions.actions.value()) { + CHECK(action.name == actions.name); + CHECK(!action.category.is_zero()); + if (action.text) { + if (action.text.value().empty()) { + entries_[action.name].erase(action.category); + } else { + entries_[action.name][action.category] = action.text.value(); + } + } else { + auto it = entries_.find(action.name); + if (it != entries_.end()) { + it->second.erase(action.category); + } + } + } + } +}; + +class CheckedDns { + public: + explicit CheckedDns(bool check_smc = true, bool check_combine = true) { + if (check_smc) { + key_ = td::Ed25519::generate_private_key().move_as_ok(); + dns_ = ManualDns::create(ManualDns::create_init_data_fast(key_.value().get_public_key().move_as_ok(), 123), -1); + } + if (check_combine) { + combined_map_dns_ = MapDns(); + } + } + using Action = MapDns::Action; + using Entry = MapDns::Entry; + void update(td::Span entries) { + if (dns_.not_null()) { + auto smc_actions = td::transform(entries, [](auto& entry) { + ton::DnsInterface::Action action; + action.name = entry.name; + action.category = entry.category; + if (entry.text) { + if (entry.text.value().empty()) { + action.data = td::Ref(); + } else { + action.data = ManualDns::EntryData::text(entry.text.value()).as_cell().move_as_ok(); + } + } + return action; + }); + auto query = dns_->create_update_query(key_.value(), smc_actions, query_id_++).move_as_ok(); + CHECK(dns_.write().send_external_message(std::move(query)).code == 0); + } + map_dns_.update(entries); + if (combined_map_dns_) { + combined_map_dns_.value().update_combined(entries); + } + } + void update(const Action& action) { + return update(td::span_one(action)); + } + + std::vector resolve(td::Slice name, td::Bits256 category) { + LOG(ERROR) << "RESOLVE: " << name << " " << category.to_hex(); + auto res = map_dns_.resolve(name, category); + LOG(ERROR) << td::format::as_array(res); + + if (dns_.not_null()) { + auto other_res = dns_->resolve(name, category).move_as_ok(); + + std::sort(other_res.begin(), other_res.end()); + if (res.size() != other_res.size()) { + LOG(ERROR) << td::format::as_array(res); + LOG(FATAL) << td::format::as_array(other_res); + } + for (size_t i = 0; i < res.size(); i++) { + if (!(res[i] == other_res[i])) { + LOG(ERROR) << td::format::as_array(res); + LOG(FATAL) << td::format::as_array(other_res); + } + } + } + if (combined_map_dns_) { + auto other_res = combined_map_dns_.value().resolve(name, category); + + std::sort(other_res.begin(), other_res.end()); + if (res.size() != other_res.size()) { + LOG(ERROR) << td::format::as_array(res); + LOG(FATAL) << td::format::as_array(other_res); + } + for (size_t i = 0; i < res.size(); i++) { + if (!(res[i] == other_res[i])) { + LOG(ERROR) << td::format::as_array(res); + LOG(FATAL) << td::format::as_array(other_res); + } + } + } + + return res; + } + + private: + using ManualDns = ton::ManualDns; + td::optional key_; + td::Ref dns_; + td::uint32 query_id_ = 1; // Query id serve as "valid until", but in tests now() == 0 + + MapDns map_dns_; + td::optional combined_map_dns_; + + void do_update_smc(const Action& entry) { + LOG(ERROR) << td::format::escaped(ManualDns::encode_name(entry.name)); + ton::DnsInterface::Action action; + action.name = entry.name; + action.category = entry.category; + action.data = ManualDns::EntryData::text(entry.text.value()).as_cell().move_as_ok(); + } +}; + +static td::Bits256 intToCat(td::uint32 x) { + auto y = td::make_refint(x); + td::Bits256 cat; + y->export_bytes(cat.data(), 32, false); + return cat; +} + +void do_dns_test(CheckedDns&& dns) { + using Action = CheckedDns::Action; + std::vector actions; + + td::Random::Xorshift128plus rnd(123); + + auto gen_name = [&] { + auto cnt = rnd.fast(1, 2); + std::string res; + for (int i = 0; i < cnt; i++) { + if (i != 0) { + res += '.'; + } + auto len = rnd.fast(1, 1); + for (int j = 0; j < len; j++) { + res += static_cast(rnd.fast('a', 'b')); + } + } + return res; + }; + auto gen_text = [&] { + std::string res; + int len = 5; + for (int j = 0; j < len; j++) { + res += static_cast(rnd.fast('a', 'b')); + } + return res; + }; + + auto gen_action = [&] { + Action action; + if (rnd.fast(0, 1000) == 0) { + return action; + } + action.name = gen_name(); + if (rnd.fast(0, 20) == 0) { + return action; + } + action.category = intToCat(rnd.fast(1, 5)); + if (rnd.fast(0, 4) == 0) { + return action; + } + if (rnd.fast(0, 4) == 0) { + action.text = ""; + return action; + } + action.text = gen_text(); + return action; + }; + + SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR)); + for (int i = 0; i < 100000; i++) { + actions.push_back(gen_action()); + if (rnd.fast(0, 10) == 0) { + dns.update(actions); + actions.clear(); + } + auto name = gen_name(); + auto category = td::Bits256::zero(); + dns.resolve(name, intToCat(rnd.fast(0, 5))); + } +}; + +TEST(Smartcont, DnsManual) { + using ManualDns = ton::ManualDns; + auto test_entry_data = [](auto&& entry_data) { + auto cell = entry_data.as_cell().move_as_ok(); + auto cs = vm::load_cell_slice(cell); + auto new_entry_data = ManualDns::EntryData::from_cellslice(cs).move_as_ok(); + ASSERT_EQ(entry_data, new_entry_data); + }; + test_entry_data(ManualDns::EntryData::text("abcd")); + test_entry_data(ManualDns::EntryData::adnl_address(ton::Bits256{})); + + CHECK(td::Slice("a\0b\0") == ManualDns::encode_name("b.a")); + CHECK(td::Slice("a\0b\0") == ManualDns::encode_name(".b.a")); + ASSERT_EQ(".b.a", ManualDns::decode_name("a\0b\0")); + ASSERT_EQ("b.a", ManualDns::decode_name("a\0b")); + ASSERT_EQ("", ManualDns::decode_name("")); + + auto key = td::Ed25519::generate_private_key().move_as_ok(); + + auto manual = ManualDns::create(ManualDns::create_init_data_fast(key.get_public_key().move_as_ok(), 123), -1); + CHECK(manual->get_wallet_id().move_as_ok() == 123); + auto init_query = manual->create_init_query(key).move_as_ok(); + LOG(ERROR) << "A"; + CHECK(manual.write().send_external_message(init_query).code == 0); + LOG(ERROR) << "B"; + CHECK(manual.write().send_external_message(init_query).code != 0); + + auto value = vm::CellBuilder().store_bytes("hello world").finalize(); + auto set_query = + manual + ->sign(key, manual->prepare(manual->create_set_value_unsigned(intToCat(1), "a\0b\0", value).move_as_ok(), 1) + .move_as_ok()) + .move_as_ok(); + CHECK(manual.write().send_external_message(set_query).code == 0); + + auto res = manual->run_get_method( + "dnsresolve", {vm::load_cell_slice_ref(vm::CellBuilder().store_bytes("a\0b\0").finalize()), td::make_refint(1)}); + CHECK(res.code == 0); + CHECK(res.stack.write().pop_cell()->get_hash() == value->get_hash()); + + CheckedDns dns; + dns.update(CheckedDns::Action{"a.b.c", intToCat(1), "hello"}); + CHECK(dns.resolve("a.b.c", intToCat(1)).at(0).text == "hello"); + dns.resolve("a", intToCat(1)); + dns.resolve("a.b", intToCat(1)); + CHECK(dns.resolve("a.b.c", intToCat(2)).empty()); + dns.update(CheckedDns::Action{"a.b.c", intToCat(2), "test"}); + CHECK(dns.resolve("a.b.c", intToCat(2)).at(0).text == "test"); + dns.resolve("a.b.c", intToCat(1)); + dns.resolve("a.b.c", intToCat(2)); + LOG(ERROR) << "Test zero category"; + dns.resolve("a.b.c", intToCat(0)); + dns.update(CheckedDns::Action{"", intToCat(0), ""}); + CHECK(dns.resolve("a.b.c", intToCat(2)).empty()); + + LOG(ERROR) << "Test multipe update"; + { + CheckedDns::Action e[4] = { + CheckedDns::Action{"", intToCat(0), ""}, CheckedDns::Action{"a.b.c", intToCat(1), "hello"}, + CheckedDns::Action{"a.b.c", intToCat(2), "world"}, CheckedDns::Action{"x.y.z", intToCat(3), "abc"}}; + dns.update(td::span(e, 4)); + } + dns.resolve("a.b.c", intToCat(1)); + dns.resolve("a.b.c", intToCat(2)); + dns.resolve("x.y.z", intToCat(3)); + + dns.update(td::span_one(CheckedDns::Action{"x.y.z", intToCat(0), ""})); + + dns.resolve("a.b.c", intToCat(1)); + dns.resolve("a.b.c", intToCat(2)); + dns.resolve("x.y.z", intToCat(3)); + + { + CheckedDns::Action e[3] = {CheckedDns::Action{"x.y.z", intToCat(0), ""}, + CheckedDns::Action{"x.y.z", intToCat(1), "xxx"}, + CheckedDns::Action{"x.y.z", intToCat(2), "yyy"}}; + dns.update(td::span(e, 3)); + } + dns.resolve("a.b.c", intToCat(1)); + dns.resolve("a.b.c", intToCat(2)); + dns.resolve("x.y.z", intToCat(1)); + dns.resolve("x.y.z", intToCat(2)); + dns.resolve("x.y.z", intToCat(3)); + + { + auto actions_ext = + ton::ManualDns::parse("delete.name one\nset one 1 TEXT:one\ndelete.name two\nset two 2 TEXT:two").move_as_ok(); + + auto actions = td::transform(actions_ext, [](auto& action) { + td::optional data; + if (action.data) { + data = action.data.value().data.template get().text; + } + return CheckedDns::Action{action.name, action.category, std::move(data)}; + }); + + dns.update(actions); + } + dns.resolve("one", intToCat(1)); + dns.resolve("two", intToCat(2)); + + // TODO: rethink semantic of creating an empty dictionary + do_dns_test(CheckedDns(true, true)); +} + +using namespace ton::pchan; + +template +struct ValidateState { + T& self() { + return static_cast(*this); + } + + void init(td::Ref state) { + state_ = state; + block::gen::ChanData::Record data_rec; + if (!tlb::unpack_cell(state, data_rec)) { + on_fatal_error(td::Status::Error("Expected Data")); + return; + } + if (!tlb::unpack_cell(data_rec.state, self().rec)) { + on_fatal_error(td::Status::Error("Expected StatePayout")); + return; + } + CHECK(self().rec.A.not_null()); + } + + T& expect_grams(td::Ref cs, td::uint64 expected, td::Slice name) { + if (has_fatal_error_) { + return self(); + } + td::RefInt256 got; + CHECK(cs.not_null()); + CHECK(block::tlb::t_Grams.as_integer_to(cs, got)); + if (got->cmp(expected) != 0) { + on_error(td::Status::Error(PSLICE() << name << ": expected " << expected << ", got " << got->to_dec_string())); + } + return self(); + } + template + T& expect_eq(S a, S expected, td::Slice name) { + if (has_fatal_error_) { + return self(); + } + if (!(a == expected)) { + on_error(td::Status::Error(PSLICE() << name << ": expected " << expected << ", got " << a)); + } + return self(); + } + + td::Status finish() { + if (errors_.empty()) { + return td::Status::OK(); + } + std::stringstream ss; + block::gen::t_ChanData.print_ref(ss, state_); + td::StringBuilder sb; + for (auto& error : errors_) { + sb << error << "\n"; + } + sb << ss.str(); + return td::Status::Error(sb.as_cslice()); + } + + void on_fatal_error(td::Status error) { + CHECK(!has_fatal_error_); + has_fatal_error_ = true; + on_error(std::move(error)); + } + void on_error(td::Status error) { + CHECK(error.is_error()); + errors_.push_back(std::move(error)); + } + + public: + td::Ref state_; + bool has_fatal_error_{false}; + std::vector errors_; +}; + +struct ValidateStatePayout : public ValidateState { + ValidateStatePayout& expect_A(td::uint64 a) { + expect_grams(rec.A, a, "A"); + return *this; + } + ValidateStatePayout& expect_B(td::uint64 b) { + expect_grams(rec.B, b, "B"); + return *this; + } + + ValidateStatePayout(td::Ref state) { + init(std::move(state)); + } + + block::gen::ChanState::Record_chan_state_payout rec; +}; + +struct ValidateStateInit : public ValidateState { + ValidateStateInit& expect_A(td::uint64 a) { + expect_grams(rec.A, a, "A"); + return *this; + } + ValidateStateInit& expect_B(td::uint64 b) { + expect_grams(rec.B, b, "B"); + return *this; + } + ValidateStateInit& expect_min_A(td::uint64 a) { + expect_grams(rec.min_A, a, "min_A"); + return *this; + } + ValidateStateInit& expect_min_B(td::uint64 b) { + expect_grams(rec.min_B, b, "min_B"); + return *this; + } + ValidateStateInit& expect_expire_at(td::uint32 b) { + expect_eq(rec.expire_at, b, "expire_at"); + return *this; + } + ValidateStateInit& expect_signed_A(bool x) { + expect_eq(rec.signed_A, x, "signed_A"); + return *this; + } + ValidateStateInit& expect_signed_B(bool x) { + expect_eq(rec.signed_B, x, "signed_B"); + return *this; + } + + ValidateStateInit(td::Ref state) { + init(std::move(state)); + } + + block::gen::ChanState::Record_chan_state_init rec; +}; + +struct ValidateStateClose : public ValidateState { + ValidateStateClose& expect_A(td::uint64 a) { + expect_grams(rec.A, a, "A"); + return *this; + } + ValidateStateClose& expect_B(td::uint64 b) { + expect_grams(rec.B, b, "B"); + return *this; + } + ValidateStateClose& expect_promise_A(td::uint64 a) { + expect_grams(rec.promise_A, a, "promise_A"); + return *this; + } + ValidateStateClose& expect_promise_B(td::uint64 b) { + expect_grams(rec.promise_B, b, "promise_B"); + return *this; + } + ValidateStateClose& expect_expire_at(td::uint32 b) { + expect_eq(rec.expire_at, b, "expire_at"); + return *this; + } + ValidateStateClose& expect_signed_A(bool x) { + expect_eq(rec.signed_A, x, "signed_A"); + return *this; + } + ValidateStateClose& expect_signed_B(bool x) { + expect_eq(rec.signed_B, x, "signed_B"); + return *this; + } + + ValidateStateClose(td::Ref state) { + init(std::move(state)); + } + + block::gen::ChanState::Record_chan_state_close rec; +}; + +// config$_ initTimeout:int exitTimeout:int a_key:int256 b_key:int256 a_addr b_addr channel_id:int256 = Config; +TEST(Smarcont, Channel) { + auto code = ton::SmartContractCode::get_code(ton::SmartContractCode::PaymentChannel); + Config config; + auto a_pkey = td::Ed25519::generate_private_key().move_as_ok(); + auto b_pkey = td::Ed25519::generate_private_key().move_as_ok(); + config.init_timeout = 20; + config.close_timeout = 40; + auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok(); + config.a_addr = dest; + config.b_addr = dest; + config.a_key = a_pkey.get_public_key().ok().as_octet_string(); + config.b_key = b_pkey.get_public_key().ok().as_octet_string(); + config.channel_id = 123; + + Data data; + data.config = config.serialize(); + data.state = data.init_state(); + auto data_cell = data.serialize(); + + auto channel = ton::SmartContract::create(ton::SmartContract::State{code, data_cell}); + ValidateStateInit(channel->get_state().data) + .expect_A(0) + .expect_B(0) + .expect_min_A(0) + .expect_min_B(0) + .expect_signed_A(false) + .expect_signed_B(false) + .expect_expire_at(0) + .finish() + .ensure(); + + enum err { + ok = 0, + wrong_a_signature = 31, + wrong_b_signature, + msg_value_too_small, + replay_protection, + no_timeout, + expected_init, + expected_close, + no_promise_signature, + wrong_channel_id + }; + +#define expect_code(description, expected_code, e) \ + { \ + auto res = e; \ + LOG_IF(FATAL, expected_code != res.code) << " res.code=" << res.code << " " << description << "\n" << #e; \ + } +#define expect_ok(description, e) expect_code(description, 0, e) + + expect_code("Trying to invoke a timeout while channel is empty", no_timeout, + channel.write().send_external_message(MsgTimeoutBuilder().finalize(), + ton::SmartContract::Args().set_now(1000000))); + + expect_code("External init message with no signatures", replay_protection, + channel.write().send_external_message(MsgInitBuilder().channel_id(config.channel_id).finalize())); + expect_code("Internal init message with not enough value", msg_value_too_small, + channel.write().send_internal_message( + MsgInitBuilder().channel_id(config.channel_id).inc_A(1000).min_B(2000).with_a_key(&a_pkey).finalize(), + ton::SmartContract::Args().set_amount(100))); + expect_code( + "Internal init message with wrong channel_id", wrong_channel_id, + channel.write().send_internal_message(MsgInitBuilder().inc_A(1000).min_B(2000).with_a_key(&a_pkey).finalize(), + ton::SmartContract::Args().set_amount(1000))); + expect_ok("A init with (inc_A = 1000, min_A = 1, min_B = 2000)", + channel.write().send_internal_message(MsgInitBuilder() + .channel_id(config.channel_id) + .inc_A(1000) + .min_A(1) + .min_B(2000) + .with_a_key(&a_pkey) + .finalize(), + ton::SmartContract::Args().set_amount(1000))); + ValidateStateInit(channel->get_state().data) + .expect_A(1000) + .expect_B(0) + .expect_min_A(1) + .expect_min_B(2000) + .expect_signed_A(true) + .expect_signed_B(false) + .expect_expire_at(config.init_timeout) + .finish() + .ensure(); + + expect_code("Repeated init of A init with (inc_A = 100, min_B = 5000). Must be ignored", replay_protection, + channel.write().send_internal_message( + MsgInitBuilder().channel_id(config.channel_id).inc_A(100).min_B(5000).with_a_key(&a_pkey).finalize(), + ton::SmartContract::Args().set_amount(1000))); + expect_code( + "Trying to invoke a timeout too early", no_timeout, + channel.write().send_external_message(MsgTimeoutBuilder().finalize(), ton::SmartContract::Args().set_now(0))); + + { + auto channel_copy = channel; + expect_ok("Invoke a timeout", channel_copy.write().send_external_message(MsgTimeoutBuilder().finalize(), + ton::SmartContract::Args().set_now(21))); + ValidateStatePayout(channel_copy->get_state().data).expect_A(1000).expect_B(0).finish().ensure(); + } + { + auto channel_copy = channel; + expect_ok("B init with inc_B < min_B. Leads to immediate payout", + channel_copy.write().send_internal_message( + MsgInitBuilder().channel_id(config.channel_id).inc_B(1500).with_b_key(&b_pkey).finalize(), + ton::SmartContract::Args().set_amount(1500))); + ValidateStatePayout(channel_copy->get_state().data).expect_A(1000).expect_B(1500).finish().ensure(); + } + + expect_ok("B init with (inc_B = 2000, min_A = 1, min_A = 1000)", + channel.write().send_internal_message( + MsgInitBuilder().channel_id(config.channel_id).inc_B(2000).min_A(1000).with_b_key(&b_pkey).finalize(), + ton::SmartContract::Args().set_amount(2000))); + ValidateStateClose(channel->get_state().data) + .expect_A(1000) + .expect_B(2000) + .expect_promise_A(0) + .expect_promise_B(0) + .expect_signed_A(false) + .expect_signed_B(false) + .expect_expire_at(0) + .finish() + .ensure(); + + { + auto channel_copy = channel; + expect_ok("A&B send Promise(1000000, 1000000 + 10) signed by nobody", + channel_copy.write().send_external_message(MsgCloseBuilder() + .signed_promise(SignedPromiseBuilder() + .promise_A(1000000) + .promise_B(1000000 + 10) + .channel_id(config.channel_id) + .finalize()) + .with_a_key(&a_pkey) + .with_b_key(&b_pkey) + .finalize(), + ton::SmartContract::Args().set_now(21))); + ValidateStatePayout(channel_copy->get_state().data).expect_A(1000 + 10).expect_B(2000 - 10).finish().ensure(); + } + { + auto channel_copy = channel; + expect_ok("A&B send Promise(1000000, 1000000 + 10) signed by A", + channel_copy.write().send_external_message(MsgCloseBuilder() + .signed_promise(SignedPromiseBuilder() + .promise_A(1000000) + .promise_B(1000000 + 10) + .with_key(&a_pkey) + .channel_id(config.channel_id) + .finalize()) + .with_a_key(&a_pkey) + .with_b_key(&b_pkey) + .finalize(), + ton::SmartContract::Args().set_now(21))); + ValidateStatePayout(channel_copy->get_state().data).expect_A(1000 + 10).expect_B(2000 - 10).finish().ensure(); + } + + expect_code( + "A sends Promise(1000000, 0) signed by A", wrong_b_signature, + channel.write().send_external_message( + MsgCloseBuilder() + .signed_promise( + SignedPromiseBuilder().promise_A(1000000).with_key(&a_pkey).channel_id(config.channel_id).finalize()) + .with_a_key(&a_pkey) + .finalize(), + ton::SmartContract::Args().set_now(21))); + expect_code( + "B sends Promise(1000000, 0) signed by B", wrong_a_signature, + channel.write().send_external_message( + MsgCloseBuilder() + .signed_promise( + SignedPromiseBuilder().promise_A(1000000).with_key(&b_pkey).channel_id(config.channel_id).finalize()) + .with_b_key(&b_pkey) + .finalize(), + ton::SmartContract::Args().set_now(21))); + expect_code("B sends Promise(1000000, 0) signed by A with wrong channel_id", wrong_channel_id, + channel.write().send_external_message(MsgCloseBuilder() + .signed_promise(SignedPromiseBuilder() + .promise_A(1000000) + .with_key(&a_pkey) + .channel_id(config.channel_id + 1) + .finalize()) + .with_b_key(&b_pkey) + .finalize(), + ton::SmartContract::Args().set_now(21))); + expect_code( + "B sends unsigned Promise(1000000, 0)", no_promise_signature, + channel.write().send_external_message( + MsgCloseBuilder() + .signed_promise(SignedPromiseBuilder().promise_A(1000000).channel_id(config.channel_id).finalize()) + .with_b_key(&b_pkey) + .finalize(), + ton::SmartContract::Args().set_now(21))); + + expect_ok( + "B sends Promise(1000000, 0) signed by A", + channel.write().send_external_message( + MsgCloseBuilder() + .signed_promise( + SignedPromiseBuilder().promise_A(1000000).with_key(&a_pkey).channel_id(config.channel_id).finalize()) + .with_b_key(&b_pkey) + .finalize(), + ton::SmartContract::Args().set_now(21))); + ValidateStateClose(channel->get_state().data) + .expect_A(1000) + .expect_B(2000) + .expect_promise_A(1000000) + .expect_promise_B(0) + .expect_signed_A(false) + .expect_signed_B(true) + .expect_expire_at(21 + config.close_timeout) + .finish() + .ensure(); + + expect_ok("B sends Promise(0, 1000000 + 10) signed by A", + channel.write().send_external_message(MsgCloseBuilder() + .signed_promise(SignedPromiseBuilder() + .promise_B(1000000 + 10) + .with_key(&b_pkey) + .channel_id(config.channel_id) + .finalize()) + .with_a_key(&a_pkey) + .finalize(), + ton::SmartContract::Args().set_now(21))); + ValidateStatePayout(channel->get_state().data).expect_A(1000 + 10).expect_B(2000 - 10).finish().ensure(); +#undef expect_ok +#undef expect_code +} diff --git a/crypto/test/vm.cpp b/crypto/test/vm.cpp index 72e2f8c6..0f1b0442 100644 --- a/crypto/test/vm.cpp +++ b/crypto/test/vm.cpp @@ -14,20 +14,21 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ -#include "vm/continuation.h" +#include "vm/vm.h" #include "vm/cp0.h" #include "vm/dict.h" #include "fift/utils.h" #include "common/bigint.hpp" +#include "td/utils/base64.h" #include "td/utils/tests.h" #include "td/utils/ScopeGuard.h" #include "td/utils/StringBuilder.h" std::string run_vm(td::Ref cell) { - vm::init_op_cp0(); + vm::init_vm().ensure(); vm::DictionaryBase::get_empty_dictionary(); class Logger : public td::LogInterface { @@ -53,7 +54,10 @@ std::string run_vm(td::Ref cell) { vm::Stack stack; try { - vm::run_vm_code(vm::load_cell_slice_ref(cell), stack, 0 /*flags*/, nullptr /*data*/, std::move(log) /*VmLog*/); + vm::GasLimits gas_limit(1000, 1000); + + vm::run_vm_code(vm::load_cell_slice_ref(cell), stack, 0 /*flags*/, nullptr /*data*/, std::move(log) /*VmLog*/, + nullptr, &gas_limit); } catch (...) { LOG(FATAL) << "catch unhandled exception"; } @@ -77,6 +81,14 @@ void test_run_vm(td::Slice code_hex) { test_run_vm(to_cell(buff, bits)); } +void test_run_vm_raw(td::Slice code64) { + auto code = td::base64_decode(code64).move_as_ok(); + if (code.size() > 127) { + code.resize(127); + } + test_run_vm(vm::CellBuilder().store_bytes(code).finalize()); +} + TEST(VM, simple) { test_run_vm("ABCBABABABA"); } @@ -126,12 +138,12 @@ TEST(VM, unhandled_exception_1) { TEST(VM, unhandled_exception_2) { // infinite loop now - // test_run_vm("EBEDB4"); + test_run_vm("EBEDB4"); } TEST(VM, unhandled_exception_3) { // infinite loop now - // test_run_vm("EBEDC0"); + test_run_vm("EBEDC0"); } TEST(VM, unhandled_exception_4) { @@ -142,6 +154,17 @@ TEST(VM, unhandled_exception_5) { test_run_vm("738B04016D21F41476A721F49F"); } +TEST(VM, infinity_loop_1) { + test_run_vm_raw("f3r4AJGQ6rDraIQ="); +} +TEST(VM, infinity_loop_2) { + test_run_vm_raw("kpTt7ZLrig=="); +} + +TEST(VM, oom_1) { + test_run_vm_raw("bXflX/BvDw=="); +} + TEST(VM, bigint) { td::StringBuilder sb({}, true); diff --git a/crypto/test/wycheproof.h b/crypto/test/wycheproof.h index 182760ac..86a18501 100644 --- a/crypto/test/wycheproof.h +++ b/crypto/test/wycheproof.h @@ -1,3 +1,21 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ #pragma once #include @@ -288,7 +306,8 @@ std::string wycheproof_ed25519() { "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab07a9155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b30d2020", "result" : "invalid", "flags" : [] - },)abcd" R"abcd( + },)abcd" + R"abcd( { "tcId" : 34, "comment" : "include pk in signature", @@ -570,7 +589,8 @@ std::string wycheproof_ed25519() { "flags" : [ "SignatureMalleability" ] - },)abcd" R"abcd( + },)abcd" + R"abcd( { "tcId" : 68, "comment" : "checking malleability ", @@ -858,7 +878,8 @@ std::string wycheproof_ed25519() { "flags" : [] } ] - },)abcd" R"abcd( + },)abcd" + R"abcd( { "key" : { "curve" : "edwards25519", diff --git a/crypto/tl/tlbc-aux.h b/crypto/tl/tlbc-aux.h index 54f72bcc..97e5082e 100644 --- a/crypto/tl/tlbc-aux.h +++ b/crypto/tl/tlbc-aux.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include diff --git a/crypto/tl/tlbc-data.h b/crypto/tl/tlbc-data.h index 2aae4e8a..8b6f2392 100644 --- a/crypto/tl/tlbc-data.h +++ b/crypto/tl/tlbc-data.h @@ -14,11 +14,12 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include #include +#include "common/linalloc.hpp" namespace tlbc { @@ -26,6 +27,8 @@ using src::Lexem; using src::Lexer; using sym::sym_idx_t; +extern td::LinearAllocator AR; + struct Type; struct Constructor; diff --git a/crypto/tl/tlbc-gen-cpp.cpp b/crypto/tl/tlbc-gen-cpp.cpp index 3de92bd5..6edd0a12 100644 --- a/crypto/tl/tlbc-gen-cpp.cpp +++ b/crypto/tl/tlbc-gen-cpp.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "tlbc-gen-cpp.h" #include "td/utils/bits.h" @@ -94,6 +94,7 @@ void init_forbidden_cpp_idents() { l.insert("get_size"); l.insert("pack"); l.insert("unpack"); + l.insert("ops"); l.insert("cs"); l.insert("cb"); l.insert("cell_ref"); @@ -158,7 +159,6 @@ std::string CppIdentSet::compute_cpp_ident(std::string orig_ident, int count) { } if (!cnt) { os << '_'; - prev_skip = true; } if (count) { os << count; @@ -2014,7 +2014,7 @@ void CppTypeCode::generate_skip_field(const Constructor& constr, const Field& fi output_cpp_expr(ss, expr, 100, true); ss << '.'; } - ss << (validating ? "validate_skip(cs, weak" : "skip(cs"); + ss << (validating ? "validate_skip(ops, cs, weak" : "skip(cs"); output_negative_type_arguments(ss, expr); ss << ")"; actions += Action{std::move(ss)}; @@ -2054,7 +2054,7 @@ void CppTypeCode::generate_skip_field(const Constructor& constr, const Field& fi output_cpp_expr(ss, expr, 100); ss << '.'; } - ss << (validating ? "validate_skip(cs, weak)" : "skip(cs)") << tail; + ss << (validating ? "validate_skip(ops, cs, weak)" : "skip(cs)") << tail; actions += Action{std::move(ss)}; return; } @@ -2074,7 +2074,7 @@ void CppTypeCode::generate_skip_field(const Constructor& constr, const Field& fi output_cpp_expr(ss, expr, 100); ss << '.'; } - ss << "validate_skip_ref(cs, weak)" << tail; + ss << "validate_skip_ref(ops, cs, weak)" << tail; actions += Action{ss.str()}; } @@ -2101,8 +2101,8 @@ void CppTypeCode::generate_skip_cons_method(std::ostream& os, std::string nl, in void CppTypeCode::generate_skip_method(std::ostream& os, int options) { bool validate = options & 1; bool ret_ext = options & 2; - os << "\nbool " << cpp_type_class_name << "::" << (validate ? "validate_" : "") << "skip(vm::CellSlice& cs" - << (validate ? ", bool weak" : ""); + os << "\nbool " << cpp_type_class_name + << "::" << (validate ? "validate_skip(int* ops, vm::CellSlice& cs, bool weak" : "skip(vm::CellSlice& cs"); if (ret_ext) { os << skip_extra_args; } @@ -2470,7 +2470,7 @@ void CppTypeCode::generate_unpack_field(const CppTypeCode::ConsField& fi, const output_cpp_expr(ss, expr, 100, true); ss << '.'; } - ss << (validating ? "validate_fetch_to(cs, weak, " : "fetch_to(cs, ") << field_vars.at(i); + ss << (validating ? "validate_fetch_to(ops, cs, weak, " : "fetch_to(cs, ") << field_vars.at(i); output_negative_type_arguments(ss, expr); ss << ")"; actions += Action{std::move(ss)}; @@ -2514,8 +2514,8 @@ void CppTypeCode::generate_unpack_field(const CppTypeCode::ConsField& fi, const output_cpp_expr(ss, expr, 100); ss << '.'; } - ss << (validating ? "validate_" : "") << "fetch_" << (cvt == ct_enum ? "enum_" : "") << "to(cs, " - << (validating ? "weak, " : "") << field_vars.at(i) << ")" << tail; + ss << (validating ? "validate_" : "") << "fetch_" << (cvt == ct_enum ? "enum_" : "") + << (validating ? "to(ops, cs, weak, " : "to(cs, ") << field_vars.at(i) << ")" << tail; field_var_set[i] = true; actions += Action{std::move(ss)}; return; @@ -2540,7 +2540,7 @@ void CppTypeCode::generate_unpack_field(const CppTypeCode::ConsField& fi, const output_cpp_expr(ss, expr, 100); ss << '.'; } - ss << "validate_ref(" << field_vars.at(i) << "))" << tail; + ss << "validate_ref(ops, " << field_vars.at(i) << "))" << tail; actions += Action{ss.str()}; } @@ -2559,7 +2559,11 @@ void CppTypeCode::generate_unpack_method(std::ostream& os, CppTypeCode::ConsReco << "\n auto cs = load_cell_slice(std::move(cell_ref));" << "\n return " << (options & 1 ? "validate_" : "") << "unpack"; if (!(options & 8)) { - os << "(cs, data"; + os << "("; + if (options & 1) { + os << "ops, "; + } + os << "cs, data"; } else { os << "_" << cons_enum_name.at(rec.cons_idx) << "(cs"; for (const auto& f : rec.cpp_fields) { @@ -2773,7 +2777,7 @@ void CppTypeCode::generate_pack_field(const CppTypeCode::ConsField& fi, const Co output_cpp_expr(ss, expr, 100); ss << '.'; } - ss << "validate_ref(" << field_vars.at(i) << "))" << tail; + ss << "validate_ref(ops, " << field_vars.at(i) << "))" << tail; actions += Action{ss.str()}; } @@ -3093,7 +3097,7 @@ void CppTypeCode::generate_header(std::ostream& os, int options) { if (ret_params) { os << " bool skip(vm::CellSlice& cs" << skip_extra_args << ") const;\n"; } - os << " bool validate_skip(vm::CellSlice& cs, bool weak = false) const override"; + os << " bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override"; if (!inline_validate_skip) { os << ";\n"; } else if (sz) { @@ -3102,7 +3106,7 @@ void CppTypeCode::generate_header(std::ostream& os, int options) { os << " {\n return true;\n }\n"; } if (ret_params) { - os << " bool validate_skip(vm::CellSlice& cs, bool weak" << skip_extra_args << ") const;\n"; + os << " bool validate_skip(int *ops, vm::CellSlice& cs, bool weak" << skip_extra_args << ") const;\n"; os << " bool fetch_to(vm::CellSlice& cs, Ref& res" << skip_extra_args << ") const;\n"; } if (type.is_simple_enum) { diff --git a/crypto/tl/tlbc-gen-cpp.h b/crypto/tl/tlbc-gen-cpp.h index c84d825d..351f9257 100644 --- a/crypto/tl/tlbc-gen-cpp.h +++ b/crypto/tl/tlbc-gen-cpp.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/crypto/tl/tlbc.cpp b/crypto/tl/tlbc.cpp index 0364e5db..b48bc472 100644 --- a/crypto/tl/tlbc.cpp +++ b/crypto/tl/tlbc.cpp @@ -23,7 +23,7 @@ exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include @@ -219,6 +219,8 @@ inline bool is_uc_ident(sym_idx_t idx) { namespace tlbc { +td::LinearAllocator AR(1 << 22); + /* * * AUXILIARY DATA TYPES @@ -906,7 +908,7 @@ bool TypeExpr::no_tchk() const { } TypeExpr* TypeExpr::mk_intconst(const src::SrcLocation& loc, unsigned int_const) { - return new TypeExpr{loc, te_IntConst, (int)int_const}; + return new (AR) TypeExpr{loc, te_IntConst, (int)int_const}; } TypeExpr* TypeExpr::mk_intconst(const src::SrcLocation& loc, std::string int_const) { @@ -951,16 +953,16 @@ TypeExpr* TypeExpr::mk_mulint(const src::SrcLocation& loc, TypeExpr* expr1, Type return expr2; } // delete expr2; - return new TypeExpr{loc, te_MulConst, val, {expr1}, expr1->negated}; + return new (AR) TypeExpr{loc, te_MulConst, val, {expr1}, expr1->negated}; } TypeExpr* TypeExpr::mk_apply(const src::SrcLocation& loc, int tp, TypeExpr* expr1, TypeExpr* expr2) { - TypeExpr* expr = new TypeExpr{loc, tp, 0, {expr1, expr2}}; + TypeExpr* expr = new (AR) TypeExpr{loc, tp, 0, {expr1, expr2}}; return expr; } TypeExpr* TypeExpr::mk_cellref(const src::SrcLocation& loc, TypeExpr* expr1) { - TypeExpr* expr = new TypeExpr{loc, te_Ref, 0, {expr1}}; + TypeExpr* expr = new (AR) TypeExpr{loc, te_Ref, 0, {expr1}}; return expr; } @@ -1046,7 +1048,7 @@ bool TypeExpr::close(const src::SrcLocation& loc) { } TypeExpr* TypeExpr::mk_apply_empty(const src::SrcLocation& loc, sym_idx_t name, Type* type_applied) { - TypeExpr* expr = new TypeExpr{loc, te_Apply, name}; + TypeExpr* expr = new (AR) TypeExpr{loc, te_Apply, name}; expr->type_applied = type_applied; expr->is_nat_subtype = (type_applied->produces_nat && !type_applied->arity); return expr; @@ -1984,7 +1986,7 @@ void parse_field_list(Lexer& lex, Constructor& cs); TypeExpr* parse_anonymous_constructor(Lexer& lex, Constructor& cs) { sym::open_scope(lex); - Constructor* cs2 = new Constructor(lex.cur().loc); // anonymous constructor + Constructor* cs2 = new (AR) Constructor(lex.cur().loc); // anonymous constructor parse_field_list(lex, *cs2); if (lex.tp() != ']') { lex.expect(']'); @@ -1996,7 +1998,7 @@ TypeExpr* parse_anonymous_constructor(Lexer& lex, Constructor& cs) { if (types[i].parent_type_idx >= 0) { types[i].parent_type_idx = -2; } - delete cs2; + cs2->~Constructor(); return TypeExpr::mk_apply_empty(lex.cur().loc, 0, &types[i]); } } @@ -2089,7 +2091,7 @@ TypeExpr* parse_term(Lexer& lex, Constructor& cs, int mode) { } int i = sym_val->idx; assert(i >= 0 && i < cs.fields_num); - auto res = new TypeExpr{lex.cur().loc, TypeExpr::te_Param, i}; + auto res = new (AR) TypeExpr{lex.cur().loc, TypeExpr::te_Param, i}; auto field_type = cs.fields[i].type; assert(field_type); if ((mode & 4) && !cs.fields[i].known) { @@ -2250,11 +2252,9 @@ TypeExpr* parse_expr10(Lexer& lex, Constructor& cs, int mode) { } if (op == '>') { std::swap(expr, expr2); - op = '<'; op_name = Less_name; } else if (op == src::_Geq) { std::swap(expr, expr2); - op = src::_Leq; op_name = Leq_name; } auto sym_def = sym::lookup_symbol(op_name, 2); @@ -2345,7 +2345,7 @@ void parse_constructor_def(Lexer& lex) { } //std::cerr << "parsing constructor `" << sym::symbols.get_name(constr_name) << "` with tag " << std::hex << tag // << std::dec << std::endl; - auto cs_ref = new Constructor(where, constr_name, 0, tag); + auto cs_ref = new (AR) Constructor(where, constr_name, 0, tag); Constructor& cs = *cs_ref; cs.is_special = is_special; parse_field_list(lex, cs); @@ -2417,9 +2417,11 @@ void parse_constructor_def(Lexer& lex) { * */ -bool parse_source(std::istream* is, const src::FileDescr* fdescr) { +std::vector source_fdescr; + +bool parse_source(std::istream* is, src::FileDescr* fdescr) { src::SourceReader reader{is, fdescr}; - src::Lexer lex{reader, true, "(){}:;? #$. ^~ #", "//", "/*", "*/"}; + src::Lexer lex{reader, true, "(){}:;? #$. ^~ #", "//", "/*", "*/", ""}; while (lex.tp() != src::_Eof) { parse_constructor_def(lex); // std::cerr << lex.cur().str << '\t' << lex.cur().name_str() << std::endl; @@ -2432,6 +2434,7 @@ bool parse_source_file(const char* filename) { throw src::Fatal{"source file name is an empty string"}; } src::FileDescr* cur_source = new src::FileDescr{filename}; + source_fdescr.push_back(cur_source); std::ifstream ifs{filename}; if (ifs.fail()) { throw src::Fatal{std::string{"cannot open source file `"} + filename + "`"}; @@ -2440,7 +2443,9 @@ bool parse_source_file(const char* filename) { } bool parse_source_stdin() { - return parse_source(&std::cin, new src::FileDescr{"stdin", true}); + src::FileDescr* cur_source = new src::FileDescr{"stdin", true}; + source_fdescr.push_back(cur_source); + return parse_source(&std::cin, cur_source); } /* @@ -2466,7 +2471,7 @@ Type* define_builtin_type(std::string name_str, std::string args, bool produces_ } auto sym_def = sym::define_global_symbol(name, true); assert(sym_def); - sym_def->value = new SymValType{type}; + sym_def->value = new (AR) SymValType{type}; if (size < 0) { type->size = MinMaxSize::Any; } else if (min_size >= 0 && min_size != size) { diff --git a/crypto/tl/tlblib.cpp b/crypto/tl/tlblib.cpp index dd277cbc..0e0e5626 100644 --- a/crypto/tl/tlblib.cpp +++ b/crypto/tl/tlblib.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include @@ -114,46 +114,55 @@ bool TupleT::skip(vm::CellSlice& cs) const { return !i; } -bool TupleT::validate_skip(vm::CellSlice& cs, bool weak) const { +bool TupleT::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { int i = n; for (; i > 0; --i) { - if (!X.validate_skip(cs, weak)) { + if (!X.validate_skip(ops, cs, weak)) { break; } } return !i; } -bool TLB::validate_ref_internal(Ref cell_ref, bool weak) const { +bool TLB::validate_ref_internal(int* ops, Ref cell_ref, bool weak) const { + if (ops) { + if (*ops <= 0) { + return false; + } + --*ops; + } bool is_special; auto cs = load_cell_slice_special(std::move(cell_ref), is_special); - return always_special() ? is_special : (is_special ? weak : (validate_skip(cs) && cs.empty_ext())); + return always_special() ? is_special : (is_special ? weak : (validate_skip(ops, cs) && cs.empty_ext())); } bool TLB::print_skip(PrettyPrinter& pp, vm::CellSlice& cs) const { pp.open("raw@"); pp << *this << ' '; vm::CellSlice cs_copy{cs}; - if (!validate_skip(cs) || !cs_copy.cut_tail(cs)) { + int size_limit = pp.limit; + if (!validate_skip(&size_limit, cs) || !cs_copy.cut_tail(cs)) { return pp.fail("invalid value"); } pp.raw_nl(); - cs_copy.print_rec(pp.os, pp.indent); - return pp.mkindent() && pp.close(); + return (cs_copy.print_rec(pp.os, &pp.limit, pp.indent) && pp.mkindent() && pp.close()) || + pp.fail("raw value too long"); } bool TLB::print_special(PrettyPrinter& pp, vm::CellSlice& cs) const { pp.open("raw@"); pp << *this << ' '; pp.raw_nl(); - cs.print_rec(pp.os, pp.indent); - return pp.mkindent() && pp.close(); + return (cs.print_rec(pp.os, &pp.limit, pp.indent) && pp.mkindent() && pp.close()) || pp.fail("raw value too long"); } bool TLB::print_ref(PrettyPrinter& pp, Ref cell_ref) const { if (cell_ref.is_null()) { return pp.fail("null cell reference"); } + if (!pp.register_recursive_call()) { + return pp.fail("too many recursive calls while printing a TL-B value"); + } bool is_special; auto cs = load_cell_slice_special(std::move(cell_ref), is_special); if (is_special) { @@ -163,18 +172,21 @@ bool TLB::print_ref(PrettyPrinter& pp, Ref cell_ref) const { } } -bool TLB::print_skip(std::ostream& os, vm::CellSlice& cs, int indent) const { +bool TLB::print_skip(std::ostream& os, vm::CellSlice& cs, int indent, int rec_limit) const { PrettyPrinter pp{os, indent}; + pp.set_limit(rec_limit); return pp.fail_unless(print_skip(pp, cs)); } -bool TLB::print(std::ostream& os, const vm::CellSlice& cs, int indent) const { +bool TLB::print(std::ostream& os, const vm::CellSlice& cs, int indent, int rec_limit) const { PrettyPrinter pp{os, indent}; + pp.set_limit(rec_limit); return pp.fail_unless(print(pp, cs)); } -bool TLB::print_ref(std::ostream& os, Ref cell_ref, int indent) const { +bool TLB::print_ref(std::ostream& os, Ref cell_ref, int indent, int rec_limit) const { PrettyPrinter pp{os, indent}; + pp.set_limit(rec_limit); return pp.fail_unless(print_ref(pp, std::move(cell_ref))); } @@ -213,7 +225,7 @@ PrettyPrinter::~PrettyPrinter() { } bool PrettyPrinter::fail(std::string msg) { - os << ""; + os << "" << std::endl; failed = true; return false; } @@ -366,4 +378,10 @@ const TLB* TypenameLookup::lookup(std::string str) const { return it != types.end() ? it->second : nullptr; } +const TLB* TypenameLookup::lookup(td::Slice str) const { + auto it = std::lower_bound(types.begin(), types.end(), str, + [](const auto& x, const auto& y) { return td::Slice(x.first) < y; }); + return it != types.end() && td::Slice(it->first) == str ? it->second : nullptr; +} + } // namespace tlb diff --git a/crypto/tl/tlblib.hpp b/crypto/tl/tlblib.hpp index 2f6794ec..a6350ece 100644 --- a/crypto/tl/tlblib.hpp +++ b/crypto/tl/tlblib.hpp @@ -14,12 +14,13 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include #include #include "vm/cellslice.h" +#include namespace tlb { @@ -30,6 +31,7 @@ struct PrettyPrinter; class TLB { public: + enum { default_validate_max_cells = 1024 }; virtual ~TLB() = default; virtual int get_size(const vm::CellSlice& cs) const { return -1; @@ -37,14 +39,26 @@ class TLB { virtual bool skip(vm::CellSlice& cs) const { return cs.skip_ext(get_size(cs)); } - virtual bool validate(const vm::CellSlice& cs, bool weak = false) const { + virtual bool validate(int* ops, const vm::CellSlice& cs, bool weak = false) const { return cs.have_ext(get_size(cs)); } - virtual bool validate_exact(const vm::CellSlice& cs, bool weak = false) const { + virtual bool validate_exact(int* ops, const vm::CellSlice& cs, bool weak = false) const { return (int)cs.size_ext() == get_size(cs); } + bool validate_upto(int ops, const vm::CellSlice& cs, bool weak = false) const { + return validate(&ops, cs, weak); + } + bool validate_exact_upto(int ops, const vm::CellSlice& cs, bool weak = false) const { + return validate_exact(&ops, cs, weak); + } + bool validate_csr(int* ops, Ref cs_ref, bool weak = false) const { + return cs_ref.not_null() && validate_skip_exact(ops, cs_ref.write(), weak); + } + bool validate_csr(int ops, Ref cs_ref, bool weak = false) const { + return validate_csr(&ops, std::move(cs_ref), weak); + } bool validate_csr(Ref cs_ref, bool weak = false) const { - return cs_ref.not_null() && validate_skip_exact(cs_ref.write(), weak); + return validate_csr(default_validate_max_cells, std::move(cs_ref), weak); } Ref fetch(vm::CellSlice& cs) const { return cs.fetch_subslice_ext(get_size(cs)); @@ -52,67 +66,71 @@ class TLB { Ref prefetch(const vm::CellSlice& cs) const { return cs.prefetch_subslice_ext(get_size(cs)); } - virtual Ref validate_fetch(vm::CellSlice& cs, bool weak = false) const { - return validate(cs, weak) ? cs.fetch_subslice_ext(get_size(cs)) : Ref{}; + virtual Ref validate_fetch(int* ops, vm::CellSlice& cs, bool weak = false) const { + return validate(ops, cs, weak) ? cs.fetch_subslice_ext(get_size(cs)) : Ref{}; } - virtual Ref validate_prefetch(const vm::CellSlice& cs, bool weak = false) const { - return validate(cs, weak) ? cs.prefetch_subslice_ext(get_size(cs)) : Ref{}; + virtual Ref validate_prefetch(int* ops, const vm::CellSlice& cs, bool weak = false) const { + return validate(ops, cs, weak) ? cs.prefetch_subslice_ext(get_size(cs)) : Ref{}; } bool fetch_to(vm::CellSlice& cs, Ref& res) const { return (res = fetch(cs)).not_null(); } - bool validate_fetch_to(vm::CellSlice& cs, Ref& res, bool weak = false) const { - return (res = validate_fetch(cs, weak)).not_null(); + bool validate_fetch_to(int* ops, vm::CellSlice& cs, Ref& res, bool weak = false) const { + return (res = validate_fetch(ops, cs, weak)).not_null(); } bool store_from(vm::CellBuilder& cb, Ref field) const { return field.not_null() && get_size(*field) == (int)field->size_ext() && cb.append_cellslice_bool(std::move(field)); } - bool validate_store_from(vm::CellBuilder& cb, Ref field, bool weak = false) const { + bool validate_store_from(int* ops, vm::CellBuilder& cb, Ref field, bool weak = false) const { if (field.is_null()) { return false; } vm::CellSlice cs{*field}; - return validate_skip(cs, weak) && cs.empty_ext() && cb.append_cellslice_bool(std::move(field)); + return validate_skip(ops, cs, weak) && cs.empty_ext() && cb.append_cellslice_bool(std::move(field)); } virtual bool extract(vm::CellSlice& cs) const { return cs.only_ext(get_size(cs)); } - virtual bool validate_extract(vm::CellSlice& cs, bool weak = false) const { - return validate(cs, weak) && extract(cs); + virtual bool validate_extract(int* ops, vm::CellSlice& cs, bool weak = false) const { + return validate(ops, cs, weak) && extract(cs); } int get_size_by_skip(const vm::CellSlice& cs) const { vm::CellSlice copy{cs}; return skip(copy) ? copy.subtract_base_ext(cs) : -1; } - virtual bool validate_skip(vm::CellSlice& cs, bool weak = false) const { - return validate(cs, weak) && skip(cs); + virtual bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const { + return validate(ops, cs, weak) && skip(cs); } - bool validate_skip_exact(vm::CellSlice& cs, bool weak = false) const { - return validate_skip(cs, weak) && cs.empty_ext(); + bool validate_skip_upto(int ops, vm::CellSlice& cs, bool weak = false) const { + return validate_skip(&ops, cs, weak); } - bool validate_by_skip(const vm::CellSlice& cs, bool weak = false) const { + bool validate_skip_exact(int* ops, vm::CellSlice& cs, bool weak = false) const { + return validate_skip(ops, cs, weak) && cs.empty_ext(); + } + bool validate_by_skip(int* ops, const vm::CellSlice& cs, bool weak = false) const { vm::CellSlice copy{cs}; - return validate_skip(copy, weak); + return validate_skip(ops, copy, weak); } - bool validate_by_skip_exact(const vm::CellSlice& cs, bool weak = false) const { + bool validate_by_skip_exact(int* ops, const vm::CellSlice& cs, bool weak = false) const { vm::CellSlice copy{cs}; - return validate_skip_exact(copy, weak); + return validate_skip_exact(ops, copy, weak); } bool extract_by_skip(vm::CellSlice& cs) const { vm::CellSlice copy{cs}; return skip(copy) && cs.cut_tail(copy); } - bool validate_extract_by_skip(vm::CellSlice& cs, bool weak = false) const { + bool validate_extract_by_skip(int* ops, vm::CellSlice& cs, bool weak = false) const { vm::CellSlice copy{cs}; - return validate_skip(copy, weak) && cs.cut_tail(copy); + return validate_skip(ops, copy, weak) && cs.cut_tail(copy); } - Ref validate_fetch_by_skip(vm::CellSlice& cs, bool weak = false) const { + Ref validate_fetch_by_skip(int* ops, vm::CellSlice& cs, bool weak = false) const { Ref copy{true, cs}; - return validate_skip(cs, weak) && copy.unique_write().cut_tail(cs) ? copy : Ref{}; + return validate_skip(ops, cs, weak) && copy.unique_write().cut_tail(cs) ? copy : Ref{}; } - Ref validate_prefetch_by_skip(const vm::CellSlice& cs, bool weak = false) const { + Ref validate_prefetch_by_skip(int* ops, const vm::CellSlice& cs, bool weak = false) const { vm::CellSlice copy{cs}; - return validate_skip(copy, false) ? cs.prefetch_subslice_ext(copy.subtract_base_ext(cs)) : Ref{}; + return validate_skip(ops, copy, false) ? cs.prefetch_subslice_ext(copy.subtract_base_ext(cs)) + : Ref{}; } virtual bool skip_copy(vm::CellBuilder& cb, vm::CellSlice& cs) const { return cb.append_cellslice_bool(fetch(cs)); @@ -156,14 +174,29 @@ class TLB { bool as_integer_to(Ref cs_ref, td::RefInt256& res) const { return (res = as_integer(std::move(cs_ref))).not_null(); } + bool validate_ref(int* ops, Ref cell_ref, bool weak = false) const { + return cell_ref.not_null() && validate_ref_internal(ops, std::move(cell_ref), weak); + } + bool validate_ref(int ops, Ref cell_ref, bool weak = false) const { + return validate_ref(&ops, std::move(cell_ref), weak); + } bool validate_ref(Ref cell_ref, bool weak = false) const { - return cell_ref.not_null() && validate_ref_internal(std::move(cell_ref), weak); + return validate_ref(default_validate_max_cells, std::move(cell_ref), weak); + } + bool force_validate_ref(int* ops, Ref cell_ref) const { + return cell_ref.not_null() && validate_ref_internal(ops, std::move(cell_ref), false); + } + bool force_validate_ref(int ops, Ref cell_ref) const { + return force_validate_ref(&ops, std::move(cell_ref)); } bool force_validate_ref(Ref cell_ref) const { - return cell_ref.not_null() && validate_ref_internal(std::move(cell_ref), false); + return force_validate_ref(default_validate_max_cells, std::move(cell_ref)); } - bool validate_skip_ref(vm::CellSlice& cs, bool weak = false) const { - return validate_ref(cs.fetch_ref(), weak); + bool validate_skip_ref(int* ops, vm::CellSlice& cs, bool weak = false) const { + return validate_ref(ops, cs.fetch_ref(), weak); + } + bool validate_skip_ref(int ops, vm::CellSlice& cs, bool weak = false) const { + return validate_skip_ref(&ops, cs, weak); } virtual bool null_value(vm::CellBuilder& cb) const { return false; @@ -208,12 +241,15 @@ class TLB { bool print(PrettyPrinter& pp, Ref cs_ref) const { return print(pp, *cs_ref); } - bool print_skip(std::ostream& os, vm::CellSlice& cs, int indent = 0) const; - bool print(std::ostream& os, const vm::CellSlice& cs, int indent = 0) const; - bool print(std::ostream& os, Ref cs_ref, int indent = 0) const { - return print(os, *cs_ref, indent); + bool print_skip(std::ostream& os, vm::CellSlice& cs, int indent = 0, int rec_limit = 0) const; + bool print(std::ostream& os, const vm::CellSlice& cs, int indent = 0, int rec_limit = 0) const; + bool print(std::ostream& os, Ref cs_ref, int indent = 0, int rec_limit = 0) const { + return print(os, *cs_ref, indent, rec_limit); + } + bool print_ref(std::ostream& os, Ref cell_ref, int indent = 0, int rec_limit = 0) const; + bool print_ref(int rec_limit, std::ostream& os, Ref cell_ref, int indent = 0) const { + return print_ref(os, std::move(cell_ref), indent, rec_limit); } - bool print_ref(std::ostream& os, Ref cell_ref, int indent = 0) const; std::string as_string_skip(vm::CellSlice& cs, int indent = 0) const; std::string as_string(const vm::CellSlice& cs, int indent = 0) const; std::string as_string(Ref cs_ref, int indent = 0) const { @@ -225,7 +261,7 @@ class TLB { } protected: - bool validate_ref_internal(Ref cell_ref, bool weak = false) const; + bool validate_ref_internal(int* ops, Ref cell_ref, bool weak = false) const; }; static inline std::ostream& operator<<(std::ostream& os, const TLB& type) { @@ -234,29 +270,29 @@ static inline std::ostream& operator<<(std::ostream& os, const TLB& type) { struct TLB_Complex : TLB { bool skip(vm::CellSlice& cs) const override { - return validate_skip(cs); + return validate_skip(nullptr, cs); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override = 0; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override = 0; int get_size(const vm::CellSlice& cs) const override { return get_size_by_skip(cs); } - bool validate(const vm::CellSlice& cs, bool weak = false) const override { - return validate_by_skip(cs, weak); + bool validate(int* ops, const vm::CellSlice& cs, bool weak = false) const override { + return validate_by_skip(ops, cs, weak); } - bool validate_exact(const vm::CellSlice& cs, bool weak = false) const override { - return validate_by_skip_exact(cs, weak); + bool validate_exact(int* ops, const vm::CellSlice& cs, bool weak = false) const override { + return validate_by_skip_exact(ops, cs, weak); } bool extract(vm::CellSlice& cs) const override { return extract_by_skip(cs); } - bool validate_extract(vm::CellSlice& cs, bool weak = false) const override { - return validate_extract_by_skip(cs, weak); + bool validate_extract(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return validate_extract_by_skip(ops, cs, weak); } - Ref validate_fetch(vm::CellSlice& cs, bool weak = false) const override { - return validate_fetch_by_skip(cs, weak); + Ref validate_fetch(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return validate_fetch_by_skip(ops, cs, weak); } - Ref validate_prefetch(const vm::CellSlice& cs, bool weak = false) const override { - return validate_prefetch_by_skip(cs, weak); + Ref validate_prefetch(int* ops, const vm::CellSlice& cs, bool weak = false) const override { + return validate_prefetch_by_skip(ops, cs, weak); } td::RefInt256 as_integer(const vm::CellSlice& cs) const override { vm::CellSlice copy{cs}; @@ -456,15 +492,20 @@ bool store_from(vm::CellBuilder& cb, const T& tlb_type, Ref field namespace tlb { struct PrettyPrinter { + enum { default_print_limit = 4096 }; std::ostream& os; int indent; int level; bool failed; bool nl_used; int mode; + int limit{default_print_limit}; PrettyPrinter(std::ostream& _os, int _indent = 0, int _mode = 1) : os(_os), indent(_indent), level(0), failed(false), nl_used(false), mode(_mode) { } + PrettyPrinter(int _limit, std::ostream& _os, int _indent = 0, int _mode = 1) + : os(_os), indent(_indent), level(0), failed(false), nl_used(false), mode(_mode), limit(_limit) { + } ~PrettyPrinter(); bool ok() const { return !failed && !level; @@ -489,6 +530,14 @@ struct PrettyPrinter { bool field_int(long long value, std::string name); bool field_uint(unsigned long long value); bool field_uint(unsigned long long value, std::string name); + bool register_recursive_call() { + return limit--; + } + void set_limit(int new_limit) { + if (new_limit > 0) { + limit = new_limit; + } + } bool out(std::string str) { os << str; return true; @@ -546,6 +595,7 @@ class TypenameLookup { bool register_type(const char* name, const TLB* tp); bool register_types(register_func_t func); const TLB* lookup(std::string str) const; + const TLB* lookup(td::Slice str) const; }; } // namespace tlb @@ -602,23 +652,23 @@ struct FwdT final : TLB { bool skip(vm::CellSlice& cs) const override { return X.skip(cs); } - bool validate(const vm::CellSlice& cs, bool weak = false) const override { - return X.validate(cs, weak); + bool validate(int* ops, const vm::CellSlice& cs, bool weak = false) const override { + return X.validate(ops, cs, weak); } - Ref validate_fetch(vm::CellSlice& cs, bool weak = false) const override { - return X.validate_fetch(cs, weak); + Ref validate_fetch(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return X.validate_fetch(ops, cs, weak); } - Ref validate_prefetch(const vm::CellSlice& cs, bool weak = false) const override { - return X.validate_prefetch(cs, weak); + Ref validate_prefetch(int* ops, const vm::CellSlice& cs, bool weak = false) const override { + return X.validate_prefetch(ops, cs, weak); } bool extract(vm::CellSlice& cs) const override { return X.extract(cs); } - bool validate_extract(vm::CellSlice& cs, bool weak = false) const override { - return X.validate_extract(cs, weak); + bool validate_extract(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return X.validate_extract(ops, cs, weak); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { - return X.validate_skip(cs, weak); + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return X.validate_skip(ops, cs, weak); } bool skip_copy(vm::CellBuilder& cb, vm::CellSlice& cs) const override { return X.skip_copy(cb, cs); @@ -713,10 +763,10 @@ struct NatLess final : TLB { int get_size(const vm::CellSlice& cs) const override { return n >= 0 ? w : -1; } - bool validate(const vm::CellSlice& cs, bool weak = false) const override { + bool validate(int* ops, const vm::CellSlice& cs, bool weak = false) const override { return n >= 0 && (unsigned)cs.prefetch_ulong(w) <= (unsigned)n; } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { return n >= 0 && (unsigned)cs.fetch_ulong(w) <= (unsigned)n; } unsigned long long as_uint(const vm::CellSlice& cs) const override { @@ -736,10 +786,10 @@ struct NatLeq final : TLB { int get_size(const vm::CellSlice& cs) const override { return n >= 0 ? w : -1; } - bool validate(const vm::CellSlice& cs, bool weak = false) const override { + bool validate(int* ops, const vm::CellSlice& cs, bool weak = false) const override { return n >= 0 && (unsigned)cs.prefetch_ulong(w) <= (unsigned)n; } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { return n >= 0 && (unsigned)cs.fetch_ulong(w) <= (unsigned)n; } unsigned long long as_uint(const vm::CellSlice& cs) const override { @@ -758,7 +808,7 @@ struct TupleT final : TLB_Complex { TupleT(int _n, const TLB& _X) : n(_n), X(_X) { } bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { return 0; } @@ -773,8 +823,8 @@ struct CondT final : TLB_Complex { bool skip(vm::CellSlice& cs) const override { return !n || X.skip(cs); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { - return !n || (n > 0 && X.validate_skip(cs, weak)); + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return !n || (n > 0 && X.validate_skip(ops, cs, weak)); } int get_tag(const vm::CellSlice& cs) const override { return 0; @@ -795,8 +845,8 @@ struct Cond final : TLB_Complex { bool skip(vm::CellSlice& cs) const override { return !n || field_type.skip(cs); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { - return !n || (n > 0 && field_type.validate_skip(cs, weak)); + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return !n || (n > 0 && field_type.validate_skip(ops, cs, weak)); } int get_tag(const vm::CellSlice& cs) const override { return 0; @@ -892,7 +942,7 @@ struct Maybe : TLB_Complex { Maybe(Args... args) : field_type(args...) { } bool skip(vm::CellSlice& cs) const override; - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; int get_tag(const vm::CellSlice& cs) const override { return cs.have(1) ? (int)cs.prefetch_ulong(1) : -1; } @@ -915,10 +965,10 @@ bool Maybe::skip(vm::CellSlice& cs) const { } template -bool Maybe::validate_skip(vm::CellSlice& cs, bool weak) const { +bool Maybe::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { int t = get_tag(cs); if (t > 0) { - return cs.advance(1) && field_type.validate_skip(cs, weak); + return cs.advance(1) && field_type.validate_skip(ops, cs, weak); } else if (!t) { return cs.advance(1); } else { @@ -966,11 +1016,11 @@ struct RefTo final : TLB { int get_size(const vm::CellSlice& cs) const override { return 0x10000; } - bool validate(const vm::CellSlice& cs, bool weak = false) const override { - return cs.size_refs() ? ref_type.validate_ref(cs.prefetch_ref(), weak) : false; + bool validate(int* ops, const vm::CellSlice& cs, bool weak = false) const override { + return cs.size_refs() ? ref_type.validate_ref(ops, cs.prefetch_ref(), weak) : false; } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { - return ref_type.validate_skip_ref(cs, weak); + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return ref_type.validate_skip_ref(ops, cs, weak); } std::ostream& print_type(std::ostream& os) const override { return os << '^' << ref_type; @@ -987,11 +1037,11 @@ struct RefT final : TLB { int get_size(const vm::CellSlice& cs) const override { return 0x10000; } - bool validate(const vm::CellSlice& cs, bool weak = false) const override { - return X.validate_ref(cs.prefetch_ref(), weak); + bool validate(int* ops, const vm::CellSlice& cs, bool weak = false) const override { + return X.validate_ref(ops, cs.prefetch_ref(), weak); } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { - return X.validate_skip_ref(cs, weak); + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return X.validate_skip_ref(ops, cs, weak); } std::ostream& print_type(std::ostream& os) const override { return os << '^' << X; @@ -1008,9 +1058,10 @@ struct Either final : TLB_Complex { bool skip(vm::CellSlice& cs) const override { return cs.have(1) ? (cs.fetch_ulong(1) ? right_type.skip(cs) : left_type.skip(cs)) : false; } - bool validate_skip(vm::CellSlice& cs, bool weak = false) const override { - return cs.have(1) ? (cs.fetch_ulong(1) ? right_type.validate_skip(cs, weak) : left_type.validate_skip(cs, weak)) - : false; + bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { + return cs.have(1) + ? (cs.fetch_ulong(1) ? right_type.validate_skip(ops, cs, weak) : left_type.validate_skip(ops, cs, weak)) + : false; } int get_tag(const vm::CellSlice& cs) const override { return (int)cs.prefetch_ulong(1); diff --git a/crypto/util/Miner.cpp b/crypto/util/Miner.cpp new file mode 100644 index 00000000..3e26fac6 --- /dev/null +++ b/crypto/util/Miner.cpp @@ -0,0 +1,129 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ +#include "Miner.h" + +#include "td/utils/Random.h" +#include "td/utils/misc.h" +#include "td/utils/crypto.h" +#include "td/utils/port/Clocks.h" +#include + +namespace ton { +#pragma pack(push, 1) +struct HData { + unsigned char op[4]; + signed char flags = -4; + unsigned char expire[4] = {}, myaddr[32] = {}, rdata1[32] = {}, pseed[16] = {}, rdata2[32] = {}; + void inc() { + for (long i = 31; !(rdata1[i] = ++(rdata2[i])); --i) { + } + } + void set_expire(unsigned x) { + for (int i = 3; i >= 0; --i) { + expire[i] = (x & 0xff); + x >>= 8; + } + } + + td::Slice as_slice() const { + return td::Slice(reinterpret_cast(this), sizeof(*this)); + } +}; + +struct HDataEnv { + unsigned char d1 = 0, d2 = sizeof(HData) * 2; + HData body; + + td::Slice as_slice() const { + return td::Slice(reinterpret_cast(this), sizeof(*this)); + } + + void init(const block::StdAddress& my_address, td::Slice seed) { + std::memcpy(body.myaddr, my_address.addr.data(), sizeof(body.myaddr)); + body.flags = static_cast(my_address.workchain * 4 + my_address.bounceable); + CHECK(seed.size() == 16); + std::memcpy(body.pseed, seed.data(), 16); + std::memcpy(body.op, "Mine", 4); + + td::Random::secure_bytes(body.rdata1, 32); + std::memcpy(body.rdata2, body.rdata1, 32); + } +}; + +static_assert(std::is_trivially_copyable::value, "HDataEnv must be a trivial type"); +#pragma pack(pop) + +td::optional Miner::run(const Options& options) { + HDataEnv H; + H.init(options.my_address, td::Slice(options.seed.data(), options.seed.size())); + + td::Slice data = H.as_slice(); + CHECK(data.size() == 123); + + constexpr size_t prefix_size = 72; + constexpr size_t guard_pos = prefix_size - (72 - 28); + CHECK(0 <= guard_pos && guard_pos < 32); + size_t got_prefix_size = (const unsigned char*)H.body.rdata1 + guard_pos + 1 - (const unsigned char*)&H; + CHECK(prefix_size == got_prefix_size); + + auto head = data.substr(0, prefix_size); + auto tail = data.substr(prefix_size); + + SHA256_CTX shactx1, shactx2; + std::array hash; + SHA256_Init(&shactx1); + auto guard = head.back(); + + td::int64 i = 0, i0 = 0; + for (; i < options.max_iterations; i++) { + if (!(i & 0xfffff) || head.back() != guard) { + if (options.token_) { + break; + } + if (options.hashes_computed) { + *options.hashes_computed += i - i0; + } + i0 = i; + if (options.expire_at && options.expire_at.value().is_in_past(td::Timestamp::now())) { + break; + } + H.body.set_expire((unsigned)td::Clocks::system() + 900); + guard = head.back(); + SHA256_Init(&shactx1); + SHA256_Update(&shactx1, head.ubegin(), head.size()); + } + shactx2 = shactx1; + SHA256_Update(&shactx2, tail.ubegin(), tail.size()); + SHA256_Final(hash.data(), &shactx2); + + if (memcmp(hash.data(), options.complexity.data(), 32) < 0) { + // FOUND + if (options.hashes_computed) { + *options.hashes_computed += i - i0; + } + return H.body.as_slice().str(); + } + H.body.inc(); + } + if (options.hashes_computed) { + *options.hashes_computed += i - i0; + } + return {}; +} +} // namespace ton diff --git a/crypto/util/Miner.h b/crypto/util/Miner.h new file mode 100644 index 00000000..f91a6686 --- /dev/null +++ b/crypto/util/Miner.h @@ -0,0 +1,43 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ +#pragma once + +#include "block/block.h" +#include "td/utils/CancellationToken.h" +#include "td/utils/optional.h" +#include "td/utils/Time.h" +#include +#include + +namespace ton { +class Miner { + public: + struct Options { + block::StdAddress my_address; + std::array seed; + std::array complexity; + td::optional expire_at; + td::int64 max_iterations = std::numeric_limits::max(); + std::atomic* hashes_computed{nullptr}; + td::CancellationToken token_; + }; + + static td::optional run(const Options& options); +}; +} // namespace ton diff --git a/crypto/util/pow-miner.cpp b/crypto/util/pow-miner.cpp new file mode 100644 index 00000000..c065fdc7 --- /dev/null +++ b/crypto/util/pow-miner.cpp @@ -0,0 +1,245 @@ +/* + This file is part of TON Blockchain source code. + + TON Blockchain is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + TON Blockchain 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TON Blockchain. If not, see . + + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement + from all source files in the program, then also delete it here. + + Copyright 2017-2020 Telegram Systems LLP +*/ +#include "common/bigint.hpp" +#include "common/refint.h" +#include "block/block.h" +#include "td/utils/benchmark.h" +#include "td/utils/filesystem.h" +#include "vm/boc.h" +#include "openssl/digest.hpp" +#include +#include +#include +#include +#include +#include "git.h" +#include "Miner.h" + +const char* progname; + +int usage() { + std::cerr + << "usage: " << progname + << " [-v][-B][-w] [-t] [ " + "] [-V]\n" + "Outputs a valid value for proof-of-work testgiver after computing at most hashes " + "or terminates with non-zero exit code\n"; + std::exit(2); +} + +td::RefInt256 parse_bigint(std::string str, int bits) { + int len = (int)str.size(); + auto num = td::make_refint(); + auto& x = num.write(); + if (len >= 3 && str[0] == '0' && str[1] == 'x') { + if (x.parse_hex(str.data() + 2, len - 2) != len - 2) { + return {}; + } + } else if (!len || x.parse_dec(str.data(), len) != len) { + return {}; + } + return x.unsigned_fits_bits(bits) ? std::move(num) : td::RefInt256{}; +} + +td::RefInt256 parse_bigint_chk(std::string str, int bits) { + auto x = parse_bigint(std::move(str), bits); + if (x.is_null()) { + std::cerr << "fatal: `" << str << "` is not an integer" << std::endl; + usage(); + } + return x; +} + +void parse_addr(std::string str, block::StdAddress& addr) { + if (!addr.parse_addr(str) || (addr.workchain != -1 && addr.workchain != 0)) { + std::cerr << "fatal: `" << str.c_str() << "` is not a valid blockchain address" << std::endl; + usage(); + } +} + +bool make_boc = false; +std::string boc_filename; +block::StdAddress miner_address; + +int verbosity = 0; +std::atomic hashes_computed{0}; +td::Timestamp start_at; + +void print_stats() { + auto passed = td::Timestamp::now().at() - start_at.at(); + if (passed < 1e-9) { + passed = 1; + } + std::cerr << "[ hashes computed: " << hashes_computed << " ]" << std::endl; + std::cerr << "[ speed: " << static_cast(hashes_computed) / passed << " hps ]" << std::endl; +} + +int found(td::Slice data) { + for (unsigned i = 0; i < data.size(); i++) { + printf("%02X", data.ubegin()[i]); + } + printf("\n"); + if (make_boc) { + vm::CellBuilder cb; + td::Ref ext_msg, body; + CHECK(cb.store_bytes_bool(data) // body + && cb.finalize_to(body) // -> body + && cb.store_long_bool(0x44, 7) // ext_message_in$10 ... + && cb.store_long_bool(miner_address.workchain, 8) // workchain + && cb.store_bytes_bool(miner_address.addr.as_slice()) // miner addr + && cb.store_long_bool(1, 6) // amount:Grams ... + && cb.store_ref_bool(std::move(body)) // body:^Cell + && cb.finalize_to(ext_msg)); + auto boc = vm::std_boc_serialize(std::move(ext_msg), 2).move_as_ok(); + std::cerr << "Saving " << boc.size() << " bytes of serialized external message into file `" << boc_filename << "`" + << std::endl; + td::write_file(boc_filename, boc).ensure(); + } + if (verbosity > 0) { + print_stats(); + } + std::exit(0); + return 0; +} + +void miner(const ton::Miner::Options& options) { + auto res = ton::Miner::run(options); + if (res) { + found(res.value()); + } +} + +class MinerBench : public td::Benchmark { + public: + std::string get_description() const override { + return "Miner"; + } + + void run(int n) override { + ton::Miner::Options options; + options.my_address.parse_addr("EQDU86V5wyPrLd4nQ0RHPcCLPZq_y1O5wFWyTsMw63vjXTOv"); + std::fill(options.seed.begin(), options.seed.end(), 0xa7); + std::fill(options.complexity.begin(), options.complexity.end(), 0); + options.max_iterations = n; + CHECK(!ton::Miner::run(options)); + } +}; + +int main(int argc, char* const argv[]) { + ton::Miner::Options options; + + progname = argv[0]; + int i, threads = 0; + bool bounce = false, benchmark = false; + while ((i = getopt(argc, argv, "bnvw:t:Bh:V")) != -1) { + switch (i) { + case 'v': + ++verbosity; + break; + case 'w': + threads = atoi(optarg); + CHECK(threads > 0 && threads <= 256); + break; + case 't': { + int timeout = atoi(optarg); + CHECK(timeout > 0); + options.expire_at = td::Timestamp::in(timeout); + break; + } + case 'B': + benchmark = true; + break; + case 'b': + bounce = true; + break; + case 'n': + bounce = false; + break; + case 'V': + std::cout << "pow-miner build information: [ Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate() << "]\n"; + std::exit(0); + break; + case 'h': + return usage(); + default: + std::cerr << "unknown option" << std::endl; + return usage(); + } + } + if (benchmark && argc == optind) { + td::bench(MinerBench()); + return 0; + } + + if (argc != optind + 4 && argc != optind + 6) { + return usage(); + } + + parse_addr(argv[optind], options.my_address); + options.my_address.bounceable = bounce; + CHECK(parse_bigint_chk(argv[optind + 1], 128)->export_bytes(options.seed.data(), 16, false)); + + auto cmplx = parse_bigint_chk(argv[optind + 2], 256); + CHECK(cmplx->export_bytes(options.complexity.data(), 32, false)); + CHECK(!cmplx->unsigned_fits_bits(256 - 62)); + td::BigInt256 bigpower, hrate; + bigpower.set_pow2(256).mod_div(*cmplx, hrate); + long long hash_rate = hrate.to_long(); + options.max_iterations = parse_bigint_chk(argv[optind + 3], 50)->to_long(); + if (argc == optind + 6) { + make_boc = true; + parse_addr(argv[optind + 4], miner_address); + boc_filename = argv[optind + 5]; + } + + if (verbosity >= 2) { + std::cerr << "[ expected required hashes for success: " << hash_rate << " ]" << std::endl; + } + if (benchmark) { + td::bench(MinerBench()); + } + + start_at = td::Timestamp::now(); + + options.hashes_computed = &hashes_computed; + // may invoke several miner threads + if (threads <= 0) { + miner(options); + } else { + std::vector T; + for (int i = 0; i < threads; i++) { + T.emplace_back(miner, options); + } + for (auto& thr : T) { + thr.join(); + } + } + if (verbosity > 0) { + print_stats(); + } +} diff --git a/crypto/vm/Hasher.cpp b/crypto/vm/Hasher.cpp new file mode 100644 index 00000000..f70988d3 --- /dev/null +++ b/crypto/vm/Hasher.cpp @@ -0,0 +1,148 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#include "vm/Hasher.h" +#include "vm/excno.hpp" +#include "vm/vm.h" +#include +#include +#include "keccak/keccak.h" + +namespace vm { + +using td::Ref; + +class HasherImplEVP : public Hasher::HasherImpl { + public: + explicit HasherImplEVP(EVP_MD_CTX* ctx) : ctx_(ctx) { + } + + ~HasherImplEVP() override { + EVP_MD_CTX_free(ctx_); + } + + void append(const unsigned char *data, size_t size) override { + CHECK(EVP_DigestUpdate(ctx_, data, size)); + } + + td::BufferSlice finish() override { + td::BufferSlice hash(EVP_MD_CTX_size(ctx_)); + unsigned size; + CHECK(EVP_DigestFinal_ex(ctx_, (unsigned char *)hash.data(), &size) || size != hash.size()); + return hash; + } + + std::unique_ptr make_copy() const override { + EVP_MD_CTX *new_ctx = nullptr; + new_ctx = EVP_MD_CTX_new(); + CHECK(new_ctx != nullptr); + CHECK(EVP_MD_CTX_copy_ex(new_ctx, ctx_)); + return std::make_unique(new_ctx); + } + + private: + EVP_MD_CTX *ctx_; +}; + +class HasherImplKeccak : public Hasher::HasherImpl { + public: + explicit HasherImplKeccak(size_t hash_size) : hash_size_(hash_size) { + CHECK(keccak_init(&state_, hash_size * 2, 24) == 0); + CHECK(state_ != nullptr); + } + + ~HasherImplKeccak() override { + CHECK(keccak_destroy(state_) == 0); + } + + void append(const unsigned char *data, size_t size) override { + CHECK(keccak_absorb(state_, data, size) == 0); + } + + td::BufferSlice finish() override { + td::BufferSlice hash(hash_size_); + CHECK(keccak_digest(state_, (unsigned char*)hash.data(), hash_size_, 1) == 0); + return hash; + } + + std::unique_ptr make_copy() const override { + auto copy = std::make_unique(hash_size_); + CHECK(keccak_copy(state_, copy->state_) == 0); + return copy; + } + + private: + size_t hash_size_; + keccak_state *state_ = nullptr; +}; + +Hasher::Hasher(int hash_id) : id_(hash_id) { + if (hash_id == KECCAK256 || hash_id == KECCAK512) { + impl_ = std::make_unique(hash_id == KECCAK256 ? 32 : 64); + return; + } + + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + CHECK(ctx != nullptr); + const EVP_MD *evp; + switch (hash_id) { + case SHA256: evp = EVP_sha256(); break; + case SHA512: evp = EVP_sha512(); break; + case BLAKE2B: evp = EVP_blake2b512(); break; + default: + throw VmError{Excno::range_chk, "invalid hash id"}; + } + CHECK(evp != nullptr && EVP_DigestInit_ex(ctx, evp, nullptr)); + impl_ = std::make_unique(ctx); +} + +void Hasher::append(td::ConstBitPtr data, unsigned size) { + if (!impl_) { + throw VmError{Excno::unknown, "can't use finished hasher"}; + } + while (size > 0) { + unsigned cur_size = std::min(size, BUF_SIZE * 8 - buf_ptr_); + td::BitPtr{buf_, (int)buf_ptr_}.copy_from(data, cur_size); + buf_ptr_ += cur_size; + if (buf_ptr_ == BUF_SIZE * 8) { + impl_->append(buf_, BUF_SIZE); + buf_ptr_ = 0; + } + size -= cur_size; + data += cur_size; + } +} + +td::BufferSlice Hasher::finish() { + if (!impl_) { + throw VmError{Excno::unknown, "can't use finished hasher"}; + } + if (buf_ptr_ % 8 != 0) { + throw VmError{Excno::cell_und, "data does not consist of an integer number of bytes"}; + } + impl_->append(buf_, buf_ptr_ / 8); + td::BufferSlice hash = impl_->finish(); + impl_ = nullptr; + return hash; +} + +static const size_t BYTES_PER_GAS_UNIT[5] = {33, 16, 19, 11, 6}; + +size_t Hasher::bytes_per_gas_unit() const { + return BYTES_PER_GAS_UNIT[id_]; +} + +} diff --git a/crypto/vm/Hasher.h b/crypto/vm/Hasher.h new file mode 100644 index 00000000..7e441690 --- /dev/null +++ b/crypto/vm/Hasher.h @@ -0,0 +1,58 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#pragma once +#include "common/refcnt.hpp" +#include "td/utils/buffer.h" +#include "common/bitstring.h" +#include "vm/cells/Cell.h" +#include + +namespace vm { + +using td::Ref; + +class Hasher { + public: + explicit Hasher(int id); + Hasher(const Hasher&) = delete; + void append(td::ConstBitPtr data, unsigned size); + td::BufferSlice finish(); + size_t bytes_per_gas_unit() const; + + static const int SHA256 = 0; + static const int SHA512 = 1; + static const int BLAKE2B = 2; + static const int KECCAK256 = 3; + static const int KECCAK512 = 4; + + class HasherImpl { + public: + virtual ~HasherImpl() = default; + virtual void append(const unsigned char* data, size_t size) = 0; + virtual td::BufferSlice finish() = 0; + virtual std::unique_ptr make_copy() const = 0; + }; + + private: + int id_ = 0; + static const unsigned BUF_SIZE = 256; + unsigned char buf_[BUF_SIZE]; + unsigned buf_ptr_ = 0; + std::unique_ptr impl_; +}; + +} \ No newline at end of file diff --git a/crypto/vm/arithops.cpp b/crypto/vm/arithops.cpp index 19279cf1..fc482fc4 100644 --- a/crypto/vm/arithops.cpp +++ b/crypto/vm/arithops.cpp @@ -14,15 +14,15 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/arithops.h" #include "vm/log.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" #include "common/bigint.hpp" #include "common/refint.h" @@ -38,8 +38,8 @@ int exec_push_tinyint4(VmState* st, unsigned args) { std::string dump_push_tinyint4(CellSlice&, unsigned args) { int x = (int)((args + 5) & 15) - 5; - std::ostringstream os{"PUSHINT "}; - os << x; + std::ostringstream os; + os << "PUSHINT " << x; return os.str(); } @@ -53,8 +53,8 @@ int exec_push_tinyint8(VmState* st, unsigned args) { std::string dump_op_tinyint8(const char* op_prefix, CellSlice&, unsigned args) { int x = (signed char)args; - std::ostringstream os{op_prefix}; - os << x; + std::ostringstream os; + os << op_prefix << x; return os.str(); } @@ -68,8 +68,8 @@ int exec_push_smallint(VmState* st, unsigned args) { std::string dump_push_smallint(CellSlice&, unsigned args) { int x = (short)args; - std::ostringstream os{"PUSHINT "}; - os << x; + std::ostringstream os; + os << "PUSHINT " << x; return os.str(); } @@ -93,8 +93,8 @@ std::string dump_push_int(CellSlice& cs, unsigned args, int pfx_bits) { } cs.advance(pfx_bits); td::RefInt256 x = cs.fetch_int256(3 + l * 8); - std::ostringstream os{"PUSHINT "}; - os << x; + std::ostringstream os; + os << "PUSHINT " << x; return os.str(); } @@ -265,26 +265,45 @@ void register_add_mul_ops(OpcodeTable& cp0) { int exec_divmod(VmState* st, unsigned args, int quiet) { int round_mode = (int)(args & 3) - 1; - if (!(args & 12) || round_mode == 2) { + unsigned d = (args >> 2) & 3; + bool add = false; + if (d == 0 && st->get_global_version() >= 4) { + d = 3; + add = true; + } + if (d == 0 || round_mode == 2) { throw VmError{Excno::inv_opcode}; } Stack& stack = st->get_stack(); VM_LOG(st) << "execute DIV/MOD " << (args & 15); - stack.check_underflow(2); + stack.check_underflow(add ? 3 : 2); auto y = stack.pop_int(); + auto w = add ? stack.pop_int() : td::RefInt256{}; auto x = stack.pop_int(); - switch ((args >> 2) & 3) { - case 1: - stack.push_int_quiet(td::div(std::move(x), std::move(y), round_mode), quiet); - break; - case 2: - stack.push_int_quiet(td::mod(std::move(x), std::move(y), round_mode), quiet); - break; - case 3: { - auto dm = td::divmod(std::move(x), std::move(y), round_mode); - stack.push_int_quiet(std::move(dm.first), quiet); - stack.push_int_quiet(std::move(dm.second), quiet); - break; + if (add) { + CHECK(d == 3); + typename td::BigInt256::DoubleInt tmp{*x}, quot; + tmp += *w; + tmp.mod_div(*y, quot, round_mode); + auto q = td::make_refint(quot), r = td::make_refint(tmp); + q.write().normalize(); + r.write().normalize(); + stack.push_int_quiet(std::move(q), quiet); + stack.push_int_quiet(std::move(r), quiet); + } else { + switch (d) { + case 1: + stack.push_int_quiet(td::div(std::move(x), std::move(y), round_mode), quiet); + break; + case 2: + stack.push_int_quiet(td::mod(std::move(x), std::move(y), round_mode), quiet); + break; + case 3: { + auto dm = td::divmod(std::move(x), std::move(y), round_mode); + stack.push_int_quiet(std::move(dm.first), quiet); + stack.push_int_quiet(std::move(dm.second), quiet); + break; + } } } return 0; @@ -292,17 +311,26 @@ int exec_divmod(VmState* st, unsigned args, int quiet) { std::string dump_divmod(CellSlice&, unsigned args, bool quiet) { int round_mode = (int)(args & 3); - if (!(args & 12) || round_mode == 3) { + unsigned d = (args >> 2) & 3; + bool add = false; + if (d == 0) { + d = 3; + add = true; + } + if (round_mode == 3) { return ""; } - std::string s = (args & 4) ? "DIV" : ""; - if (args & 8) { + std::string s = add ? "ADD" : ""; + if (d & 1) { + s += "DIV"; + } + if (d & 2) { s += "MOD"; } if (quiet) { s = "Q" + s; } - return s + "FRC"[round_mode]; + return round_mode ? s + "FRC"[round_mode] : s; } int exec_shrmod(VmState* st, unsigned args, int mode) { @@ -312,32 +340,50 @@ int exec_shrmod(VmState* st, unsigned args, int mode) { args >>= 8; } int round_mode = (int)(args & 3) - 1; - if (!(args & 12) || round_mode == 2) { + unsigned d = (args >> 2) & 3; + bool add = false; + if (d == 0 && st->get_global_version() >= 4) { + d = 3; + add = true; + } + if (d == 0 || round_mode == 2) { throw VmError{Excno::inv_opcode}; } Stack& stack = st->get_stack(); VM_LOG(st) << "execute SHR/MOD " << (args & 15) << ',' << y; if (!(mode & 2)) { - stack.check_underflow(2); + stack.check_underflow(add ? 3 : 2); y = stack.pop_smallint_range(256); } else { - stack.check_underflow(1); + stack.check_underflow(add ? 2 : 1); } if (!y) { round_mode = -1; } + auto w = add ? stack.pop_int() : td::RefInt256{}; auto x = stack.pop_int(); - switch ((args >> 2) & 3) { - case 1: - stack.push_int_quiet(td::rshift(std::move(x), y, round_mode), mode & 1); - break; - case 3: - stack.push_int_quiet(td::rshift(x, y, round_mode), mode & 1); - // fallthrough - case 2: - x.write().mod_pow2(y, round_mode).normalize(); - stack.push_int_quiet(std::move(x), mode & 1); - break; + if (add) { + CHECK(d == 3); + typename td::BigInt256::DoubleInt tmp{*x}, quot; + tmp += *w; + typename td::BigInt256::DoubleInt tmp2{tmp}; + tmp2.rshift(y, round_mode).normalize(); + stack.push_int_quiet(td::make_refint(tmp2), mode & 1); + tmp.normalize().mod_pow2(y, round_mode).normalize(); + stack.push_int_quiet(td::make_refint(tmp), mode & 1); + } else { + switch (d) { + case 1: + stack.push_int_quiet(td::rshift(std::move(x), y, round_mode), mode & 1); + break; + case 3: + stack.push_int_quiet(td::rshift(x, y, round_mode), mode & 1); + // fallthrough + case 2: + x.write().mod_pow2(y, round_mode).normalize(); + stack.push_int_quiet(std::move(x), mode & 1); + break; + } } return 0; } @@ -349,59 +395,76 @@ std::string dump_shrmod(CellSlice&, unsigned args, int mode) { args >>= 8; } int round_mode = (int)(args & 3); - if (!(args & 12) || round_mode == 3) { + if (round_mode == 3) { return ""; } - std::string s; + std::ostringstream os; + if (mode & 1) { + os << 'Q'; + } + std::string end; switch (args & 12) { case 4: - s = "RSHIFT"; + os << "RSHIFT"; break; case 8: - s = "MODPOW2"; + os << "MODPOW2"; break; case 12: - s = "RSHIFTMOD"; + os << "RSHIFT"; + end = "MOD"; + break; + case 0: + os << "ADDRSHIFT"; + end = "MOD"; break; } - if (mode & 1) { - s = "Q" + s; + if (!(mode & 2)) { + os << end; + } + if (round_mode) { + os << "FRC"[round_mode]; } - s += "FRC"[round_mode]; if (mode & 2) { - char buff[8]; - sprintf(buff, " %d", y); - s += buff; + os << "#" << end << ' ' << y; } - return s; + return os.str(); } int exec_muldivmod(VmState* st, unsigned args, int quiet) { int round_mode = (int)(args & 3) - 1; - if (!(args & 12) || round_mode == 2) { + unsigned d = (args >> 2) & 3; + bool add = false; + if (d == 0 && st->get_global_version() >= 4) { + d = 3; + add = true; + } + if (d == 0 || round_mode == 2) { throw VmError{Excno::inv_opcode}; } Stack& stack = st->get_stack(); VM_LOG(st) << "execute MULDIV/MOD " << (args & 15); - stack.check_underflow(3); + stack.check_underflow(add ? 4 : 3); auto z = stack.pop_int(); + auto w = add ? stack.pop_int() : td::RefInt256{}; auto y = stack.pop_int(); auto x = stack.pop_int(); - typename td::BigInt256::DoubleInt tmp{0}; + typename td::BigInt256::DoubleInt tmp{0}, quot; + if (add) { + tmp = *w; + } tmp.add_mul(*x, *y); - auto q = td::RefInt256{true}; - tmp.mod_div(*z, q.unique_write(), round_mode); - switch ((args >> 2) & 3) { + auto q = td::make_refint(); + tmp.mod_div(*z, quot, round_mode); + switch (d) { case 1: - q.unique_write().normalize(); - stack.push_int_quiet(std::move(q), quiet); + stack.push_int_quiet(td::make_refint(quot.normalize()), quiet); break; case 3: - q.unique_write().normalize(); - stack.push_int_quiet(std::move(q), quiet); + stack.push_int_quiet(td::make_refint(quot.normalize()), quiet); // fallthrough case 2: - stack.push_int_quiet(td::RefInt256{true, tmp}, quiet); + stack.push_int_quiet(td::make_refint(tmp), quiet); break; } return 0; @@ -409,17 +472,26 @@ int exec_muldivmod(VmState* st, unsigned args, int quiet) { std::string dump_muldivmod(CellSlice&, unsigned args, bool quiet) { int round_mode = (int)(args & 3); - if (!(args & 12) || round_mode == 3) { + unsigned d = (args >> 2) & 3; + bool add = false; + if (d == 0) { + d = 3; + add = true; + } + if (round_mode == 3) { return ""; } - std::string s = (args & 4) ? "MULDIV" : "MUL"; - if (args & 8) { + std::string s = add ? "MULADD" : "MUL"; + if (d & 1) { + s += "DIV"; + } + if (d & 2) { s += "MOD"; } if (quiet) { s = "Q" + s; } - return s + "FRC"[round_mode]; + return round_mode ? s + "FRC"[round_mode] : s; } int exec_mulshrmod(VmState* st, unsigned args, int mode) { @@ -429,38 +501,48 @@ int exec_mulshrmod(VmState* st, unsigned args, int mode) { args >>= 8; } int round_mode = (int)(args & 3) - 1; - if (!(args & 12) || round_mode == 2) { + unsigned d = (args >> 2) & 3; + bool add = false; + if (d == 0 && st->get_global_version() >= 4) { + d = 3; + add = true; + } + if (d == 0 || round_mode == 2) { throw VmError{Excno::inv_opcode}; } Stack& stack = st->get_stack(); VM_LOG(st) << "execute MULSHR/MOD " << (args & 15) << ',' << z; if (!(mode & 2)) { - stack.check_underflow(3); + stack.check_underflow(add ? 4 : 3); z = stack.pop_smallint_range(256); } else { - stack.check_underflow(2); + stack.check_underflow(add ? 3 : 2); } if (!z) { round_mode = -1; } + auto w = add ? stack.pop_int() : td::RefInt256{}; auto y = stack.pop_int(); auto x = stack.pop_int(); typename td::BigInt256::DoubleInt tmp{0}; - tmp.add_mul(*x, *y); - switch ((args >> 2) & 3) { + if (add) { + tmp = *w; + } + tmp.add_mul(*x, *y).normalize(); + switch (d) { case 1: tmp.rshift(z, round_mode).normalize(); - stack.push_int_quiet(td::RefInt256{true, tmp}, mode & 1); + stack.push_int_quiet(td::make_refint(tmp), mode & 1); break; case 3: { typename td::BigInt256::DoubleInt tmp2{tmp}; tmp2.rshift(z, round_mode).normalize(); - stack.push_int_quiet(td::RefInt256{true, tmp2}, mode & 1); + stack.push_int_quiet(td::make_refint(tmp2), mode & 1); } // fallthrough case 2: - tmp.mod_pow2(z, round_mode).normalize(); - stack.push_int_quiet(td::RefInt256{true, tmp}, mode & 1); + tmp.normalize().mod_pow2(z, round_mode).normalize(); + stack.push_int_quiet(td::make_refint(tmp), mode & 1); break; } return 0; @@ -473,31 +555,41 @@ std::string dump_mulshrmod(CellSlice&, unsigned args, int mode) { args >>= 8; } int round_mode = (int)(args & 3); - if (!(args & 12) || round_mode == 3) { + if (round_mode == 3) { return ""; } - std::string s; + std::ostringstream os; + if (mode & 1) { + os << 'Q'; + } + std::string end; switch (args & 12) { case 4: - s = "MULRSHIFT"; + os << "MULRSHIFT"; break; case 8: - s = "MULMODPOW2"; + os << "MULMODPOW2"; break; case 12: - s = "MULRSHIFTMOD"; + os << "MULRSHIFT"; + end = "MOD"; + break; + case 0: + os << "MULADDRSHIFT"; + end = "MOD"; break; } - if (mode & 1) { - s = "Q" + s; + if (round_mode) { + os << "FRC"[round_mode]; } - s += "FRC"[round_mode]; if (mode & 2) { - char buff[8]; - sprintf(buff, " %d", y); - s += buff; + os << "#"; } - return s; + os << end; + if (mode & 2) { + os << ' ' << y; + } + return os.str(); } int exec_shldivmod(VmState* st, unsigned args, int mode) { @@ -507,60 +599,92 @@ int exec_shldivmod(VmState* st, unsigned args, int mode) { args >>= 8; } int round_mode = (int)(args & 3) - 1; - if (!(args & 12) || round_mode == 2) { + unsigned d = (args >> 2) & 3; + bool add = false; + if (d == 0 && st->get_global_version() >= 4) { + d = 3; + add = true; + } + if (d == 0 || round_mode == 2) { throw VmError{Excno::inv_opcode}; } Stack& stack = st->get_stack(); VM_LOG(st) << "execute SHLDIV/MOD " << (args & 15) << ',' << y; if (!(mode & 2)) { - stack.check_underflow(3); + stack.check_underflow(add ? 4 : 3); y = stack.pop_smallint_range(256); } else { - stack.check_underflow(2); + stack.check_underflow(add ? 3 : 2); } auto z = stack.pop_int(); + auto w = add ? stack.pop_int() : td::RefInt256{}; auto x = stack.pop_int(); - typename td::BigInt256::DoubleInt tmp{*x}; + typename td::BigInt256::DoubleInt tmp{*x}, quot; tmp <<= y; - switch ((args >> 2) & 3) { + if (add) { + tmp += *w; + } + switch (d) { case 1: { - auto q = td::RefInt256{true}; - tmp.mod_div(*z, q.unique_write(), round_mode); - q.unique_write().normalize(); - stack.push_int_quiet(std::move(q), mode & 1); + tmp.mod_div(*z, quot, round_mode); + stack.push_int_quiet(td::make_refint(quot.normalize()), mode & 1); break; } case 3: { - auto q = td::RefInt256{true}; - tmp.mod_div(*z, q.unique_write(), round_mode); - q.unique_write().normalize(); - stack.push_int_quiet(std::move(q), mode & 1); - stack.push_int_quiet(td::RefInt256{true, tmp}, mode & 1); + tmp.mod_div(*z, quot, round_mode); + stack.push_int_quiet(td::make_refint(quot.normalize()), mode & 1); + stack.push_int_quiet(td::make_refint(tmp), mode & 1); break; } case 2: { typename td::BigInt256::DoubleInt tmp2; tmp.mod_div(*z, tmp2, round_mode); - stack.push_int_quiet(td::RefInt256{true, tmp}, mode & 1); + stack.push_int_quiet(td::make_refint(tmp), mode & 1); break; } } return 0; } -std::string dump_shldivmod(CellSlice&, unsigned args, bool quiet) { +std::string dump_shldivmod(CellSlice&, unsigned args, int mode) { + int y = -1; + if (mode & 2) { + y = (args & 0xff) + 1; + args >>= 8; + } int round_mode = (int)(args & 3); - if (!(args & 12) || round_mode == 3) { + if (round_mode == 3) { return ""; } - std::string s = (args & 4) ? "LSHIFTDIV" : "LSHIFT"; - if (args & 8) { - s += "MOD"; + std::ostringstream os; + if (mode & 1) { + os << "Q"; } - if (quiet) { - s = "Q" + s; + os << "LSHIFT"; + if (mode & 2) { + os << "#"; } - return s + "FRC"[round_mode]; + switch (args & 12) { + case 4: + os << "DIV"; + break; + case 8: + os << "MOD"; + break; + case 12: + os << "DIVMOD"; + break; + case 0: + os << "ADDDIVMOD"; + break; + } + if (round_mode) { + os << "FRC"[round_mode]; + } + if (y >= 0) { + os << ' ' << y; + } + return os.str(); } void register_div_ops(OpcodeTable& cp0) { @@ -740,7 +864,7 @@ int exec_bitsize(VmState* st, bool sgnd, bool quiet) { } else if (!quiet) { throw VmError{Excno::range_chk, "CHKSIZE for negative integer"}; } else { - stack.push_int_quiet(td::RefInt256{true}, quiet); + stack.push_int_quiet(td::make_refint(), quiet); } return 0; } @@ -789,7 +913,7 @@ void register_shift_logic_ops(OpcodeTable& cp0) { int exec_minmax(VmState* st, int mode) { Stack& stack = st->get_stack(); - VM_LOG(st) << "execute MINMAXOP " << mode; + VM_LOG(st) << "execute " << (mode & 1 ? "Q" : "") << (mode & 2 ? "MIN" : "") << (mode & 4 ? "MAX" : ""); stack.check_underflow(2); auto x = stack.pop_int(); auto y = stack.pop_int(); @@ -811,7 +935,7 @@ int exec_minmax(VmState* st, int mode) { int exec_abs(VmState* st, bool quiet) { Stack& stack = st->get_stack(); - VM_LOG(st) << "execute ABS"; + VM_LOG(st) << "execute " << (quiet ? "QABS" : "ABS"); stack.check_underflow(1); auto x = stack.pop_int(); if (x->is_valid() && x->sgn() < 0) { @@ -855,7 +979,9 @@ int exec_cmp(VmState* st, int mode, bool quiet, const char* name) { auto y = stack.pop_int(); auto x = stack.pop_int(); if (!x->is_valid() || !y->is_valid()) { - stack.push_int_quiet(std::move(x), quiet); + td::RefInt256 r{true}; + r.unique_write().invalidate(); + stack.push_int_quiet(std::move(r), quiet); } else { int z = td::cmp(std::move(x), std::move(y)); stack.push_smallint(((mode >> (4 + z * 4)) & 15) - 8); @@ -943,4 +1069,26 @@ void register_arith_ops(OpcodeTable& cp0) { register_int_cmp_ops(cp0); } +namespace util { + +const td::RefInt256& check_signed_fits(const td::RefInt256& x, int bits) { + if (!x->signed_fits_bits(bits)) { + throw VmError{Excno::int_ov}; + } + return x; +} + +const td::RefInt256& check_unsigned_fits(const td::RefInt256& x, int bits) { + if (!x->unsigned_fits_bits(bits)) { + throw VmError{Excno::int_ov}; + } + return x; +} + +const td::RefInt256& check_finite(const td::RefInt256& x) { + return check_signed_fits(x, 257); +} + +} // namespace util + } // namespace vm diff --git a/crypto/vm/arithops.h b/crypto/vm/arithops.h index 1395d8b4..c9735798 100644 --- a/crypto/vm/arithops.h +++ b/crypto/vm/arithops.h @@ -14,14 +14,24 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once +#include "common/refint.h" namespace vm { class OpcodeTable; void register_arith_ops(OpcodeTable& cp0); +namespace util { + +// throw on error +const td::RefInt256& check_signed_fits(const td::RefInt256& x, int bits); +const td::RefInt256& check_unsigned_fits(const td::RefInt256& x, int bits); +const td::RefInt256& check_finite(const td::RefInt256& x); + +} // namespace util + } // namespace vm diff --git a/crypto/vm/atom.cpp b/crypto/vm/atom.cpp index 1b654fa1..a7d5486a 100644 --- a/crypto/vm/atom.cpp +++ b/crypto/vm/atom.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "atom.h" diff --git a/crypto/vm/atom.h b/crypto/vm/atom.h index 08e40632..9946a9ff 100644 --- a/crypto/vm/atom.h +++ b/crypto/vm/atom.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include diff --git a/crypto/vm/bls.cpp b/crypto/vm/bls.cpp new file mode 100644 index 00000000..f6ccc275 --- /dev/null +++ b/crypto/vm/bls.cpp @@ -0,0 +1,334 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ + +#include "bls.h" +#include "blst.h" +#include "blst.hpp" +#include "excno.hpp" + +namespace vm { +namespace bls { + +static const std::string DST = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; + +bool verify(const P1 &pub, td::Slice msg, const P2 &sig) { + try { + blst::P1_Affine p1(pub.data(), P1_SIZE); + if (p1.is_inf()) { + return false; + } + blst::P2_Affine p2(sig.data(), P2_SIZE); + // core_verify checks for p1.in_group() and p2.in_group() + return p2.core_verify(p1, true, (const byte *)msg.data(), msg.size(), DST) == BLST_SUCCESS; + } catch (BLST_ERROR) { + return false; + } +} + +P2 aggregate(const std::vector &sig) { + try { + if (sig.empty()) { + throw VmError{Excno::unknown, "no signatures"}; + } + blst::P2 aggregated; + for (size_t i = 0; i < sig.size(); ++i) { + blst::P2_Affine p2(sig[i].data(), P2_SIZE); + if (i == 0) { + aggregated = p2.to_jacobian(); + } else { + aggregated.aggregate(p2); + } + } + P2 result; + aggregated.compress(result.data()); + return result; + } catch (BLST_ERROR e) { + throw VmError{Excno::unknown, PSTRING() << "blst error " << e}; + } +} + +bool fast_aggregate_verify(const std::vector &pubs, td::Slice msg, const P2 &sig) { + try { + if (pubs.empty()) { + return false; + } + blst::P1 p1_aggregated; + for (size_t i = 0; i < pubs.size(); ++i) { + blst::P1_Affine p1(pubs[i].data(), P1_SIZE); + if (p1.is_inf()) { + return false; + } + if (i == 0) { + p1_aggregated = p1.to_jacobian(); + } else { + p1_aggregated.aggregate(p1); + } + } + blst::P2_Affine p2(sig.data(), P2_SIZE); + blst::P1_Affine p1 = p1_aggregated.to_affine(); + // core_verify checks for p1.in_group() and p2.in_group() + return p2.core_verify(p1, true, (const byte *)msg.data(), msg.size(), DST) == BLST_SUCCESS; + } catch (BLST_ERROR) { + return false; + } +} + +bool aggregate_verify(const std::vector> &pubs_msgs, const P2 &sig) { + try { + if (pubs_msgs.empty()) { + return false; + } + std::unique_ptr pairing = std::make_unique(true, DST); + for (const auto &p : pubs_msgs) { + blst::P1_Affine p1(p.first.data(), P1_SIZE); + if (!p1.in_group() || p1.is_inf()) { + return false; + } + pairing->aggregate(&p1, nullptr, (const td::uint8 *)p.second.data(), p.second.size()); + } + pairing->commit(); + blst::P2_Affine p2(sig.data(), P2_SIZE); + if (!p2.in_group()) { + return false; + } + blst::PT pt(p2); + return pairing->finalverify(&pt); + } catch (BLST_ERROR) { + return false; + } +} + +template +static P generic_add(const P &a, const P &b) { + try { + blst_P point(a.data(), a.size() / 8); + point.aggregate(blst_P_Affine(b.data(), b.size() / 8)); + P result; + point.compress(result.data()); + return result; + } catch (BLST_ERROR e) { + throw VmError{Excno::unknown, PSTRING() << "blst error " << e}; + } +} + +template +static P generic_sub(const P &a, const P &b) { + try { + blst_P point(b.data(), b.size() / 8); + point.neg(); + point.aggregate(blst_P_Affine(a.data(), a.size() / 8)); + P result; + point.compress(result.data()); + return result; + } catch (BLST_ERROR e) { + throw VmError{Excno::unknown, PSTRING() << "blst error " << e}; + } +} + +template +static P generic_neg(const P &a) { + try { + blst_P point(a.data(), a.size() / 8); + point.neg(); + P result; + point.compress(result.data()); + return result; + } catch (BLST_ERROR e) { + throw VmError{Excno::unknown, PSTRING() << "blst error " << e}; + } +} + +template +static P generic_zero() { + static P zero = []() -> P { + blst_P point = blst_P(); + P result; + point.compress(result.data()); + return result; + }(); + return zero; +} + +template +static P generic_mul(const P &p, const td::RefInt256 &x) { + CHECK(x.not_null() && x->is_valid()); + if (x->sgn() == 0) { + return generic_zero(); + } + td::uint8 x_bytes[32]; + CHECK((x % get_r())->export_bytes(x_bytes, 32, false)); + try { + blst_P point(p.data(), p.size() / 8); + blst::Scalar scalar; + scalar.from_bendian(x_bytes, 32); + point.mult(scalar); + P result; + point.compress(result.data()); + return result; + } catch (BLST_ERROR e) { + throw VmError{Excno::unknown, PSTRING() << "blst error " << e}; + } +} + +template +static P generic_multiexp(const std::vector> &ps) { + if (ps.size() == 1) { + return generic_mul(ps[0].first, ps[0].second); + } + try { + std::vector points(ps.size()); + std::vector scalars(ps.size()); + std::vector scalar_ptrs(ps.size()); + for (size_t i = 0; i < ps.size(); ++i) { + points[i] = blst_P_Affine(ps[i].first.data(), ps[i].first.size() / 8); + CHECK(ps[i].second.not_null() && ps[i].second->is_valid()); + CHECK((ps[i].second % get_r())->export_bytes_lsb(scalars[i].data(), 32)); + scalar_ptrs[i] = (const byte *)&scalars[i]; + } + blst_P point = + ps.empty() ? blst_P() : blst_P_Affines::mult_pippenger(points.data(), points.size(), scalar_ptrs.data(), 256); + P result; + point.compress(result.data()); + return result; + } catch (BLST_ERROR e) { + throw VmError{Excno::unknown, PSTRING() << "blst error " << e}; + } +} + +template +static bool generic_in_group(const P &a) { + try { + blst_P point = blst_P(a.data(), a.size() / 8); + return point.in_group(); + } catch (BLST_ERROR e) { + return false; + } +} + +template +static bool generic_is_zero(const P &a) { + return a == generic_zero(); +} + +P1 g1_add(const P1 &a, const P1 &b) { + return generic_add(a, b); +} + +P1 g1_sub(const P1 &a, const P1 &b) { + return generic_sub(a, b); +} + +P1 g1_neg(const P1 &a) { + return generic_neg(a); +} + +P1 g1_mul(const P1 &p, const td::RefInt256 &x) { + return generic_mul(p, x); +} + +P1 g1_multiexp(const std::vector> &ps) { + return generic_multiexp(ps); +} + +P1 g1_zero() { + return generic_zero(); +} + +P1 map_to_g1(const FP &a) { + blst_fp fp; + blst_fp_from_bendian(&fp, a.data()); + blst_p1 point; + blst_map_to_g1(&point, &fp, nullptr); + P1 result; + blst_p1_compress(result.data(), &point); + return result; +} + +bool g1_in_group(const P1 &a) { + return generic_in_group(a); +} + +bool g1_is_zero(const P1 &a) { + return generic_is_zero(a); +} + +P2 g2_add(const P2 &a, const P2 &b) { + return generic_add(a, b); +} + +P2 g2_sub(const P2 &a, const P2 &b) { + return generic_sub(a, b); +} + +P2 g2_neg(const P2 &a) { + return generic_neg(a); +} + +P2 g2_mul(const P2 &p, const td::RefInt256 &x) { + return generic_mul(p, x); +} + +P2 g2_multiexp(const std::vector> &ps) { + return generic_multiexp(ps); +} + +P2 g2_zero() { + return generic_zero(); +} + +P2 map_to_g2(const FP2 &a) { + blst_fp2 fp2; + blst_fp_from_bendian(&fp2.fp[0], a.data()); + blst_fp_from_bendian(&fp2.fp[1], a.data() + FP_SIZE); + blst_p2 point; + blst_map_to_g2(&point, &fp2, nullptr); + P2 result; + blst_p2_compress(result.data(), &point); + return result; +} + +bool g2_in_group(const P2 &a) { + return generic_in_group(a); +} + +bool g2_is_zero(const P2 &a) { + return generic_is_zero(a); +} + +bool pairing(const std::vector> &ps) { + try { + std::unique_ptr pairing = std::make_unique(true, DST); + for (const auto &p : ps) { + blst::P1_Affine point1(p.first.data(), P1_SIZE); + blst::P2_Affine point2(p.second.data(), P2_SIZE); + pairing->raw_aggregate(&point2, &point1); + } + pairing->commit(); + return pairing->finalverify(); + } catch (BLST_ERROR e) { + throw VmError{Excno::unknown, PSTRING() << "blst error " << e}; + } +} + +td::RefInt256 get_r() { + static td::RefInt256 r = td::dec_string_to_int256( + td::Slice{"52435875175126190479447740508185965837690552500527637822603658699938581184513"}); + return r; +} + +} // namespace bls +} // namespace vm diff --git a/crypto/vm/bls.h b/crypto/vm/bls.h new file mode 100644 index 00000000..b7ffc136 --- /dev/null +++ b/crypto/vm/bls.h @@ -0,0 +1,65 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ + +#include +#include "td/utils/buffer.h" +#include "common/bitstring.h" +#include "common/refint.h" + +namespace vm { +namespace bls { + +const size_t P1_SIZE = 48; +const size_t P2_SIZE = 96; +const size_t FP_SIZE = 48; + +using P1 = td::BitArray; +using P2 = td::BitArray; +using FP = td::BitArray; +using FP2 = td::BitArray; + +bool verify(const P1 &pub, td::Slice msg, const P2 &sig); +P2 aggregate(const std::vector &sig); +bool fast_aggregate_verify(const std::vector &pubs, td::Slice msg, const P2 &sig); +bool aggregate_verify(const std::vector> &pubs_msgs, const P2 &sig); + +P1 g1_add(const P1 &a, const P1 &b); +P1 g1_sub(const P1 &a, const P1 &b); +P1 g1_neg(const P1 &a); +P1 g1_mul(const P1 &p, const td::RefInt256 &x); +P1 g1_multiexp(const std::vector> &ps); +P1 g1_zero(); +P1 map_to_g1(const FP &a); +bool g1_in_group(const P1 &a); +bool g1_is_zero(const P1 &a); + +P2 g2_add(const P2 &a, const P2 &b); +P2 g2_sub(const P2 &a, const P2 &b); +P2 g2_neg(const P2 &a); +P2 g2_mul(const P2 &p, const td::RefInt256 &x); +P2 g2_multiexp(const std::vector> &ps); +P2 g2_zero(); +P2 map_to_g2(const FP2 &a); +bool g2_in_group(const P2 &a); +bool g2_is_zero(const P2 &a); + +bool pairing(const std::vector> &ps); + +td::RefInt256 get_r(); + +} // namespace bls +} // namespace vm diff --git a/crypto/vm/boc-writers.h b/crypto/vm/boc-writers.h new file mode 100644 index 00000000..e33886df --- /dev/null +++ b/crypto/vm/boc-writers.h @@ -0,0 +1,146 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#pragma once +#include "td/utils/port/FileFd.h" +#include "td/utils/crypto.h" +#include + +namespace vm { +namespace boc_writers { +struct BufferWriter { + BufferWriter(unsigned char* store_start, unsigned char* store_end) + : store_start(store_start), store_ptr(store_start), store_end(store_end) {} + + size_t position() const { + return store_ptr - store_start; + } + size_t remaining() const { + return store_end - store_ptr; + } + void chk() const { + DCHECK(store_ptr <= store_end); + } + bool empty() const { + return store_ptr == store_end; + } + void store_uint(unsigned long long value, unsigned bytes) { + unsigned char* ptr = store_ptr += bytes; + chk(); + while (bytes) { + *--ptr = value & 0xff; + value >>= 8; + --bytes; + } + DCHECK(!bytes); + } + void store_bytes(unsigned char const* data, size_t s) { + store_ptr += s; + chk(); + memcpy(store_ptr - s, data, s); + } + unsigned get_crc32() const { + return td::crc32c(td::Slice{store_start, store_ptr}); + } + + private: + unsigned char* store_start; + unsigned char* store_ptr; + unsigned char* store_end; +}; + +struct FileWriter { + FileWriter(td::FileFd& fd, size_t expected_size) + : fd(fd), expected_size(expected_size) {} + + ~FileWriter() { + flush(); + } + + size_t position() const { + return flushed_size + writer.position(); + } + size_t remaining() const { + return expected_size - position(); + } + void chk() const { + DCHECK(position() <= expected_size); + } + bool empty() const { + return remaining() == 0; + } + void store_uint(unsigned long long value, unsigned bytes) { + flush_if_needed(bytes); + writer.store_uint(value, bytes); + } + void store_bytes(unsigned char const* data, size_t s) { + flush_if_needed(s); + writer.store_bytes(data, s); + } + unsigned get_crc32() const { + unsigned char const* start = buf.data(); + unsigned char const* end = start + writer.position(); + return td::crc32c_extend(current_crc32, td::Slice(start, end)); + } + + td::Status finalize() { + flush(); + return std::move(res); + } + + private: + void flush_if_needed(size_t s) { + DCHECK(s <= BUF_SIZE); + if (s > BUF_SIZE - writer.position()) { + flush(); + } + } + + void flush() { + chk(); + unsigned char* start = buf.data(); + unsigned char* end = start + writer.position(); + if (start == end) { + return; + } + flushed_size += end - start; + current_crc32 = td::crc32c_extend(current_crc32, td::Slice(start, end)); + if (res.is_ok()) { + while (end > start) { + auto R = fd.write(td::Slice(start, end)); + if (R.is_error()) { + res = R.move_as_error(); + break; + } + size_t s = R.move_as_ok(); + start += s; + } + } + writer = BufferWriter(buf.data(), buf.data() + buf.size()); + } + + td::FileFd& fd; + size_t expected_size; + size_t flushed_size = 0; + unsigned current_crc32 = td::crc32c(td::Slice()); + + static const size_t BUF_SIZE = 1 << 22; + std::vector buf = std::vector(BUF_SIZE, '\0'); + BufferWriter writer = BufferWriter(buf.data(), buf.data() + buf.size()); + td::Status res = td::Status::OK(); +}; +} +} \ No newline at end of file diff --git a/crypto/vm/boc.cpp b/crypto/vm/boc.cpp index fbb39dfe..bd334cbf 100644 --- a/crypto/vm/boc.cpp +++ b/crypto/vm/boc.cpp @@ -14,18 +14,20 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include #include #include "vm/boc.h" +#include "vm/boc-writers.h" #include "vm/cells.h" #include "vm/cellslice.h" #include "td/utils/bits.h" -#include "td/utils/Slice-decl.h" -#include "td/utils/format.h" #include "td/utils/crypto.h" +#include "td/utils/format.h" +#include "td/utils/misc.h" +#include "td/utils/Slice-decl.h" namespace vm { using td::Ref; @@ -94,10 +96,8 @@ td::Result> CellSerializationInfo::create_data_cell(td::Slice cell for (int k = 0; k < refs_cnt; k++) { cb.store_ref(std::move(refs[k])); } - auto res = cb.finalize_novm(special); - if (res.is_null()) { - return td::Status::Error("CellBuilder::finalize failed"); - } + TRY_RESULT(res, cb.finalize_novm_nothrow(special)); + CHECK(!res.is_null()); if (res->is_special() != special) { return td::Status::Error("is_special mismatch"); } @@ -181,6 +181,7 @@ int BagOfCells::add_root(td::Ref add_root) { return 1; } +// Changes in this function may require corresponding changes in crypto/vm/large-boc-serializer.cpp td::Status BagOfCells::import_cells() { cells_clear(); for (auto& root : roots) { @@ -198,6 +199,7 @@ td::Status BagOfCells::import_cells() { return td::Status::OK(); } +// Changes in this function may require corresponding changes in crypto/vm/large-boc-serializer.cpp td::Result BagOfCells::import_cell(td::Ref cell, int depth) { if (depth > max_depth) { return td::Status::Error("error while importing a cell into a bag of cells: cell depth too large"); @@ -247,6 +249,7 @@ td::Result BagOfCells::import_cell(td::Ref cell, int depth) { return cell_count++; } +// Changes in this function may require corresponding changes in crypto/vm/large-boc-serializer.cpp void BagOfCells::reorder_cells() { int_hashes = 0; for (int i = cell_count - 1; i >= 0; --i) { @@ -324,6 +327,7 @@ void BagOfCells::reorder_cells() { // force=0 : previsit (recursively until special cells are found; then visit them) // force=1 : visit (allocate and process all children) // force=2 : allocate (assign a new index; can be run only after visiting) +// Changes in this function may require corresponding changes in crypto/vm/large-boc-serializer.cpp int BagOfCells::revisit(int cell_idx, int force) { DCHECK(cell_idx >= 0 && cell_idx < cell_count); CellInfo& dci = cell_list_[cell_idx]; @@ -370,13 +374,14 @@ int BagOfCells::revisit(int cell_idx, int force) { return dci.new_idx = -3; // mark as visited (and all children processed) } +// Changes in this function may require corresponding changes in crypto/vm/large-boc-serializer.cpp td::uint64 BagOfCells::compute_sizes(int mode, int& r_size, int& o_size) { int rs = 0, os = 0; if (!root_count || !data_bytes) { r_size = o_size = 0; return 0; } - while (cell_count >= (1 << (rs << 3))) { + while (cell_count >= (1LL << (rs << 3))) { rs++; } td::uint64 hashes = @@ -396,6 +401,7 @@ td::uint64 BagOfCells::compute_sizes(int mode, int& r_size, int& o_size) { return data_bytes_adj; } +// Changes in this function may require corresponding changes in crypto/vm/large-boc-serializer.cpp std::size_t BagOfCells::estimate_serialized_size(int mode) { if ((mode & Mode::WithCacheBits) && !(mode & Mode::WithIndex)) { info.invalidate(); @@ -476,17 +482,6 @@ std::string BagOfCells::extract_string() const { return std::string{serialized.data(), serialized.data() + serialized.size()}; } -void BagOfCells::store_uint(unsigned long long value, unsigned bytes) { - unsigned char* ptr = store_ptr += bytes; - store_chk(); - while (bytes) { - *--ptr = value & 0xff; - value >>= 8; - --bytes; - } - DCHECK(!bytes); -} - //serialized_boc#672fb0ac has_idx:(## 1) has_crc32c:(## 1) // has_cache_bits:(## 1) flags:(## 2) { flags = 0 } // size:(## 3) { size <= 4 } @@ -498,13 +493,17 @@ void BagOfCells::store_uint(unsigned long long value, unsigned bytes) { // index:(cells * ##(off_bytes * 8)) // cell_data:(tot_cells_size * [ uint8 ]) // = BagOfCells; -std::size_t BagOfCells::serialize_to(unsigned char* buffer, std::size_t buff_size, int mode) { - std::size_t size_est = estimate_serialized_size(mode); - if (!size_est || size_est > buff_size) { - return 0; - } - init_store(buffer, buffer + size_est); - store_uint(info.magic, 4); +// Changes in this function may require corresponding changes in crypto/vm/large-boc-serializer.cpp +template +std::size_t BagOfCells::serialize_to_impl(WriterT& writer, int mode) { + auto store_ref = [&](unsigned long long value) { + writer.store_uint(value, info.ref_byte_size); + }; + auto store_offset = [&](unsigned long long value) { + writer.store_uint(value, info.offset_byte_size); + }; + + writer.store_uint(info.magic, 4); td::uint8 byte{0}; if (info.has_index) { @@ -521,9 +520,9 @@ std::size_t BagOfCells::serialize_to(unsigned char* buffer, std::size_t buff_siz return 0; } byte |= static_cast(info.ref_byte_size); - store_uint(byte, 1); + writer.store_uint(byte, 1); - store_uint(info.offset_byte_size, 1); + writer.store_uint(info.offset_byte_size, 1); store_ref(cell_count); store_ref(root_count); store_ref(0); @@ -533,7 +532,7 @@ std::size_t BagOfCells::serialize_to(unsigned char* buffer, std::size_t buff_siz DCHECK(k >= 0 && k < cell_count); store_ref(k); } - DCHECK(store_ptr - buffer == (long long)info.index_offset); + DCHECK(writer.position() == info.index_offset); DCHECK((unsigned)cell_count == cell_list_.size()); if (info.has_index) { std::size_t offs = 0; @@ -552,8 +551,8 @@ std::size_t BagOfCells::serialize_to(unsigned char* buffer, std::size_t buff_siz } DCHECK(offs == info.data_size); } - DCHECK(store_ptr - buffer == (long long)info.data_offset); - unsigned char* keep_ptr = store_ptr; + DCHECK(writer.position() == info.data_offset); + size_t keep_position = writer.position(); for (int i = 0; i < cell_count; ++i) { const auto& dc_info = cell_list_[cell_count - 1 - i]; const Ref& dc = dc_info.dc_ref; @@ -561,9 +560,9 @@ std::size_t BagOfCells::serialize_to(unsigned char* buffer, std::size_t buff_siz if (dc_info.is_root_cell && (mode & Mode::WithTopHash)) { with_hash = true; } - int s = dc->serialize(store_ptr, 256, with_hash); - store_ptr += s; - store_chk(); + unsigned char buf[256]; + int s = dc->serialize(buf, 256, with_hash); + writer.store_bytes(buf, s); DCHECK(dc->size_refs() == dc_info.ref_num); // std::cerr << (dc_info.is_special() ? '*' : ' ') << i << '<' << (int)dc_info.wt << ">:"; for (unsigned j = 0; j < dc_info.ref_num; ++j) { @@ -574,16 +573,38 @@ std::size_t BagOfCells::serialize_to(unsigned char* buffer, std::size_t buff_siz } // std::cerr << std::endl; } - store_chk(); - DCHECK(store_ptr - keep_ptr == (long long)info.data_size); - DCHECK(store_end - store_ptr == (info.has_crc32c ? 4 : 0)); + writer.chk(); + DCHECK(writer.position() - keep_position == info.data_size); + DCHECK(writer.remaining() == (info.has_crc32c ? 4 : 0)); if (info.has_crc32c) { - // compute crc32c of buffer .. store_ptr - unsigned crc = td::crc32c(td::Slice{buffer, store_ptr}); - store_uint(td::bswap32(crc), 4); + unsigned crc = writer.get_crc32(); + writer.store_uint(td::bswap32(crc), 4); } - DCHECK(store_empty()); - return store_ptr - buffer; + DCHECK(writer.empty()); + return writer.position(); +} + +std::size_t BagOfCells::serialize_to(unsigned char* buffer, std::size_t buff_size, int mode) { + std::size_t size_est = estimate_serialized_size(mode); + if (!size_est || size_est > buff_size) { + return 0; + } + boc_writers::BufferWriter writer{buffer, buffer + size_est}; + return serialize_to_impl(writer, mode); +} + +td::Status BagOfCells::serialize_to_file(td::FileFd& fd, int mode) { + std::size_t size_est = estimate_serialized_size(mode); + if (!size_est) { + return td::Status::Error("no cells to serialize to this bag of cells"); + } + boc_writers::FileWriter writer{fd, size_est}; + size_t s = serialize_to_impl(writer, mode); + TRY_STATUS(writer.finalize()); + if (s != size_est) { + return td::Status::Error("error while serializing a bag of cells: actual serialized size differs from estimated"); + } + return td::Status::OK(); } unsigned long long BagOfCells::Info::read_int(const unsigned char* ptr, unsigned bytes) { @@ -654,10 +675,10 @@ long long BagOfCells::Info::parse_serialized_header(const td::Slice& slice) { ptr += 6; sz -= 6; if (sz < ref_byte_size) { - return -(int)roots_offset; + return -static_cast(roots_offset); } cell_count = (int)read_ref(ptr); - if (cell_count < 0) { + if (cell_count <= 0) { cell_count = -1; return 0; } @@ -671,7 +692,7 @@ long long BagOfCells::Info::parse_serialized_header(const td::Slice& slice) { } index_offset = roots_offset; if (magic == boc_generic) { - index_offset += root_count * ref_byte_size; + index_offset += (long long)root_count * ref_byte_size; has_roots = true; } else { if (root_count != 1) { @@ -690,12 +711,18 @@ long long BagOfCells::Info::parse_serialized_header(const td::Slice& slice) { return 0; } if (sz < 3 * ref_byte_size + offset_byte_size) { - return -(int)roots_offset; + return -static_cast(roots_offset); } data_size = read_offset(ptr + 3 * ref_byte_size); if (data_size > ((unsigned long long)cell_count << 10)) { return 0; } + if (data_size > (1ull << 40)) { + return 0; // bag of cells with more than 1TiB data is unlikely + } + if (data_size < cell_count * (2ull + ref_byte_size) - ref_byte_size) { + return 0; // invalid header, too many cells for this amount of data bytes + } valid = true; total_size = data_offset + data_size + (has_crc32c ? 4 : 0); return total_size; @@ -747,14 +774,13 @@ td::Result> BagOfCells::deserialize_cell(int idx, td::Slic return cell_info.create_data_cell(cell_slice, refs); } -td::Result BagOfCells::deserialize(const td::Slice& data) { +td::Result BagOfCells::deserialize(const td::Slice& data, int max_roots) { clear(); long long size_est = info.parse_serialized_header(data); //LOG(INFO) << "estimated size " << size_est << ", true size " << data.size(); if (size_est == 0) { return td::Status::Error(PSLICE() << "cannot deserialize bag-of-cells: invalid header, error " << size_est); } - if (size_est < 0) { //LOG(ERROR) << "cannot deserialize bag-of-cells: not enough bytes (" << data.size() << " present, " << -size_est //<< " required)"; @@ -767,6 +793,9 @@ td::Result BagOfCells::deserialize(const td::Slice& data) { return -size_est; } //LOG(INFO) << "estimated size " << size_est << ", true size " << data.size(); + if (info.root_count > max_roots) { + return td::Status::Error("Bag-of-cells has more root cells than expected"); + } if (info.has_crc32c) { unsigned crc_computed = td::crc32c(td::Slice{data.ubegin(), data.uend() - 4}); unsigned crc_stored = td::as(data.uend() - 4); @@ -901,12 +930,12 @@ unsigned long long BagOfCells::get_idx_entry_raw(int index) { * */ -td::Result> std_boc_deserialize(td::Slice data, bool can_be_empty) { +td::Result> std_boc_deserialize(td::Slice data, bool can_be_empty, bool allow_nonzero_level) { if (data.empty() && can_be_empty) { return Ref(); } BagOfCells boc; - auto res = boc.deserialize(data); + auto res = boc.deserialize(data, 1); if (res.is_error()) { return res.move_as_error(); } @@ -917,18 +946,18 @@ td::Result> std_boc_deserialize(td::Slice data, bool can_be_empty) { if (root.is_null()) { return td::Status::Error("bag of cells has null root cell (?)"); } - if (root->get_level() != 0) { + if (!allow_nonzero_level && root->get_level() != 0) { return td::Status::Error("bag of cells has a root with non-zero level"); } return std::move(root); } -td::Result>> std_boc_deserialize_multi(td::Slice data) { +td::Result>> std_boc_deserialize_multi(td::Slice data, int max_roots) { if (data.empty()) { return std::vector>{}; } BagOfCells boc; - auto res = boc.deserialize(data); + auto res = boc.deserialize(data, max_roots); if (res.is_error()) { return res.move_as_error(); } @@ -979,27 +1008,40 @@ td::Result std_boc_serialize_multi(std::vector> roots * */ -bool CellStorageStat::compute_used_storage(Ref cs_ref, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::compute_used_storage(Ref cs_ref, bool kill_dup, + unsigned skip_count_root) { clear(); - return add_used_storage(std::move(cs_ref), kill_dup, skip_count_root) && clear_seen(); + TRY_RESULT(res, add_used_storage(std::move(cs_ref), kill_dup, skip_count_root)); + clear_seen(); + return res; } -bool CellStorageStat::compute_used_storage(const CellSlice& cs, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::compute_used_storage(const CellSlice& cs, bool kill_dup, + unsigned skip_count_root) { clear(); - return add_used_storage(cs, kill_dup, skip_count_root) && clear_seen(); + TRY_RESULT(res, add_used_storage(cs, kill_dup, skip_count_root)); + clear_seen(); + return res; } -bool CellStorageStat::compute_used_storage(CellSlice&& cs, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::compute_used_storage(CellSlice&& cs, bool kill_dup, + unsigned skip_count_root) { clear(); - return add_used_storage(std::move(cs), kill_dup, skip_count_root) && clear_seen(); + TRY_RESULT(res, add_used_storage(std::move(cs), kill_dup, skip_count_root)); + clear_seen(); + return res; } -bool CellStorageStat::compute_used_storage(Ref cell, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::compute_used_storage(Ref cell, bool kill_dup, + unsigned skip_count_root) { clear(); - return add_used_storage(std::move(cell), kill_dup, skip_count_root) && clear_seen(); + TRY_RESULT(res, add_used_storage(std::move(cell), kill_dup, skip_count_root)); + clear_seen(); + return res; } -bool CellStorageStat::add_used_storage(Ref cs_ref, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::add_used_storage(Ref cs_ref, bool kill_dup, + unsigned skip_count_root) { if (cs_ref->is_unique()) { return add_used_storage(std::move(cs_ref.unique_write()), kill_dup, skip_count_root); } else { @@ -1007,44 +1049,67 @@ bool CellStorageStat::add_used_storage(Ref cs_ref, bool kill_dup, } } -bool CellStorageStat::add_used_storage(const CellSlice& cs, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::add_used_storage(const CellSlice& cs, bool kill_dup, + unsigned skip_count_root) { if (!(skip_count_root & 1)) { ++cells; + if (cells > limit_cells) { + return td::Status::Error("too many cells"); + } } if (!(skip_count_root & 2)) { bits += cs.size(); + if (bits > limit_bits) { + return td::Status::Error("too many bits"); + } } + CellInfo res; for (unsigned i = 0; i < cs.size_refs(); i++) { - if (!add_used_storage(cs.prefetch_ref(i), kill_dup)) { - return false; - } + TRY_RESULT(child, add_used_storage(cs.prefetch_ref(i), kill_dup)); + res.max_merkle_depth = std::max(res.max_merkle_depth, child.max_merkle_depth); } - return true; + if (cs.special_type() == CellTraits::SpecialType::MerkleProof || + cs.special_type() == CellTraits::SpecialType::MerkleUpdate) { + ++res.max_merkle_depth; + } + return res; } -bool CellStorageStat::add_used_storage(CellSlice&& cs, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::add_used_storage(CellSlice&& cs, bool kill_dup, + unsigned skip_count_root) { if (!(skip_count_root & 1)) { ++cells; + if (cells > limit_cells) { + return td::Status::Error("too many cells"); + } } if (!(skip_count_root & 2)) { bits += cs.size(); - } - while (cs.size_refs()) { - if (!add_used_storage(cs.fetch_ref(), kill_dup)) { - return false; + if (bits > limit_bits) { + return td::Status::Error("too many bits"); } } - return true; + CellInfo res; + while (cs.size_refs()) { + TRY_RESULT(child, add_used_storage(cs.fetch_ref(), kill_dup)); + res.max_merkle_depth = std::max(res.max_merkle_depth, child.max_merkle_depth); + } + if (cs.special_type() == CellTraits::SpecialType::MerkleProof || + cs.special_type() == CellTraits::SpecialType::MerkleUpdate) { + ++res.max_merkle_depth; + } + return res; } -bool CellStorageStat::add_used_storage(Ref cell, bool kill_dup, unsigned skip_count_root) { +td::Result CellStorageStat::add_used_storage(Ref cell, bool kill_dup, + unsigned skip_count_root) { if (cell.is_null()) { - return false; + return td::Status::Error("cell is null"); } if (kill_dup) { - auto ins = seen.insert(cell->get_hash()); + auto ins = seen.emplace(cell->get_hash(), CellInfo{}); if (!ins.second) { - return true; + return ins.first->second; } } vm::CellSlice cs{vm::NoVm{}, std::move(cell)}; @@ -1125,4 +1190,28 @@ void NewCellStorageStat::dfs(Ref cell, bool need_stat, bool need_proof_sta } } +bool VmStorageStat::add_storage(Ref cell) { + if (cell.is_null() || !check_visited(cell)) { + return true; + } + if (cells >= limit) { + return false; + } + ++cells; + bool special; + auto cs = load_cell_slice_special(std::move(cell), special); + return cs.is_valid() && add_storage(std::move(cs)); +} + +bool VmStorageStat::add_storage(const CellSlice& cs) { + bits += cs.size(); + refs += cs.size_refs(); + for (unsigned i = 0; i < cs.size_refs(); i++) { + if (!add_storage(cs.prefetch_ref(i))) { + return false; + } + } + return true; +} + } // namespace vm diff --git a/crypto/vm/boc.h b/crypto/vm/boc.h index 29f582be..09ae1b66 100644 --- a/crypto/vm/boc.h +++ b/crypto/vm/boc.h @@ -14,24 +14,22 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include +#include +#include "vm/db/DynamicBagOfCellsDb.h" #include "vm/cells.h" #include "td/utils/Status.h" #include "td/utils/buffer.h" #include "td/utils/HashMap.h" +#include "td/utils/HashSet.h" +#include "td/utils/port/FileFd.h" namespace vm { using td::Ref; -td::Result> std_boc_deserialize(td::Slice data, bool can_be_empty = false); -td::Result std_boc_serialize(Ref root, int mode = 0); - -td::Result>> std_boc_deserialize_multi(td::Slice data); -td::Result std_boc_serialize_multi(std::vector> root, int mode = 0); - class NewCellStorageStat { public: NewCellStorageStat() { @@ -54,6 +52,7 @@ class NewCellStorageStat { bool operator==(const Stat& other) const { return key() == other.key(); } + Stat(const Stat& other) = default; Stat& operator=(const Stat& other) = default; Stat& operator+=(const Stat& other) { cells += other.cells; @@ -111,26 +110,55 @@ struct CellStorageStat { unsigned long long cells; unsigned long long bits; unsigned long long public_cells; - std::set seen; + struct CellInfo { + td::uint32 max_merkle_depth = 0; + }; + std::map seen; CellStorageStat() : cells(0), bits(0), public_cells(0) { } - bool clear_seen() { + explicit CellStorageStat(unsigned long long limit_cells) + : cells(0), bits(0), public_cells(0), limit_cells(limit_cells) { + } + void clear_seen() { seen.clear(); - return true; } void clear() { cells = bits = public_cells = 0; + clear_limit(); clear_seen(); } - bool compute_used_storage(Ref cs_ref, bool kill_dup = true, unsigned skip_count_root = 0); - bool compute_used_storage(const CellSlice& cs, bool kill_dup = true, unsigned skip_count_root = 0); - bool compute_used_storage(CellSlice&& cs, bool kill_dup = true, unsigned skip_count_root = 0); - bool compute_used_storage(Ref cell, bool kill_dup = true, unsigned skip_count_root = 0); + void clear_limit() { + limit_cells = std::numeric_limits::max(); + limit_bits = std::numeric_limits::max(); + } + td::Result compute_used_storage(Ref cs_ref, bool kill_dup = true, + unsigned skip_count_root = 0); + td::Result compute_used_storage(const CellSlice& cs, bool kill_dup = true, unsigned skip_count_root = 0); + td::Result compute_used_storage(CellSlice&& cs, bool kill_dup = true, unsigned skip_count_root = 0); + td::Result compute_used_storage(Ref cell, bool kill_dup = true, unsigned skip_count_root = 0); - bool add_used_storage(Ref cs_ref, bool kill_dup = true, unsigned skip_count_root = 0); - bool add_used_storage(const CellSlice& cs, bool kill_dup = true, unsigned skip_count_root = 0); - bool add_used_storage(CellSlice&& cs, bool kill_dup = true, unsigned skip_count_root = 0); - bool add_used_storage(Ref cell, bool kill_dup = true, unsigned skip_count_root = 0); + td::Result add_used_storage(Ref cs_ref, bool kill_dup = true, unsigned skip_count_root = 0); + td::Result add_used_storage(const CellSlice& cs, bool kill_dup = true, unsigned skip_count_root = 0); + td::Result add_used_storage(CellSlice&& cs, bool kill_dup = true, unsigned skip_count_root = 0); + td::Result add_used_storage(Ref cell, bool kill_dup = true, unsigned skip_count_root = 0); + + unsigned long long limit_cells = std::numeric_limits::max(); + unsigned long long limit_bits = std::numeric_limits::max(); +}; + +struct VmStorageStat { + td::uint64 cells{0}, bits{0}, refs{0}, limit; + td::HashSet visited; + VmStorageStat(td::uint64 _limit) : limit(_limit) { + } + bool add_storage(Ref cell); + bool add_storage(const CellSlice& cs); + bool check_visited(const CellHash& cell_hash) { + return visited.insert(cell_hash).second; + } + bool check_visited(const Ref& cell) { + return check_visited(cell->get_hash()); + } }; struct CellSerializationInfo { @@ -159,7 +187,7 @@ struct CellSerializationInfo { class BagOfCells { public: - enum { hash_bytes = vm::Cell::hash_bytes }; + enum { hash_bytes = vm::Cell::hash_bytes, default_max_roots = 16384 }; enum Mode { WithIndex = 1, WithCRC32C = 2, WithTopHash = 4, WithIntHashes = 8, WithCacheBits = 16, max = 31 }; enum { max_cell_whs = 64 }; using Hash = Cell::Hash; @@ -206,8 +234,6 @@ class BagOfCells { int max_depth{1024}; Info info; unsigned long long data_bytes{0}; - unsigned char* store_ptr{nullptr}; - unsigned char* store_end{nullptr}; td::HashMap cells; struct CellInfo { Ref dc_ref; @@ -257,11 +283,15 @@ class BagOfCells { std::string serialize_to_string(int mode = 0); td::Result serialize_to_slice(int mode = 0); std::size_t serialize_to(unsigned char* buffer, std::size_t buff_size, int mode = 0); + td::Status serialize_to_file(td::FileFd& fd, int mode = 0); + template + std::size_t serialize_to_impl(WriterT& writer, int mode = 0); std::string extract_string() const; - td::Result deserialize(const td::Slice& data); - td::Result deserialize(const unsigned char* buffer, std::size_t buff_size) { - return deserialize(td::Slice{buffer, buff_size}); + td::Result deserialize(const td::Slice& data, int max_roots = default_max_roots); + td::Result deserialize(const unsigned char* buffer, std::size_t buff_size, + int max_roots = default_max_roots) { + return deserialize(td::Slice{buffer, buff_size}, max_roots); } int get_root_count() const { return root_count; @@ -284,23 +314,6 @@ class BagOfCells { cell_list_.clear(); } td::uint64 compute_sizes(int mode, int& r_size, int& o_size); - void init_store(unsigned char* from, unsigned char* to) { - store_ptr = from; - store_end = to; - } - void store_chk() const { - DCHECK(store_ptr <= store_end); - } - bool store_empty() const { - return store_ptr == store_end; - } - void store_uint(unsigned long long value, unsigned bytes); - void store_ref(unsigned long long value) { - store_uint(value, info.ref_byte_size); - } - void store_offset(unsigned long long value) { - store_uint(value, info.offset_byte_size); - } void reorder_cells(); int revisit(int cell_idx, int force = 0); unsigned long long get_idx_entry_raw(int index); @@ -311,4 +324,14 @@ class BagOfCells { std::vector* cell_should_cache); }; +td::Result> std_boc_deserialize(td::Slice data, bool can_be_empty = false, bool allow_nonzero_level = false); +td::Result std_boc_serialize(Ref root, int mode = 0); + +td::Result>> std_boc_deserialize_multi(td::Slice data, + int max_roots = BagOfCells::default_max_roots); +td::Result std_boc_serialize_multi(std::vector> root, int mode = 0); + +td::Status std_boc_serialize_to_file_large(std::shared_ptr reader, Cell::Hash root_hash, + td::FileFd& fd, int mode = 0); + } // namespace vm diff --git a/crypto/vm/box.hpp b/crypto/vm/box.hpp index a448c5d8..0d676cae 100644 --- a/crypto/vm/box.hpp +++ b/crypto/vm/box.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/stack.hpp" @@ -30,7 +30,7 @@ class Box : public td::CntObject { Box(const Box&) = default; Box(Box&&) = default; template - Box(Args... args) : data_{std::move(args...)} { + Box(Args&&... args) : data_{std::forward(args)...} { } ~Box() override = default; Box(const StackEntry& data) : data_(data) { diff --git a/crypto/vm/cellops.cpp b/crypto/vm/cellops.cpp index 5346e168..61ffe5c5 100644 --- a/crypto/vm/cellops.cpp +++ b/crypto/vm/cellops.cpp @@ -14,16 +14,16 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/cellops.h" #include "vm/log.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" #include "vm/vmstate.h" +#include "vm/vm.h" #include "common/bigint.hpp" #include "common/refint.h" @@ -58,14 +58,27 @@ std::string dump_push_ref(CellSlice& cs, unsigned args, int pfx_bits, std::strin return ""; } cs.advance(pfx_bits); - cs.advance_refs(1); - return name; + auto cell = cs.fetch_ref(); + return name + " (" + cell->get_hash().to_hex() + ")"; } int compute_len_push_ref(const CellSlice& cs, unsigned args, int pfx_bits) { return cs.have_refs(1) ? (0x10000 + pfx_bits) : 0; } +std::string dump_push_ref2(CellSlice& cs, unsigned args, int pfx_bits, std::string name) { + if (!cs.have_refs(2)) { + return ""; + } + cs.advance(pfx_bits); + auto cell1 = cs.fetch_ref(), cell2 = cs.fetch_ref(); + return name + " (" + cell1->get_hash().to_hex() + ") (" + cell2->get_hash().to_hex() + ")"; +} + +int compute_len_push_ref2(const CellSlice& cs, unsigned args, int pfx_bits) { + return cs.have_refs(2) ? (0x20000 + pfx_bits) : 0; +} + int exec_push_slice_common(VmState* st, CellSlice& cs, unsigned data_bits, unsigned refs, int pfx_bits) { if (!cs.have(pfx_bits + data_bits)) { throw VmError{Excno::inv_opcode, "not enough data bits for a PUSHSLICE instruction"}; @@ -90,7 +103,8 @@ std::string dump_push_slice_common(CellSlice& cs, unsigned data_bits, unsigned r cs.advance(pfx_bits); auto slice = cs.fetch_subslice(data_bits, refs); slice.unique_write().remove_trailing(); - std::ostringstream os{name}; + std::ostringstream os; + os << name; slice->dump_hex(os, 1, false); return os.str(); } @@ -175,7 +189,8 @@ std::string dump_push_cont(CellSlice& cs, unsigned args, int pfx_bits) { } cs.advance(pfx_bits); auto slice = cs.fetch_subslice(data_bits, refs); - std::ostringstream os{"PUSHCONT "}; + std::ostringstream os; + os << "PUSHCONT "; slice->dump_hex(os, 1, false); return os.str(); } @@ -206,7 +221,8 @@ std::string dump_push_cont_simple(CellSlice& cs, unsigned args, int pfx_bits) { } cs.advance(pfx_bits); auto slice = cs.fetch_subslice(data_bits); - std::ostringstream os{"PUSHCONT "}; + std::ostringstream os; + os << "PUSHCONT "; slice->dump_hex(os, 1, false); return os.str(); } @@ -814,6 +830,9 @@ void register_cell_serialize_ops(OpcodeTable& cp0) { compute_len_store_const_ref)) .insert(OpcodeInstr::mksimple(0xcf23, 16, "ENDXC", exec_builder_to_special_cell)) .insert(OpcodeInstr::mkfixed(0xcf28 >> 2, 14, 2, dump_store_le_int, exec_store_le_int)) + .insert(OpcodeInstr::mksimple( + 0xcf30, 16, "BDEPTH", + std::bind(exec_int_builder_func, _1, "BDEPTH", [](Ref b) { return b->get_depth(); }))) .insert(OpcodeInstr::mksimple( 0xcf31, 16, "BBITS", std::bind(exec_int_builder_func, _1, "BBITS", [](Ref b) { return b->size(); }))) @@ -873,6 +892,40 @@ int exec_load_special_cell(VmState* st, bool quiet) { Stack& stack = st->get_stack(); VM_LOG(st) << "execute XLOAD" << (quiet ? "Q" : ""); auto cell = stack.pop_cell(); + if (st->get_global_version() >= 5) { + st->register_cell_load(cell->get_hash()); + auto r_loaded_cell = cell->load_cell(); + if (r_loaded_cell.is_error()) { + if (quiet) { + stack.push_bool(false); + return 0; + } else { + throw VmError{Excno::cell_und, "failed to load cell"}; + } + } + auto loaded_cell = r_loaded_cell.move_as_ok(); + if (loaded_cell.data_cell->is_special()) { + if (loaded_cell.data_cell->special_type() != CellTraits::SpecialType::Library) { + if (quiet) { + stack.push_bool(false); + return 0; + } else { + throw VmError{Excno::cell_und, "unexpected special cell"}; + } + } + CellSlice cs(std::move(loaded_cell)); + DCHECK(cs.size() == Cell::hash_bits + 8); + cell = st->load_library(cs.data_bits() + 8); + if (cell.is_null()) { + if (quiet) { + stack.push_bool(false); + return 0; + } else { + throw VmError{Excno::cell_und, "failed to load library cell"}; + } + } + } + } stack.push_cell(cell); if (quiet) { stack.push_bool(true); @@ -1044,8 +1097,8 @@ int exec_load_int_fixed2(VmState* st, unsigned args) { } std::string dump_load_int_fixed2(CellSlice&, unsigned args) { - std::ostringstream os{args & 0x200 ? "PLD" : "LD"}; - os << (args & 0x100 ? 'U' : 'I'); + std::ostringstream os; + os << (args & 0x200 ? "PLD" : "LD") << (args & 0x100 ? 'U' : 'I'); if (args & 0x400) { os << 'Q'; } @@ -1065,9 +1118,9 @@ int exec_preload_uint_fixed_0e(VmState* st, unsigned args) { } std::string dump_preload_uint_fixed_0e(CellSlice&, unsigned args) { - std::ostringstream os{"PLDUZ "}; + std::ostringstream os; unsigned bits = ((args & 7) + 1) << 5; - os << bits; + os << "PLDUZ " << bits; return os.str(); } @@ -1092,7 +1145,8 @@ int exec_load_slice_fixed2(VmState* st, unsigned args) { std::string dump_load_slice_fixed2(CellSlice&, unsigned args) { unsigned bits = (args & 0xff) + 1; - std::ostringstream os{args & 0x100 ? "PLDSLICE" : "LDSLICE"}; + std::ostringstream os; + os << (args & 0x100 ? "PLDSLICE" : "LDSLICE"); if (args & 0x200) { os << 'Q'; } @@ -1321,6 +1375,71 @@ int exec_load_same(VmState* st, const char* name, int x) { return 0; } +int exec_cell_depth(VmState* st) { + Stack& stack = st->get_stack(); + VM_LOG(st) << "execute CDEPTH"; + auto cell = stack.pop_maybe_cell(); + stack.push_smallint(cell.not_null() ? cell->get_depth() : 0); + return 0; +} + +int exec_slice_depth(VmState* st) { + Stack& stack = st->get_stack(); + VM_LOG(st) << "execute SDEPTH"; + auto cs = stack.pop_cellslice(); + stack.push_smallint(cs->get_depth()); + return 0; +} + +int exec_cell_level(VmState* st) { + Stack& stack = st->get_stack(); + VM_LOG(st) << "execute CLEVEL"; + auto cell = stack.pop_cell(); + stack.push_smallint(cell->get_level()); + return 0; +} + +int exec_cell_level_mask(VmState* st) { + Stack& stack = st->get_stack(); + VM_LOG(st) << "execute CLEVELMASK"; + auto cell = stack.pop_cell(); + stack.push_smallint(cell->get_level_mask().get_mask()); + return 0; +} + +int exec_cell_hash_i(VmState* st, unsigned args, bool var) { + unsigned i; + Stack& stack = st->get_stack(); + if (var) { + VM_LOG(st) << "execute CHASHIX"; + i = stack.pop_smallint_range(3); + } else { + i = args & 3; + VM_LOG(st) << "execute CHASHI " << i; + } + auto cell = stack.pop_cell(); + std::array hash = cell->get_hash(i).as_array(); + td::RefInt256 res{true}; + CHECK(res.write().import_bytes(hash.data(), hash.size(), false)); + stack.push_int(std::move(res)); + return 0; +} + +int exec_cell_depth_i(VmState* st, unsigned args, bool var) { + unsigned i; + Stack& stack = st->get_stack(); + if (var) { + VM_LOG(st) << "execute CDEPTHIX"; + i = stack.pop_smallint_range(3); + } else { + i = args & 3; + VM_LOG(st) << "execute CDEPTHI " << i; + } + auto cell = stack.pop_cell(); + stack.push_smallint(cell->get_depth(i)); + return 0; +} + void register_cell_deserialize_ops(OpcodeTable& cp0) { using namespace std::placeholders; cp0.insert(OpcodeInstr::mksimple(0xd0, 8, "CTOS", exec_cell_to_slice)) @@ -1407,7 +1526,15 @@ void register_cell_deserialize_ops(OpcodeTable& cp0) { .insert(OpcodeInstr::mkfixed(0xd75, 12, 4, dump_load_le_int, exec_load_le_int)) .insert(OpcodeInstr::mksimple(0xd760, 16, "LDZEROES", std::bind(exec_load_same, _1, "LDZEROES", 0))) .insert(OpcodeInstr::mksimple(0xd761, 16, "LDONES", std::bind(exec_load_same, _1, "LDONES", 1))) - .insert(OpcodeInstr::mksimple(0xd762, 16, "LDSAME", std::bind(exec_load_same, _1, "LDSAME", -1))); + .insert(OpcodeInstr::mksimple(0xd762, 16, "LDSAME", std::bind(exec_load_same, _1, "LDSAME", -1))) + .insert(OpcodeInstr::mksimple(0xd764, 16, "SDEPTH", exec_slice_depth)) + .insert(OpcodeInstr::mksimple(0xd765, 16, "CDEPTH", exec_cell_depth)) + .insert(OpcodeInstr::mksimple(0xd766, 16, "CLEVEL", exec_cell_level)->require_version(6)) + .insert(OpcodeInstr::mksimple(0xd767, 16, "CLEVELMASK", exec_cell_level_mask)->require_version(6)) + .insert(OpcodeInstr::mkfixed(0xd768 >> 2, 14, 2, instr::dump_1c_and(3, "CHASHI "), std::bind(exec_cell_hash_i, _1, _2, false))->require_version(6)) + .insert(OpcodeInstr::mkfixed(0xd76c >> 2, 14, 2, instr::dump_1c_and(3, "CDEPTHI "), std::bind(exec_cell_depth_i, _1, _2, false))->require_version(6)) + .insert(OpcodeInstr::mksimple(0xd770, 16, "CHASHIX ", std::bind(exec_cell_hash_i, _1, 0, true))->require_version(6)) + .insert(OpcodeInstr::mksimple(0xd771, 16, "CDEPTHIX ", std::bind(exec_cell_depth_i, _1, 0, true))->require_version(6)); } void register_cell_ops(OpcodeTable& cp0) { @@ -1417,4 +1544,193 @@ void register_cell_ops(OpcodeTable& cp0) { register_cell_deserialize_ops(cp0); } +namespace util { + +bool load_int256_q(CellSlice& cs, td::RefInt256& res, int len, bool sgnd, bool quiet) { + if (!cs.fetch_int256_to(len, res, sgnd)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und}; + } + return true; +} +bool load_long_q(CellSlice& cs, td::int64& res, int len, bool quiet) { + CHECK(0 <= len && len <= 64); + if (!cs.have(len)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und}; + } + res = cs.fetch_long(len); + return true; +} +bool load_ulong_q(CellSlice& cs, td::uint64& res, int len, bool quiet) { + CHECK(0 <= len && len <= 64); + if (!cs.have(len)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und}; + } + res = cs.fetch_ulong(len); + return true; +} +bool load_ref_q(CellSlice& cs, td::Ref& res, bool quiet) { + if (!cs.have_refs(1)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und}; + } + res = cs.fetch_ref(); + return true; +} +bool load_maybe_ref_q(CellSlice& cs, td::Ref& res, bool quiet) { + if (!cs.fetch_maybe_ref(res)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und}; + } + return true; +} +bool skip_bits_q(CellSlice& cs, int bits, bool quiet) { + if (!cs.skip_first(bits)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und}; + } + return true; +} + +td::RefInt256 load_int256(CellSlice& cs, int len, bool sgnd) { + td::RefInt256 x; + load_int256_q(cs, x, len, sgnd, false); + return x; +} +td::int64 load_long(CellSlice& cs, int len) { + td::int64 x; + load_long_q(cs, x, len, false); + return x; +} +td::uint64 load_ulong(CellSlice& cs, int len) { + td::uint64 x; + load_ulong_q(cs, x, len, false); + return x; +} +td::Ref load_ref(CellSlice& cs) { + td::Ref x; + load_ref_q(cs, x, false); + return x; +} +td::Ref load_maybe_ref(CellSlice& cs) { + td::Ref x; + load_maybe_ref_q(cs, x, false); + return x; +} +void check_have_bits(const CellSlice& cs, int bits) { + if (!cs.have(bits)) { + throw VmError{Excno::cell_und}; + } +} +void skip_bits(CellSlice& cs, int bits) { + skip_bits_q(cs, bits, false); +} +void end_parse(CellSlice& cs) { + if (cs.size() || cs.size_refs()) { + throw VmError{Excno::cell_und, "extra data remaining in deserialized cell"}; + } +} + +bool store_int256(CellBuilder& cb, const td::RefInt256& x, int len, bool sgnd, bool quiet) { + if (!cb.can_extend_by(len)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_ov}; + } + if (!x->fits_bits(len, sgnd)) { + if (quiet) { + return false; + } + throw VmError{Excno::range_chk}; + } + cb.store_int256(*x, len, sgnd); + return true; +} +bool store_long(CellBuilder& cb, td::int64 x, int len, bool quiet) { + CHECK(len > 0); + if (!cb.can_extend_by(len)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_ov}; + } + if (len < 64 && (x < td::int64(std::numeric_limits::max() << (len - 1)) || x >= (1LL << (len - 1)))) { + if (quiet) { + return false; + } + throw VmError{Excno::range_chk}; + } + if (len > 64) { + cb.store_bits_same(len - 64, x < 0); + len = 64; + } + cb.store_long(x, len); + return true; +} +bool store_ulong(CellBuilder& cb, td::uint64 x, int len, bool quiet) { + CHECK(len > 0); + if (!cb.can_extend_by(len)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_ov}; + } + if (len < 64 && x >= (1ULL << len)) { + if (quiet) { + return false; + } + throw VmError{Excno::range_chk}; + } + if (len > 64) { + cb.store_zeroes(len - 64); + len = 64; + } + cb.store_long(x, len); + return true; +} +bool store_ref(CellBuilder& cb, td::Ref x, bool quiet) { + if (!cb.store_ref_bool(std::move(x))) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_ov}; + } + return true; +} +bool store_maybe_ref(CellBuilder& cb, td::Ref x, bool quiet) { + if (!cb.store_maybe_ref(std::move(x))) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_ov}; + } + return true; +} +bool store_slice(CellBuilder& cb, const CellSlice& cs, bool quiet) { + if (!cell_builder_add_slice_bool(cb, cs)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_ov}; + } + return true; +} + +} // namespace util + } // namespace vm diff --git a/crypto/vm/cellops.h b/crypto/vm/cellops.h index cc0f5ab8..0fd62854 100644 --- a/crypto/vm/cellops.h +++ b/crypto/vm/cellops.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cellslice.h" @@ -23,9 +23,42 @@ namespace vm { class OpcodeTable; -void register_cell_ops(OpcodeTable &cp0); +void register_cell_ops(OpcodeTable& cp0); -std::string dump_push_ref(CellSlice &cs, unsigned args, int pfx_bits, std::string name); -int compute_len_push_ref(const CellSlice &cs, unsigned args, int pfx_bits); +std::string dump_push_ref(CellSlice& cs, unsigned args, int pfx_bits, std::string name); +int compute_len_push_ref(const CellSlice& cs, unsigned args, int pfx_bits); + +std::string dump_push_ref2(CellSlice& cs, unsigned args, int pfx_bits, std::string name); +int compute_len_push_ref2(const CellSlice& cs, unsigned args, int pfx_bits); + +namespace util { + +// "_q" functions throw on error if not quiet, return false if quiet (leaving cs unchanged) +bool load_int256_q(CellSlice& cs, td::RefInt256& res, int len, bool sgnd, bool quiet); +bool load_long_q(CellSlice& cs, td::int64& res, int len, bool quiet); +bool load_ulong_q(CellSlice& cs, td::uint64& res, int len, bool quiet); +bool load_ref_q(CellSlice& cs, td::Ref& res, bool quiet); +bool load_maybe_ref_q(CellSlice& cs, td::Ref& res, bool quiet); +bool skip_bits_q(CellSlice& cs, int bits, bool quiet); + +// Non-"_q" functions throw on error +td::RefInt256 load_int256(CellSlice& cs, int len, bool sgnd); +td::int64 load_long(CellSlice& cs, int len); +td::uint64 load_ulong(CellSlice& cs, int len); +td::Ref load_ref(CellSlice& cs); +td::Ref load_maybe_ref(CellSlice& cs); +void check_have_bits(const CellSlice& cs, int bits); +void skip_bits(CellSlice& cs, int bits); +void end_parse(CellSlice& cs); + +// store_... functions throw on error if not quiet, return false if quiet (leaving cb unchanged) +bool store_int256(CellBuilder& cb, const td::RefInt256& x, int len, bool sgnd, bool quiet = false); +bool store_long(CellBuilder& cb, td::int64 x, int len, bool quiet = false); +bool store_ulong(CellBuilder& cb, td::uint64 x, int len, bool quiet = false); +bool store_ref(CellBuilder& cb, td::Ref x, bool quiet = false); +bool store_maybe_ref(CellBuilder& cb, td::Ref x, bool quiet = false); +bool store_slice(CellBuilder& cb, const CellSlice& cs, bool quiet = false); + +} // namespace util } // namespace vm diff --git a/crypto/vm/cellparse.hpp b/crypto/vm/cellparse.hpp index 9e411632..5ca3d290 100644 --- a/crypto/vm/cellparse.hpp +++ b/crypto/vm/cellparse.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells.h" diff --git a/crypto/vm/cells.h b/crypto/vm/cells.h index ea362597..cf917eff 100644 --- a/crypto/vm/cells.h +++ b/crypto/vm/cells.h @@ -14,26 +14,16 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once -#include -#include "common/refcnt.hpp" -#include "common/bitstring.h" -#include "common/bigint.hpp" -#include "common/refint.h" - #include "vm/cells/Cell.h" #include "vm/cells/CellBuilder.h" #include "vm/cells/DataCell.h" #include "vm/cells/UsageCell.h" #include "vm/cells/VirtualCell.h" -#include "td/utils/Slice.h" -#include "td/utils/StringBuilder.h" -#include "openssl/digest.h" - // H_i(cell) = H(cell_i) // cell.hash = sha256( // d1, d2, diff --git a/crypto/vm/cells/Cell.cpp b/crypto/vm/cells/Cell.cpp index d9e392ed..1c20ba41 100644 --- a/crypto/vm/cells/Cell.cpp +++ b/crypto/vm/cells/Cell.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/cells/Cell.h" #include "vm/cells/VirtualCell.h" diff --git a/crypto/vm/cells/Cell.h b/crypto/vm/cells/Cell.h index 2f5d2cc7..03f73b14 100644 --- a/crypto/vm/cells/Cell.h +++ b/crypto/vm/cells/Cell.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/refcnt.hpp" diff --git a/crypto/vm/cells/CellBuilder.cpp b/crypto/vm/cells/CellBuilder.cpp index 223959bd..43058531 100644 --- a/crypto/vm/cells/CellBuilder.cpp +++ b/crypto/vm/cells/CellBuilder.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/cells/CellBuilder.h" @@ -24,7 +24,7 @@ #include "td/utils/misc.h" #include "td/utils/format.h" -#include "openssl/digest.h" +#include "openssl/digest.hpp" namespace vm { @@ -52,18 +52,31 @@ Ref CellBuilder::finalize_copy(bool special) const { } auto res = DataCell::create(data, size(), td::span(refs.data(), size_refs()), special); if (res.is_error()) { - LOG(ERROR) << res.error(); + LOG(DEBUG) << res.error(); throw CellWriteError{}; } - CHECK(res.ok().not_null()); - return res.move_as_ok(); + auto cell = res.move_as_ok(); + CHECK(cell.not_null()); + if (vm_state_interface) { + vm_state_interface->register_new_cell(cell); + if (cell.is_null()) { + LOG(DEBUG) << "cannot register new data cell"; + throw CellWriteError{}; + } + } + return cell; +} + +td::Result> CellBuilder::finalize_novm_nothrow(bool special) { + auto res = DataCell::create(data, size(), td::mutable_span(refs.data(), size_refs()), special); + bits = refs_cnt = 0; + return res; } Ref CellBuilder::finalize_novm(bool special) { - auto res = DataCell::create(data, size(), td::mutable_span(refs.data(), size_refs()), special); - bits = refs_cnt = 0; + auto res = finalize_novm_nothrow(special); if (res.is_error()) { - LOG(ERROR) << res.error(); + LOG(DEBUG) << res.error(); throw CellWriteError{}; } CHECK(res.ok().not_null()); @@ -72,10 +85,17 @@ Ref CellBuilder::finalize_novm(bool special) { Ref CellBuilder::finalize(bool special) { auto* vm_state_interface = VmStateInterface::get(); - if (vm_state_interface) { - vm_state_interface->register_cell_create(); + if (!vm_state_interface) { + return finalize_novm(special); } - return finalize_novm(special); + vm_state_interface->register_cell_create(); + auto cell = finalize_novm(special); + vm_state_interface->register_new_cell(cell); + if (cell.is_null()) { + LOG(DEBUG) << "cannot register new data cell"; + throw CellWriteError{}; + } + return cell; } Ref CellBuilder::create_pruned_branch(Ref cell, td::uint32 new_level, td::uint32 virt_level) { @@ -87,6 +107,7 @@ Ref CellBuilder::create_pruned_branch(Ref cell, td::uint32 new_level } return do_create_pruned_branch(std::move(cell), new_level, virt_level); } + Ref CellBuilder::do_create_pruned_branch(Ref cell, td::uint32 new_level, td::uint32 virt_level) { auto level_mask = cell->get_level_mask().apply(virt_level); auto level = level_mask.get_level(); @@ -314,7 +335,7 @@ bool CellBuilder::store_ulong_rchk_bool(unsigned long long val, unsigned val_bit } CellBuilder& CellBuilder::store_long(long long val, unsigned val_bits) { - return store_long_top(val << (64 - val_bits), val_bits); + return store_long_top(val_bits == 0 ? 0 : (unsigned long long)val << (64 - val_bits), val_bits); } CellBuilder& CellBuilder::store_long_top(unsigned long long val, unsigned top_bits) { @@ -371,6 +392,14 @@ CellBuilder& CellBuilder::store_ref(Ref ref) { return ensure_pass(store_ref_bool(std::move(ref))); } +td::uint16 CellBuilder::get_depth() const { + int d = 0; + for (unsigned i = 0; i < refs_cnt; i++) { + d = std::max(d, 1 + refs[i]->get_depth()); + } + return static_cast(d); +} + bool CellBuilder::append_data_cell_bool(const DataCell& cell) { unsigned len = cell.size(); if (can_extend_by(len, cell.size_refs())) { @@ -460,6 +489,16 @@ bool CellBuilder::append_cellslice_chk(Ref cs_ref, unsigned size_ext) return cs_ref.not_null() && append_cellslice_chk(*cs_ref, size_ext); } +CellSlice CellSlice::clone() const { + CellBuilder cb; + Ref cell; + if (cb.append_cellslice_bool(*this) && cb.finalize_to(cell)) { + return CellSlice{NoVmOrd(), std::move(cell)}; + } else { + return {}; + } +} + bool CellBuilder::append_bitstring(const td::BitString& bs) { return store_bits_bool(bs.cbits(), bs.size()); } diff --git a/crypto/vm/cells/CellBuilder.h b/crypto/vm/cells/CellBuilder.h index e709725c..954a1ac0 100644 --- a/crypto/vm/cells/CellBuilder.h +++ b/crypto/vm/cells/CellBuilder.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells/DataCell.h" @@ -78,6 +78,7 @@ class CellBuilder : public td::CntObject { const unsigned char* get_data() const { return data; } + td::uint16 get_depth() const; td::ConstBitPtr data_bits() const { return data; } @@ -106,9 +107,9 @@ class CellBuilder : public td::CntObject { CellBuilder& store_bits_same(std::size_t bit_count, bool val); bool store_bits_bool(const unsigned char* str, std::size_t bit_count, int bit_offset = 0); bool store_bits_bool(td::ConstBitPtr bs, std::size_t bit_count); - template - bool store_bits_bool(const td::BitArray& ba) { - return store_bits_bool(ba.cbits(), n); + template + bool store_bits_bool(const T& ba) { + return store_bits_bool(ba.bits(), ba.size()); } bool store_bits_same_bool(std::size_t bit_count, bool val); CellBuilder& store_zeroes(std::size_t bit_count) { @@ -176,6 +177,7 @@ class CellBuilder : public td::CntObject { Ref finalize_copy(bool special = false) const; Ref finalize(bool special = false); Ref finalize_novm(bool special = false); + td::Result> finalize_novm_nothrow(bool special = false); bool finalize_to(Ref& res, bool special = false) { return (res = finalize(special)).not_null(); } diff --git a/crypto/vm/cells/CellHash.cpp b/crypto/vm/cells/CellHash.cpp index f801e2e4..038e9a9a 100644 --- a/crypto/vm/cells/CellHash.cpp +++ b/crypto/vm/cells/CellHash.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/cells/CellHash.h" diff --git a/crypto/vm/cells/CellHash.h b/crypto/vm/cells/CellHash.h index 0b5c122d..2cbc0e8b 100644 --- a/crypto/vm/cells/CellHash.h +++ b/crypto/vm/cells/CellHash.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells/CellTraits.h" diff --git a/crypto/vm/cells/CellSlice.cpp b/crypto/vm/cells/CellSlice.cpp index 74a78970..4be1667e 100644 --- a/crypto/vm/cells/CellSlice.cpp +++ b/crypto/vm/cells/CellSlice.cpp @@ -14,11 +14,12 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/cells/CellSlice.h" #include "vm/excno.hpp" #include "td/utils/bits.h" +#include "td/utils/misc.h" namespace vm { @@ -47,7 +48,7 @@ Cell::LoadedCell load_cell_nothrow(const Ref& ref) { auto res = ref->load_cell(); if (res.is_ok()) { auto ld = res.move_as_ok(); - CHECK(ld.virt.get_virtualization() == 0 || ld.data_cell->special_type() != Cell::SpecialType::PrunnedBranch); + //CHECK(ld.virt.get_virtualization() == 0 || ld.data_cell->special_type() != Cell::SpecialType::PrunnedBranch); return ld; } return {}; @@ -57,7 +58,7 @@ Cell::LoadedCell load_cell_nothrow(const Ref& ref, int mode) { auto res = ref->load_cell(); if (res.is_ok()) { auto ld = res.move_as_ok(); - CHECK(ld.virt.get_virtualization() == 0 || ld.data_cell->special_type() != Cell::SpecialType::PrunnedBranch); + //CHECK(ld.virt.get_virtualization() == 0 || ld.data_cell->special_type() != Cell::SpecialType::PrunnedBranch); if ((mode >> (ld.data_cell->is_special() ? 1 : 0)) & 1) { return ld; } @@ -594,8 +595,7 @@ td::RefInt256 CellSlice::fetch_int256(unsigned bits, bool sgnd) { if (!have(bits)) { return {}; } else if (bits < td::BigInt256::word_shift) { - long long val = sgnd ? fetch_long(bits) : fetch_ulong(bits); - return td::RefInt256{true, val}; + return td::make_refint(td::int64(sgnd ? fetch_long(bits) : fetch_ulong(bits))); } else { td::RefInt256 res{true}; res.unique_write().import_bits(data_bits(), bits, sgnd); @@ -608,8 +608,7 @@ td::RefInt256 CellSlice::prefetch_int256(unsigned bits, bool sgnd) const { if (!have(bits)) { return {}; } else if (bits < td::BigInt256::word_shift) { - long long val = sgnd ? prefetch_long(bits) : prefetch_ulong(bits); - return td::RefInt256{true, val}; + return td::make_refint(td::int64(sgnd ? prefetch_long(bits) : prefetch_ulong(bits))); } else { td::RefInt256 res{true}; res.unique_write().import_bits(data_bits(), bits, sgnd); @@ -619,15 +618,15 @@ td::RefInt256 CellSlice::prefetch_int256(unsigned bits, bool sgnd) const { td::RefInt256 CellSlice::prefetch_int256_zeroext(unsigned bits, bool sgnd) const { if (bits > 256u + sgnd) { - return td::RefInt256{false}; + return td::make_refint(); } else { unsigned ld_bits = std::min(bits, size()); if (bits < td::BigInt256::word_shift) { long long val = sgnd ? prefetch_long(ld_bits) : prefetch_ulong(ld_bits); val <<= bits - ld_bits; - return td::RefInt256{true, val}; + return td::make_refint(val); } else { - td::RefInt256 res{true}; + auto res = td::make_refint(); res.unique_write().import_bits(data_bits(), ld_bits, sgnd); res <<= bits - ld_bits; return res; @@ -721,6 +720,10 @@ bool CellSlice::prefetch_bytes(unsigned char* buffer, unsigned bytes) const { } } +bool CellSlice::fetch_bytes(td::MutableSlice slice) { + return fetch_bytes(slice.ubegin(), td::narrow_cast(slice.size())); +} + bool CellSlice::fetch_bytes(unsigned char* buffer, unsigned bytes) { if (prefetch_bytes(buffer, bytes)) { advance(bytes * 8); @@ -730,6 +733,10 @@ bool CellSlice::fetch_bytes(unsigned char* buffer, unsigned bytes) { } } +bool CellSlice::prefetch_bytes(td::MutableSlice slice) const { + return prefetch_bytes(slice.ubegin(), td::narrow_cast(slice.size())); +} + Ref CellSlice::prefetch_ref(unsigned offset) const { if (offset < size_refs()) { auto ref_id = refs_st + offset; @@ -776,6 +783,14 @@ bool CellSlice::fetch_maybe_ref(Ref& res) { } } +td::uint16 CellSlice::get_depth() const { + int d = 0; + for (unsigned i = 0; i < size_refs(); ++i) { + d = std::max(d, prefetch_ref(i)->get_depth() + 1); + } + return static_cast(d); +} + bool CellSlice::begins_with(unsigned bits, unsigned long long value) const { return have(bits) && !((prefetch_ulong(bits) ^ value) & ((1ULL << bits) - 1)); } @@ -980,13 +995,18 @@ void CellSlice::dump_hex(std::ostream& os, int mode, bool endl) const { } } -void CellSlice::print_rec(std::ostream& os, int indent) const { +bool CellSlice::print_rec(std::ostream& os, int* limit, int indent) const { for (int i = 0; i < indent; i++) { os << ' '; } + if (!limit || *limit <= 0) { + os << "" << std::endl; + return false; + } + --*limit; if (cell.is_null()) { os << "NULL" << std::endl; - return; + return true; } if (is_special()) { os << "SPECIAL "; @@ -994,8 +1014,20 @@ void CellSlice::print_rec(std::ostream& os, int indent) const { os << "x{" << as_bitslice().to_hex() << '}' << std::endl; for (unsigned i = 0; i < size_refs(); i++) { CellSlice cs{NoVm(), prefetch_ref(i)}; - cs.print_rec(os, indent + 1); + if (!cs.print_rec(os, limit, indent + 1)) { + return false; + } } + return true; +} + +bool CellSlice::print_rec(std::ostream& os, int indent) const { + int limit = default_recursive_print_limit; + return print_rec(os, &limit, indent); +} + +bool CellSlice::print_rec(int limit, std::ostream& os, int indent) const { + return print_rec(os, &limit, indent); } td::StringBuilder& operator<<(td::StringBuilder& sb, const CellSlice& cs) { @@ -1023,44 +1055,54 @@ std::ostream& operator<<(std::ostream& os, Ref cs_ref) { // If can_be_special is not null, then it is allowed to load special cell // Flag whether loaded cell is actually special will be stored into can_be_special -VirtualCell::LoadedCell load_cell_slice_impl(const Ref& cell, bool* can_be_special) { +VirtualCell::LoadedCell load_cell_slice_impl(Ref cell, bool* can_be_special) { auto* vm_state_interface = VmStateInterface::get(); - if (vm_state_interface) { - vm_state_interface->register_cell_load(); - } - auto r_loaded_cell = cell->load_cell(); - if (r_loaded_cell.is_error()) { - throw VmError{Excno::cell_und, "failed to load cell"}; - } - auto loaded_cell = r_loaded_cell.move_as_ok(); - if (loaded_cell.data_cell->special_type() == DataCell::SpecialType::PrunnedBranch) { - auto virtualization = loaded_cell.virt.get_virtualization(); - if (virtualization != 0) { - throw VmVirtError{virtualization}; + bool library_loaded = false; + while (true) { + if (vm_state_interface && !library_loaded) { + vm_state_interface->register_cell_load(cell->get_hash()); } - } - if (can_be_special) { - *can_be_special = loaded_cell.data_cell->is_special(); - } else if (loaded_cell.data_cell->is_special()) { - if (loaded_cell.data_cell->special_type() == DataCell::SpecialType::Library) { - if (vm_state_interface) { - CellSlice cs(std::move(loaded_cell)); - DCHECK(cs.size() == Cell::hash_bits + 8); - auto library_cell = vm_state_interface->load_library(cs.data_bits() + 8); - if (library_cell.not_null()) { - //TODO: fix infinity loop - return load_cell_slice_impl(library_cell, nullptr); - } - throw VmError{Excno::cell_und, "failed to load library cell"}; + auto r_loaded_cell = cell->load_cell(); + if (r_loaded_cell.is_error()) { + throw VmError{Excno::cell_und, "failed to load cell"}; + } + auto loaded_cell = r_loaded_cell.move_as_ok(); + if (loaded_cell.data_cell->special_type() == DataCell::SpecialType::PrunnedBranch) { + auto virtualization = loaded_cell.virt.get_virtualization(); + if (virtualization != 0) { + throw VmVirtError{virtualization}; } - throw VmError{Excno::cell_und, "failed to load library cell (no vm_state_interface available)"}; - } else if (loaded_cell.data_cell->special_type() == DataCell::SpecialType::PrunnedBranch) { - CHECK(loaded_cell.virt.get_virtualization() == 0); - throw VmError{Excno::cell_und, "trying to load prunned cell"}; } - throw VmError{Excno::cell_und, "unexpected special cell"}; + if (can_be_special) { + *can_be_special = loaded_cell.data_cell->is_special(); + } else if (loaded_cell.data_cell->is_special()) { + if (loaded_cell.data_cell->special_type() == DataCell::SpecialType::Library) { + if (vm_state_interface) { + if (vm_state_interface->get_global_version() >= 5) { + if (library_loaded) { + throw VmError{Excno::cell_und, "failed to load library cell: recursive library cells are not allowed"}; + } + library_loaded = true; + } + CellSlice cs(std::move(loaded_cell)); + DCHECK(cs.size() == Cell::hash_bits + 8); + auto library_cell = vm_state_interface->load_library(cs.data_bits() + 8); + if (library_cell.not_null()) { + cell = library_cell; + can_be_special = nullptr; + continue; + } + throw VmError{Excno::cell_und, "failed to load library cell"}; + } + throw VmError{Excno::cell_und, "failed to load library cell (no vm_state_interface available)"}; + } else if (loaded_cell.data_cell->special_type() == DataCell::SpecialType::PrunnedBranch) { + CHECK(loaded_cell.virt.get_virtualization() == 0); + throw VmError{Excno::cell_und, "trying to load prunned cell"}; + } + throw VmError{Excno::cell_und, "unexpected special cell"}; + } + return loaded_cell; } - return loaded_cell; } CellSlice load_cell_slice(const Ref& cell) { diff --git a/crypto/vm/cells/CellSlice.h b/crypto/vm/cells/CellSlice.h index da081469..33fad741 100644 --- a/crypto/vm/cells/CellSlice.h +++ b/crypto/vm/cells/CellSlice.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -44,6 +44,7 @@ class CellSlice : public td::CntObject { public: static constexpr long long fetch_long_eof = (static_cast(-1LL) << 63); static constexpr unsigned long long fetch_ulong_eof = (unsigned long long)-1LL; + enum { default_recursive_print_limit = 100 }; struct CellReadError {}; CellSlice(NoVm, Ref cell_ref); @@ -129,6 +130,7 @@ class CellSlice : public td::CntObject { const unsigned char* data() const { return cell->get_data(); } + td::uint16 get_depth() const; td::ConstBitPtr data_bits() const { return td::ConstBitPtr{data(), (int)cur_pos()}; } @@ -216,7 +218,9 @@ class CellSlice : public td::CntObject { return prefetch_bits_to(buffer.bits(), n); } bool fetch_bytes(unsigned char* buffer, unsigned bytes); + bool fetch_bytes(td::MutableSlice slice); bool prefetch_bytes(unsigned char* buffer, unsigned bytes) const; + bool prefetch_bytes(td::MutableSlice slice) const; td::BitSlice as_bitslice() const { return prefetch_bits(size()); } @@ -252,7 +256,9 @@ class CellSlice : public td::CntObject { bool contents_equal(const CellSlice& cs2) const; void dump(std::ostream& os, int level = 0, bool endl = true) const; void dump_hex(std::ostream& os, int mode = 0, bool endl = false) const; - void print_rec(std::ostream& os, int indent = 0) const; + bool print_rec(std::ostream& os, int indent = 0) const; + bool print_rec(std::ostream& os, int* limit, int indent = 0) const; + bool print_rec(int limit, std::ostream& os, int indent = 0) const; void error() const { throw CellReadError{}; } @@ -275,6 +281,7 @@ class CellSlice : public td::CntObject { offs = std::min(offs, size()); return CellSlice{*this, size() - offs, size_refs(), offs, 0}; } + CellSlice clone() const; private: void init_bits_refs(); diff --git a/crypto/vm/cells/CellString.cpp b/crypto/vm/cells/CellString.cpp index ad2cbf5f..474bc797 100644 --- a/crypto/vm/cells/CellString.cpp +++ b/crypto/vm/cells/CellString.cpp @@ -1,3 +1,21 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2019-2020 Telegram Systems LLP +*/ #include "CellString.h" #include "td/utils/misc.h" @@ -61,4 +79,134 @@ td::Result CellString::load(CellSlice &cs, unsigned int top_bits) { CHECK(to.offs == (int)size); return res; } +td::Result> CellString::create(td::Slice slice, unsigned int top_bits) { + vm::CellBuilder cb; + TRY_STATUS(store(cb, slice, top_bits)); + return cb.finalize(); +} +bool CellString::fetch_to(CellSlice &cs, std::string &res, unsigned int top_bits) { + auto r_str = load(cs, top_bits); + if (r_str.is_error()) { + return false; + } + res = r_str.move_as_ok(); + return true; +} + +td::Status CellText::store(CellBuilder &cb, td::Slice slice, unsigned int top_bits) { + td::uint32 size = td::narrow_cast(slice.size() * 8); + return store(cb, td::BitSlice(slice.ubegin(), size), top_bits); +} + +td::Status CellText::store(CellBuilder &cb, td::BitSlice slice, unsigned int top_bits) { + if (slice.size() > max_bytes * 8) { + return td::Status::Error("String is too long (1)"); + } + if (cb.remaining_bits() < 16) { + return td::Status::Error("Not enough space in a builder"); + } + if (top_bits < 16) { + return td::Status::Error("Need at least 16 top bits"); + } + if (slice.size() == 0) { + cb.store_long(0, 8); + return td::Status::OK(); + } + unsigned int head = td::min(slice.size(), td::min(cb.remaining_bits(), top_bits) - 16) / 8 * 8; + auto max_bits = vm::Cell::max_bits / 8 * 8; + auto depth = 1 + (slice.size() - head + max_bits - 8 - 1) / (max_bits - 8); + if (depth > max_chain_length) { + return td::Status::Error("String is too long (2)"); + } + cb.store_long(depth, 8); + cb.store_long(head / 8, 8); + cb.append_bitslice(slice.subslice(0, head)); + slice.advance(head); + if (slice.size() == 0) { + return td::Status::OK(); + } + cb.store_ref(do_store(std::move(slice))); + return td::Status::OK(); +} + +td::Ref CellText::do_store(td::BitSlice slice) { + vm::CellBuilder cb; + unsigned int head = td::min(slice.size(), cb.remaining_bits() - 8) / 8 * 8; + cb.store_long(head / 8, 8); + cb.append_bitslice(slice.subslice(0, head)); + slice.advance(head); + if (slice.size() != 0) { + cb.store_ref(do_store(std::move(slice))); + } + return cb.finalize(); +} + +template +td::Status CellText::for_each(F &&f, CellSlice cs) { + if (!cs.have(8)) { + return td::Status::Error("Cell underflow"); + } + auto depth = cs.fetch_ulong(8); + if (depth > max_chain_length) { + return td::Status::Error("Too deep string"); + } + + for (td::uint32 i = 0; i < depth; i++) { + if (!cs.have(8)) { + return td::Status::Error("Cell underflow"); + } + auto size = td::narrow_cast(cs.fetch_ulong(8)); + if (!cs.have(size * 8)) { + return td::Status::Error("Cell underflow"); + } + TRY_STATUS(f(cs.fetch_bits(size * 8))); + if (i + 1 < depth) { + if (!cs.have_refs()) { + return td::Status::Error("Cell underflow"); + } + cs = vm::load_cell_slice(cs.prefetch_ref()); + } + } + return td::Status::OK(); +} + +td::Result CellText::load(CellSlice &cs) { + unsigned int size = 0; + TRY_STATUS(for_each( + [&](auto slice) { + size += slice.size(); + if (size > max_bytes * 8) { + return td::Status::Error("String is too long"); + } + return td::Status::OK(); + }, + cs)); + if (size % 8 != 0) { + return td::Status::Error("Size is not divisible by 8"); + } + std::string res(size / 8, 0); + + td::BitPtr to(td::MutableSlice(res).ubegin()); + TRY_STATUS(for_each( + [&](auto slice) { + to.concat(slice); + return td::Status::OK(); + }, + cs)); + CHECK(to.offs == (int)size); + return res; +} +td::Result> CellText::create(td::Slice slice, unsigned int top_bits) { + vm::CellBuilder cb; + TRY_STATUS(store(cb, slice, top_bits)); + return cb.finalize(); +} +bool CellText::fetch_to(CellSlice &cs, std::string &res) { + auto r_str = load(cs); + if (r_str.is_error()) { + return false; + } + res = r_str.move_as_ok(); + return true; +} } // namespace vm diff --git a/crypto/vm/cells/CellString.h b/crypto/vm/cells/CellString.h index 89c933d8..10bd89aa 100644 --- a/crypto/vm/cells/CellString.h +++ b/crypto/vm/cells/CellString.h @@ -1,3 +1,21 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2019-2020 Telegram Systems LLP +*/ #pragma once #include "td/utils/Status.h" @@ -13,10 +31,29 @@ class CellString { static td::Status store(CellBuilder &cb, td::Slice slice, unsigned int top_bits = Cell::max_bits); static td::Status store(CellBuilder &cb, td::BitSlice slice, unsigned int top_bits = Cell::max_bits); static td::Result load(CellSlice &cs, unsigned int top_bits = Cell::max_bits); + static td::Result> create(td::Slice slice, unsigned int top_bits = Cell::max_bits); + static bool fetch_to(CellSlice &cs, std::string &res, unsigned int top_bits = Cell::max_bits); private: template static void for_each(F &&f, CellSlice &cs, unsigned int top_bits = Cell::max_bits); }; +class CellText { + public: + static constexpr unsigned int max_bytes = 1024; + static constexpr unsigned int max_chain_length = 16; + + static td::Status store(CellBuilder &cb, td::Slice slice, unsigned int top_bits = Cell::max_bits); + static td::Status store(CellBuilder &cb, td::BitSlice slice, unsigned int top_bits = Cell::max_bits); + static td::Result load(CellSlice &cs); + static td::Result> create(td::Slice slice, unsigned int top_bits = Cell::max_bits); + static bool fetch_to(CellSlice &cs, std::string &res); + + private: + template + static td::Status for_each(F &&f, CellSlice cs); + static td::Ref do_store(td::BitSlice slice); +}; + } // namespace vm diff --git a/crypto/vm/cells/CellTraits.cpp b/crypto/vm/cells/CellTraits.cpp index b0ce7381..a619d86c 100644 --- a/crypto/vm/cells/CellTraits.cpp +++ b/crypto/vm/cells/CellTraits.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/cells/CellTraits.h" diff --git a/crypto/vm/cells/CellTraits.h b/crypto/vm/cells/CellTraits.h index 9e55dd67..c994e47e 100644 --- a/crypto/vm/cells/CellTraits.h +++ b/crypto/vm/cells/CellTraits.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "td/utils/int_types.h" diff --git a/crypto/vm/cells/CellUsageTree.cpp b/crypto/vm/cells/CellUsageTree.cpp index 117d1c32..3f43ec6b 100644 --- a/crypto/vm/cells/CellUsageTree.cpp +++ b/crypto/vm/cells/CellUsageTree.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/cells/CellUsageTree.h" diff --git a/crypto/vm/cells/CellUsageTree.h b/crypto/vm/cells/CellUsageTree.h index b3383984..150dd2bd 100644 --- a/crypto/vm/cells/CellUsageTree.h +++ b/crypto/vm/cells/CellUsageTree.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/crypto/vm/cells/CellWithStorage.h b/crypto/vm/cells/CellWithStorage.h index b9841e1c..1830c37d 100644 --- a/crypto/vm/cells/CellWithStorage.h +++ b/crypto/vm/cells/CellWithStorage.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/crypto/vm/cells/DataCell.cpp b/crypto/vm/cells/DataCell.cpp index 4a89a276..cccb11dc 100644 --- a/crypto/vm/cells/DataCell.cpp +++ b/crypto/vm/cells/DataCell.cpp @@ -14,11 +14,11 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/cells/DataCell.h" -#include "openssl/digest.h" +#include "openssl/digest.hpp" #include "td/utils/ScopeGuard.h" diff --git a/crypto/vm/cells/DataCell.h b/crypto/vm/cells/DataCell.h index 416d1835..933ff04a 100644 --- a/crypto/vm/cells/DataCell.h +++ b/crypto/vm/cells/DataCell.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells/Cell.h" @@ -34,7 +34,7 @@ class DataCell : public Cell { td::bitstring::bits_store_long(dest, depth, depth_bits); } static td::uint16 load_depth(const td::uint8* src) { - return td::bitstring::bits_load_ulong(src, depth_bits) & 0xff; + return td::bitstring::bits_load_ulong(src, depth_bits) & 0xffff; } protected: diff --git a/crypto/vm/cells/ExtCell.h b/crypto/vm/cells/ExtCell.h index 72490677..401bb048 100644 --- a/crypto/vm/cells/ExtCell.h +++ b/crypto/vm/cells/ExtCell.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells/Cell.h" diff --git a/crypto/vm/cells/LevelMask.cpp b/crypto/vm/cells/LevelMask.cpp index 0a6dfb36..46d73f0d 100644 --- a/crypto/vm/cells/LevelMask.cpp +++ b/crypto/vm/cells/LevelMask.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "LevelMask.h" diff --git a/crypto/vm/cells/LevelMask.h b/crypto/vm/cells/LevelMask.h index 144fe3b4..35d03d93 100644 --- a/crypto/vm/cells/LevelMask.h +++ b/crypto/vm/cells/LevelMask.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/crypto/vm/cells/MerkleProof.cpp b/crypto/vm/cells/MerkleProof.cpp index 105c74c1..26dff787 100644 --- a/crypto/vm/cells/MerkleProof.cpp +++ b/crypto/vm/cells/MerkleProof.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/cells/MerkleProof.h" #include "vm/cells/CellBuilder.h" @@ -39,7 +39,13 @@ class MerkleProofImpl { dfs_usage_tree(cell, usage_tree_->root_id()); is_prunned_ = [this](const Ref &cell) { return visited_cells_.count(cell->get_hash()) == 0; }; } - return dfs(cell, cell->get_level()); + try { + return dfs(cell, cell->get_level()); + } catch (CellBuilder::CellWriteError &) { + return {}; + } catch (CellBuilder::CellCreateError &) { + return {}; + } } private: @@ -119,6 +125,9 @@ Ref MerkleProof::generate(Ref cell, CellUsageTree *usage_tree) { return {}; } auto raw = generate_raw(std::move(cell), usage_tree); + if (raw.is_null()) { + return {}; + } return CellBuilder::create_merkle_proof(std::move(raw)); } @@ -148,6 +157,11 @@ class MerkleProofCombineFast { MerkleProofCombineFast(Ref a, Ref b) : a_(std::move(a)), b_(std::move(b)) { } td::Result> run() { + if (a_.is_null()) { + return b_; + } else if (b_.is_null()) { + return a_; + } TRY_RESULT_ASSIGN(a_, unpack_proof(a_)); TRY_RESULT_ASSIGN(b_, unpack_proof(b_)); TRY_RESULT(res, run_raw()); @@ -204,6 +218,11 @@ class MerkleProofCombine { MerkleProofCombine(Ref a, Ref b) : a_(std::move(a)), b_(std::move(b)) { } td::Result> run() { + if (a_.is_null()) { + return b_; + } else if (b_.is_null()) { + return a_; + } TRY_RESULT_ASSIGN(a_, unpack_proof(a_)); TRY_RESULT_ASSIGN(b_, unpack_proof(b_)); TRY_RESULT(res, run_raw()); @@ -323,6 +342,10 @@ Ref MerkleProof::combine(Ref a, Ref b) { return res.move_as_ok(); } +td::Result> MerkleProof::combine_status(Ref a, Ref b) { + return MerkleProofCombine(std::move(a), std::move(b)).run(); +} + Ref MerkleProof::combine_fast(Ref a, Ref b) { auto res = MerkleProofCombineFast(std::move(a), std::move(b)).run(); if (res.is_error()) { @@ -331,6 +354,10 @@ Ref MerkleProof::combine_fast(Ref a, Ref b) { return res.move_as_ok(); } +td::Result> MerkleProof::combine_fast_status(Ref a, Ref b) { + return MerkleProofCombineFast(std::move(a), std::move(b)).run(); +} + Ref MerkleProof::combine_raw(Ref a, Ref b) { auto res = MerkleProofCombine(std::move(a), std::move(b)).run_raw(); if (res.is_error()) { @@ -366,21 +393,29 @@ bool MerkleProofBuilder::clear() { return true; } -Ref MerkleProofBuilder::extract_proof() const { - return MerkleProof::generate(orig_root, usage_tree.get()); +td::Result> MerkleProofBuilder::extract_proof() const { + Ref proof = MerkleProof::generate(orig_root, usage_tree.get()); + if (proof.is_null()) { + return td::Status::Error("cannot create Merkle proof"); + } + return proof; } bool MerkleProofBuilder::extract_proof_to(Ref &proof_root) const { - return orig_root.not_null() && (proof_root = extract_proof()).not_null(); + if (orig_root.is_null()) { + return false; + } + auto R = extract_proof(); + if (R.is_error()) { + return false; + } + proof_root = R.move_as_ok(); + return true; } td::Result MerkleProofBuilder::extract_proof_boc() const { - Ref proof_root = extract_proof(); - if (proof_root.is_null()) { - return td::Status::Error("cannot create Merkle proof"); - } else { - return std_boc_serialize(std::move(proof_root)); - } + TRY_RESULT(proof_root, extract_proof()); + return std_boc_serialize(std::move(proof_root)); } } // namespace vm diff --git a/crypto/vm/cells/MerkleProof.h b/crypto/vm/cells/MerkleProof.h index 41788c5f..9c50fd07 100644 --- a/crypto/vm/cells/MerkleProof.h +++ b/crypto/vm/cells/MerkleProof.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells/Cell.h" @@ -38,7 +38,9 @@ class MerkleProof { static Ref virtualize(Ref cell, int virtualization); static Ref combine(Ref a, Ref b); + static td::Result> combine_status(Ref a, Ref b); static Ref combine_fast(Ref a, Ref b); + static td::Result> combine_fast_status(Ref a, Ref b); // works with upwrapped proofs // works fine with cell of non-zero level, but this is not supported (yet?) in MerkeProof special cell @@ -61,7 +63,7 @@ class MerkleProofBuilder { Ref root() const { return usage_root; } - Ref extract_proof() const; + td::Result> extract_proof() const; bool extract_proof_to(Ref &proof_root) const; td::Result extract_proof_boc() const; }; diff --git a/crypto/vm/cells/MerkleUpdate.cpp b/crypto/vm/cells/MerkleUpdate.cpp index d75f1667..894612bd 100644 --- a/crypto/vm/cells/MerkleUpdate.cpp +++ b/crypto/vm/cells/MerkleUpdate.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/cells/MerkleUpdate.h" #include "vm/cells/MerkleProof.h" @@ -221,6 +221,9 @@ Ref MerkleUpdate::generate(Ref from, Ref to, CellUsageTree *us return {}; } auto res = generate_raw(std::move(from), std::move(to), usage_tree); + if (res.first.is_null() || res.second.is_null()) { + return {}; + } return CellBuilder::create_merkle_update(res.first, res.second); } diff --git a/crypto/vm/cells/MerkleUpdate.h b/crypto/vm/cells/MerkleUpdate.h index 2978ecc6..41b4c138 100644 --- a/crypto/vm/cells/MerkleUpdate.h +++ b/crypto/vm/cells/MerkleUpdate.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells/Cell.h" diff --git a/crypto/vm/cells/PrunnedCell.h b/crypto/vm/cells/PrunnedCell.h index caacf1f3..e8434ae4 100644 --- a/crypto/vm/cells/PrunnedCell.h +++ b/crypto/vm/cells/PrunnedCell.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells/CellWithStorage.h" diff --git a/crypto/vm/cells/UsageCell.h b/crypto/vm/cells/UsageCell.h index 09defae2..bf15bb56 100644 --- a/crypto/vm/cells/UsageCell.h +++ b/crypto/vm/cells/UsageCell.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells/Cell.h" diff --git a/crypto/vm/cells/VirtualCell.h b/crypto/vm/cells/VirtualCell.h index 8b6a1ac4..02abc1c8 100644 --- a/crypto/vm/cells/VirtualCell.h +++ b/crypto/vm/cells/VirtualCell.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells/Cell.h" diff --git a/crypto/vm/cells/VirtualizationParameters.h b/crypto/vm/cells/VirtualizationParameters.h index b2a64ffb..b83e87d2 100644 --- a/crypto/vm/cells/VirtualizationParameters.h +++ b/crypto/vm/cells/VirtualizationParameters.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/crypto/vm/cellslice.h b/crypto/vm/cellslice.h index 4fe4dcc8..8546e1ce 100644 --- a/crypto/vm/cellslice.h +++ b/crypto/vm/cellslice.h @@ -14,6 +14,6 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/cells/CellSlice.h" diff --git a/crypto/vm/continuation.cpp b/crypto/vm/continuation.cpp index f75aff75..91386980 100644 --- a/crypto/vm/continuation.cpp +++ b/crypto/vm/continuation.cpp @@ -14,12 +14,16 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/dispatch.h" #include "vm/continuation.h" #include "vm/dict.h" #include "vm/log.h" +#include "vm/vm.h" +#include "vm/vmstate.h" +#include "vm/boc.h" +#include "td/utils/misc.h" namespace vm { @@ -189,6 +193,10 @@ bool ControlData::deserialize(CellSlice& cs, int mode) { } bool Continuation::serialize_ref(CellBuilder& cb) const { + auto* vsi = VmStateInterface::get(); + if (vsi && !vsi->register_op()) { + return false; + } vm::CellBuilder cb2; return serialize(cb2) && cb.store_ref_bool(cb2.finalize()); } @@ -197,6 +205,11 @@ Ref Continuation::deserialize(CellSlice& cs, int mode) { if (mode & 0x1002) { return {}; } + auto* vsi = VmStateInterface::get(); + if (vsi && !vsi->register_op()) { + return {}; + } + mode |= 0x1000; switch (cs.bselect_ext(6, 0x100f011100010001ULL)) { case 0: @@ -243,6 +256,17 @@ bool Continuation::deserialize_to(Ref cell, Ref& cont, int m return deserialize_to(cs, cont, mode & ~0x1000) && cs.empty_ext(); } +std::ostream& operator<<(std::ostream& os, const Continuation& cont) { + CellBuilder cb; + if (cont.serialize(cb)) { + auto boc = vm::std_boc_serialize(cb.finalize()); + if (boc.is_ok()) { + os << td::buffer_to_hex(boc.move_as_ok().as_slice()); + } + } + return os; +} + bool QuitCont::serialize(CellBuilder& cb) const { // vmc_quit$1000 exit_code:int32 = VmCont; return cb.store_long_bool(8, 4) && cb.store_long_bool(exit_code, 32); @@ -258,6 +282,10 @@ Ref QuitCont::deserialize(CellSlice& cs, int mode) { } } +std::string QuitCont::type() const { + return "vmc_quit"; +} + int ExcQuitCont::jump(VmState* st) const & { int n = 0; try { @@ -269,6 +297,10 @@ int ExcQuitCont::jump(VmState* st) const & { return ~n; } +std::string ExcQuitCont::type() const { + return "vmc_quit_exc"; +} + bool ExcQuitCont::serialize(CellBuilder& cb) const { // vmc_quit_exc$1001 = VmCont; return cb.store_long_bool(9, 4); @@ -291,6 +323,10 @@ int PushIntCont::jump_w(VmState* st) & { return st->jump(std::move(next)); } +std::string PushIntCont::type() const { + return "vmc_pushint"; +} + bool PushIntCont::serialize(CellBuilder& cb) const { // vmc_pushint$1111 value:int32 next:^VmCont = VmCont; return cb.store_long_bool(15, 4) && cb.store_long_bool(push_val, 32) && next->serialize_ref(cb); @@ -342,6 +378,10 @@ Ref ArgContExt::deserialize(CellSlice& cs, int mode) { : Ref{}; } +std::string ArgContExt::type() const { + return "vmc_envelope"; +} + int RepeatCont::jump(VmState* st) const & { VM_LOG(st) << "repeat " << count << " more times (slow)\n"; if (count <= 0) { @@ -390,6 +430,10 @@ Ref RepeatCont::deserialize(CellSlice& cs, int mode) { } } +std::string RepeatCont::type() const { + return "vmc_repeat"; +} + int VmState::repeat(Ref body, Ref after, long long count) { if (count <= 0) { body.clear(); @@ -433,6 +477,10 @@ Ref AgainCont::deserialize(CellSlice& cs, int mode) { } } +std::string AgainCont::type() const { + return "vmc_again"; +} + int VmState::again(Ref body) { return jump(Ref{true, std::move(body)}); } @@ -482,6 +530,10 @@ Ref UntilCont::deserialize(CellSlice& cs, int mode) { } } +std::string UntilCont::type() const { + return "vmc_until"; +} + int VmState::until(Ref body, Ref after) { if (!body->has_c0()) { set_c0(Ref{true, body, std::move(after)}); @@ -564,6 +616,10 @@ Ref WhileCont::deserialize(CellSlice& cs, int mode) { } } +std::string WhileCont::type() const { + return chkcond ? "vmc_while_cond" : "vmc_while_body"; +} + int VmState::loop_while(Ref cond, Ref body, Ref after) { if (!cond->has_c0()) { set_c0(Ref{true, cond, std::move(body), std::move(after), true}); @@ -599,588 +655,8 @@ Ref OrdCont::deserialize(CellSlice& cs, int mode) { : Ref{}; } -void VmState::init_cregs(bool same_c3, bool push_0) { - cr.set_c0(quit0); - cr.set_c1(quit1); - cr.set_c2(Ref{true}); - if (same_c3) { - cr.set_c3(Ref{true, code, cp}); - if (push_0) { - VM_LOG(this) << "implicit PUSH 0 at start\n"; - get_stack().push_smallint(0); - } - } else { - cr.set_c3(Ref{true, 11}); - } - if (cr.d[0].is_null() || cr.d[1].is_null()) { - auto empty_cell = CellBuilder{}.finalize(); - for (int i = 0; i < ControlRegs::dreg_num; i++) { - if (cr.d[i].is_null()) { - cr.d[i] = empty_cell; - } - } - } - if (cr.c7.is_null()) { - cr.set_c7(Ref{true}); - } -} - -VmState::VmState() : cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) { - ensure_throw(init_cp(0)); - init_cregs(); -} - -VmState::VmState(Ref _code) - : code(std::move(_code)), cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) { - ensure_throw(init_cp(0)); - init_cregs(); -} - -VmState::VmState(Ref _code, Ref _stack, int flags, Ref _data, VmLog log, - std::vector> _libraries, Ref init_c7) - : code(std::move(_code)) - , stack(std::move(_stack)) - , cp(-1) - , dispatch(&dummy_dispatch_table) - , quit0(true, 0) - , quit1(true, 1) - , log(log) - , libraries(std::move(_libraries)) { - ensure_throw(init_cp(0)); - set_c4(std::move(_data)); - if (init_c7.not_null()) { - set_c7(std::move(init_c7)); - } - init_cregs(flags & 1, flags & 2); -} - -VmState::VmState(Ref _code, Ref _stack, const GasLimits& gas, int flags, Ref _data, VmLog log, - std::vector> _libraries, Ref init_c7) - : code(std::move(_code)) - , stack(std::move(_stack)) - , cp(-1) - , dispatch(&dummy_dispatch_table) - , quit0(true, 0) - , quit1(true, 1) - , log(log) - , gas(gas) - , libraries(std::move(_libraries)) { - ensure_throw(init_cp(0)); - set_c4(std::move(_data)); - if (init_c7.not_null()) { - set_c7(std::move(init_c7)); - } - init_cregs(flags & 1, flags & 2); -} - -Ref VmState::convert_code_cell(Ref code_cell) { - if (code_cell.is_null()) { - return {}; - } - Ref csr{true, NoVmOrd(), code_cell}; - if (csr->is_valid()) { - return csr; - } - return load_cell_slice_ref(CellBuilder{}.store_ref(std::move(code_cell)).finalize()); -} - -bool VmState::init_cp(int new_cp) { - const DispatchTable* dt = DispatchTable::get_table(new_cp); - if (dt) { - cp = new_cp; - dispatch = dt; - return true; - } else { - return false; - } -} - -bool VmState::set_cp(int new_cp) { - return new_cp == cp || init_cp(new_cp); -} - -void VmState::force_cp(int new_cp) { - if (!set_cp(new_cp)) { - throw VmError{Excno::inv_opcode, "unsupported codepage"}; - } -} - -// simple call to a continuation cont -int VmState::call(Ref cont) { - const ControlData* cont_data = cont->get_cdata(); - if (cont_data) { - if (cont_data->save.c[0].not_null()) { - // call reduces to a jump - return jump(std::move(cont)); - } - if (cont_data->stack.not_null() || cont_data->nargs >= 0) { - // if cont has non-empty stack or expects fixed number of arguments, call is not simple - return call(std::move(cont), -1, -1); - } - // create return continuation, to be stored into new c0 - Ref ret = Ref{true, std::move(code), cp}; - ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); - cr.set_c0( - std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set - return jump_to(std::move(cont)); - } - // create return continuation, to be stored into new c0 - Ref ret = Ref{true, std::move(code), cp}; - ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); - // general implementation of a simple call - cr.set_c0(std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set - return jump_to(std::move(cont)); -} - -// call with parameters to continuation cont -int VmState::call(Ref cont, int pass_args, int ret_args) { - const ControlData* cont_data = cont->get_cdata(); - if (cont_data) { - if (cont_data->save.c[0].not_null()) { - // call reduces to a jump - return jump(std::move(cont), pass_args); - } - int depth = stack->depth(); - if (pass_args > depth || cont_data->nargs > depth) { - throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"}; - } - if (cont_data->nargs > pass_args && pass_args >= 0) { - throw VmError{Excno::stk_und, - "stack underflow while calling a closure continuation: not enough arguments passed"}; - } - auto old_c0 = std::move(cr.c[0]); - // optimization(?): decrease refcnts of unused continuations in c[i] as early as possible - preclear_cr(cont_data->save); - // no exceptions should be thrown after this point - int copy = cont_data->nargs, skip = 0; - if (pass_args >= 0) { - if (copy >= 0) { - skip = pass_args - copy; - } else { - copy = pass_args; - } - } - // copy=-1 : pass whole stack, else pass top `copy` elements, drop next `skip` elements. - Ref new_stk; - if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) { - // `cont` already has a stack, create resulting stack from it - if (copy < 0) { - copy = stack->depth(); - } - if (cont->is_unique()) { - // optimization: avoid copying stack if we hold the only copy of `cont` - new_stk = std::move(cont.unique_write().get_cdata()->stack); - } else { - new_stk = cont_data->stack; - } - new_stk.write().move_from_stack(get_stack(), copy); - if (skip > 0) { - get_stack().pop_many(skip); - } - } else if (copy >= 0) { - new_stk = get_stack().split_top(copy, skip); - } else { - new_stk = std::move(stack); - stack.clear(); - } - // create return continuation using the remainder of current stack - Ref ret = Ref{true, std::move(code), cp, std::move(stack), ret_args}; - ret.unique_write().get_cdata()->save.set_c0(std::move(old_c0)); - Ref ord_cont = static_cast>(cont); - set_stack(std::move(new_stk)); - cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0 - return jump_to(std::move(cont)); - } else { - // have no continuation data, situation is somewhat simpler - int depth = stack->depth(); - if (pass_args > depth) { - throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"}; - } - // create new stack from the top `pass_args` elements of the current stack - Ref new_stk = (pass_args >= 0 ? get_stack().split_top(pass_args) : std::move(stack)); - // create return continuation using the remainder of the current stack - Ref ret = Ref{true, std::move(code), cp, std::move(stack), ret_args}; - ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); - set_stack(std::move(new_stk)); - cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0 - return jump_to(std::move(cont)); - } -} - -// simple jump to continuation cont -int VmState::jump(Ref cont) { - const ControlData* cont_data = cont->get_cdata(); - if (cont_data && (cont_data->stack.not_null() || cont_data->nargs >= 0)) { - // if cont has non-empty stack or expects fixed number of arguments, jump is not simple - return jump(std::move(cont), -1); - } else { - return jump_to(std::move(cont)); - } -} - -// general jump to continuation cont -int VmState::jump(Ref cont, int pass_args) { - const ControlData* cont_data = cont->get_cdata(); - if (cont_data) { - // first do the checks - int depth = stack->depth(); - if (pass_args > depth || cont_data->nargs > depth) { - throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"}; - } - if (cont_data->nargs > pass_args && pass_args >= 0) { - throw VmError{Excno::stk_und, - "stack underflow while jumping to closure continuation: not enough arguments passed"}; - } - // optimization(?): decrease refcnts of unused continuations in c[i] as early as possible - preclear_cr(cont_data->save); - // no exceptions should be thrown after this point - int copy = cont_data->nargs; - if (pass_args >= 0 && copy < 0) { - copy = pass_args; - } - // copy=-1 : pass whole stack, else pass top `copy` elements, drop the remainder. - if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) { - // `cont` already has a stack, create resulting stack from it - if (copy < 0) { - copy = get_stack().depth(); - } - Ref new_stk; - if (cont->is_unique()) { - // optimization: avoid copying the stack if we hold the only copy of `cont` - new_stk = std::move(cont.unique_write().get_cdata()->stack); - } else { - new_stk = cont_data->stack; - } - new_stk.write().move_from_stack(get_stack(), copy); - set_stack(std::move(new_stk)); - } else { - if (copy >= 0) { - get_stack().drop_bottom(stack->depth() - copy); - } - } - return jump_to(std::move(cont)); - } else { - // have no continuation data, situation is somewhat simpler - if (pass_args >= 0) { - int depth = get_stack().depth(); - if (pass_args > depth) { - throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"}; - } - get_stack().drop_bottom(depth - pass_args); - } - return jump_to(std::move(cont)); - } -} - -int VmState::ret() { - Ref cont = quit0; - cont.swap(cr.c[0]); - return jump(std::move(cont)); -} - -int VmState::ret(int ret_args) { - Ref cont = quit0; - cont.swap(cr.c[0]); - return jump(std::move(cont), ret_args); -} - -int VmState::ret_alt() { - Ref cont = quit1; - cont.swap(cr.c[1]); - return jump(std::move(cont)); -} - -int VmState::ret_alt(int ret_args) { - Ref cont = quit1; - cont.swap(cr.c[1]); - return jump(std::move(cont), ret_args); -} - -Ref VmState::extract_cc(int save_cr, int stack_copy, int cc_args) { - Ref new_stk; - if (stack_copy < 0 || stack_copy == stack->depth()) { - new_stk = std::move(stack); - stack.clear(); - } else if (stack_copy > 0) { - stack->check_underflow(stack_copy); - new_stk = get_stack().split_top(stack_copy); - } else { - new_stk = Ref{true}; - } - Ref cc = Ref{true, std::move(code), cp, std::move(stack), cc_args}; - stack = std::move(new_stk); - if (save_cr & 7) { - ControlData* cdata = cc.unique_write().get_cdata(); - if (save_cr & 1) { - cdata->save.set_c0(std::move(cr.c[0])); - cr.set_c0(quit0); - } - if (save_cr & 2) { - cdata->save.set_c1(std::move(cr.c[1])); - cr.set_c1(quit1); - } - if (save_cr & 4) { - cdata->save.set_c2(std::move(cr.c[2])); - // cr.set_c2(Ref{true}); - } - } - return cc; -} - -int VmState::throw_exception(int excno) { - Stack& stack_ref = get_stack(); - stack_ref.clear(); - stack_ref.push_smallint(0); - stack_ref.push_smallint(excno); - code.clear(); - consume_gas(exception_gas_price); - return jump(get_c2()); -} - -int VmState::throw_exception(int excno, StackEntry&& arg) { - Stack& stack_ref = get_stack(); - stack_ref.clear(); - stack_ref.push(std::move(arg)); - stack_ref.push_smallint(excno); - code.clear(); - consume_gas(exception_gas_price); - return jump(get_c2()); -} - -void GasLimits::gas_exception() const { - throw VmNoGas{}; -} - -void GasLimits::set_limits(long long _max, long long _limit, long long _credit) { - gas_max = _max; - gas_limit = _limit; - gas_credit = _credit; - change_base(_limit + _credit); -} - -void GasLimits::change_limit(long long _limit) { - _limit = std::min(std::max(_limit, 0LL), gas_max); - gas_credit = 0; - gas_limit = _limit; - change_base(_limit); -} - -bool VmState::set_gas_limits(long long _max, long long _limit, long long _credit) { - gas.set_limits(_max, _limit, _credit); - return true; -} - -void VmState::change_gas_limit(long long new_limit) { - VM_LOG(this) << "changing gas limit to " << std::min(new_limit, gas.gas_max); - gas.change_limit(new_limit); -} - -int VmState::step() { - assert(!code.is_null()); - //VM_LOG(st) << "stack:"; stack->dump(VM_LOG(st)); - //VM_LOG(st) << "; cr0.refcnt = " << get_c0()->get_refcnt() - 1 << std::endl; - if (stack_trace) { - stack->dump(std::cerr, 3); - } - ++steps; - if (code->size()) { - return dispatch->dispatch(this, code.write()); - } else if (code->size_refs()) { - VM_LOG(this) << "execute implicit JMPREF\n"; - Ref cont = Ref{true, load_cell_slice_ref(code->prefetch_ref()), get_cp()}; - return jump(std::move(cont)); - } else { - VM_LOG(this) << "execute implicit RET\n"; - return ret(); - } -} - -int VmState::run() { - if (code.is_null()) { - throw VmError{Excno::fatal, "cannot run an uninitialized VM"}; - } - int res; - Guard guard(this); - do { - // LOG(INFO) << "[BS] data cells: " << DataCell::get_total_data_cells(); - try { - try { - res = step(); - gas.check(); - } catch (vm::CellBuilder::CellWriteError) { - throw VmError{Excno::cell_ov}; - } catch (vm::CellBuilder::CellCreateError) { - throw VmError{Excno::cell_ov}; - } catch (vm::CellSlice::CellReadError) { - throw VmError{Excno::cell_und}; - } - } catch (const VmError& vme) { - VM_LOG(this) << "handling exception code " << vme.get_errno() << ": " << vme.get_msg(); - try { - // LOG(INFO) << "[EX] data cells: " << DataCell::get_total_data_cells(); - ++steps; - res = throw_exception(vme.get_errno()); - } catch (const VmError& vme2) { - VM_LOG(this) << "exception " << vme2.get_errno() << " while handling exception: " << vme.get_msg(); - // LOG(INFO) << "[EXX] data cells: " << DataCell::get_total_data_cells(); - return ~vme2.get_errno(); - } - } catch (VmNoGas vmoog) { - ++steps; - VM_LOG(this) << "unhandled out-of-gas exception: gas consumed=" << gas.gas_consumed() - << ", limit=" << gas.gas_limit; - get_stack().clear(); - get_stack().push_smallint(gas.gas_consumed()); - return vmoog.get_errno(); // no ~ for unhandled exceptions (to make their faking impossible) - } - } while (!res); - // LOG(INFO) << "[EN] data cells: " << DataCell::get_total_data_cells(); - if ((res | 1) == -1) { - commit(); - } - return res; -} - -ControlData* force_cdata(Ref& cont) { - if (!cont->get_cdata()) { - cont = Ref{true, cont}; - return cont.unique_write().get_cdata(); - } else { - return cont.write().get_cdata(); - } -} - -ControlRegs* force_cregs(Ref& cont) { - return &force_cdata(cont)->save; -} - -int run_vm_code(Ref code, Ref& stack, int flags, Ref* data_ptr, VmLog log, long long* steps, - GasLimits* gas_limits, std::vector> libraries, Ref init_c7, Ref* actions_ptr) { - VmState vm{code, - std::move(stack), - gas_limits ? *gas_limits : GasLimits{}, - flags, - data_ptr ? *data_ptr : Ref{}, - log, - std::move(libraries), - std::move(init_c7)}; - int res = vm.run(); - stack = vm.get_stack_ref(); - if (vm.committed() && data_ptr) { - *data_ptr = vm.get_committed_state().c4; - } - if (vm.committed() && actions_ptr) { - *actions_ptr = vm.get_committed_state().c5; - } - if (steps) { - *steps = vm.get_steps_count(); - } - if (gas_limits) { - *gas_limits = vm.get_gas_limits(); - LOG(INFO) << "steps: " << vm.get_steps_count() << " gas: used=" << gas_limits->gas_consumed() - << ", max=" << gas_limits->gas_max << ", limit=" << gas_limits->gas_limit - << ", credit=" << gas_limits->gas_credit; - } - if ((vm.get_log().log_mask & vm::VmLog::DumpStack) != 0) { - VM_LOG(&vm) << "BEGIN_STACK_DUMP"; - for (int i = stack->depth(); i > 0; i--) { - VM_LOG(&vm) << (*stack)[i - 1].to_string(); - } - VM_LOG(&vm) << "END_STACK_DUMP"; - } - - return ~res; -} - -int run_vm_code(Ref code, Stack& stack, int flags, Ref* data_ptr, VmLog log, long long* steps, - GasLimits* gas_limits, std::vector> libraries, Ref init_c7, Ref* actions_ptr) { - Ref stk{true}; - stk.unique_write().set_contents(std::move(stack)); - stack.clear(); - int res = run_vm_code(code, stk, flags, data_ptr, log, steps, gas_limits, std::move(libraries), std::move(init_c7), - actions_ptr); - CHECK(stack.is_unique()); - if (stk.is_null()) { - stack.clear(); - } else if (&(*stk) != &stack) { - VmState* st = nullptr; - if (stk->is_unique()) { - VM_LOG(st) << "move resulting stack (" << stk->depth() << " entries)"; - stack.set_contents(std::move(stk.unique_write())); - } else { - VM_LOG(st) << "copying resulting stack (" << stk->depth() << " entries)"; - stack.set_contents(*stk); - } - } - return res; -} - -// may throw a dictionary exception; returns nullptr if library is not found in context -Ref VmState::load_library(td::ConstBitPtr hash) { - std::unique_ptr tmp_ctx; - // install temporary dummy vm state interface to prevent charging for cell load operations during library lookup - VmStateInterface::Guard(tmp_ctx.get()); - for (const auto& lib_collection : libraries) { - auto lib = lookup_library_in(hash, lib_collection); - if (lib.not_null()) { - return lib; - } - } - return {}; -} - -bool VmState::register_library_collection(Ref lib) { - if (lib.is_null()) { - return true; - } - libraries.push_back(std::move(lib)); - return true; -} - -void VmState::register_cell_load() { - consume_gas(cell_load_gas_price); -} - -void VmState::register_cell_create() { - consume_gas(cell_create_gas_price); -} - -td::BitArray<256> VmState::get_state_hash() const { - // TODO: implement properly, by serializing the stack etc, and computing the Merkle hash - td::BitArray<256> res; - res.clear(); - return res; -} - -td::BitArray<256> VmState::get_final_state_hash(int exit_code) const { - // TODO: implement properly, by serializing the stack etc, and computing the Merkle hash - td::BitArray<256> res; - res.clear(); - return res; -} - -Ref lookup_library_in(td::ConstBitPtr key, vm::Dictionary& dict) { - try { - auto val = dict.lookup(key, 256); - if (val.is_null() || !val->have_refs()) { - return {}; - } - auto root = val->prefetch_ref(); - if (root.not_null() && !root->get_hash().bits().compare(key, 256)) { - return root; - } - return {}; - } catch (vm::VmError) { - return {}; - } -} - -Ref lookup_library_in(td::ConstBitPtr key, Ref lib_root) { - if (lib_root.is_null()) { - return lib_root; - } - vm::Dictionary dict{std::move(lib_root), 256}; - return lookup_library_in(key, dict); +std::string OrdCont::type() const { + return "vmc_std"; } } // namespace vm diff --git a/crypto/vm/continuation.h b/crypto/vm/continuation.h index 1c24c4cd..8208fc16 100644 --- a/crypto/vm/continuation.h +++ b/crypto/vm/continuation.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -191,8 +191,11 @@ class Continuation : public td::CntObject { return (cont = deserialize(cs, mode)).not_null(); } static bool deserialize_to(Ref cell, Ref& cont, int mode = 0); + virtual std::string type() const = 0; }; +std::ostream& operator<<(std::ostream& os, const Continuation& cont); + class QuitCont : public Continuation { int exit_code; @@ -205,6 +208,7 @@ class QuitCont : public Continuation { } bool serialize(CellBuilder& cb) const override; static Ref deserialize(CellSlice& cs, int mode = 0); + std::string type() const override; }; class ExcQuitCont : public Continuation { @@ -214,6 +218,7 @@ class ExcQuitCont : public Continuation { int jump(VmState* st) const & override; bool serialize(CellBuilder& cb) const override; static Ref deserialize(CellSlice& cs, int mode = 0); + std::string type() const override; }; class PushIntCont : public Continuation { @@ -228,6 +233,7 @@ class PushIntCont : public Continuation { int jump_w(VmState* st) & override; bool serialize(CellBuilder& cb) const override; static Ref deserialize(CellSlice& cs, int mode = 0); + std::string type() const override; }; class RepeatCont : public Continuation { @@ -243,6 +249,7 @@ class RepeatCont : public Continuation { int jump_w(VmState* st) & override; bool serialize(CellBuilder& cb) const override; static Ref deserialize(CellSlice& cs, int mode = 0); + std::string type() const override; }; class AgainCont : public Continuation { @@ -256,6 +263,7 @@ class AgainCont : public Continuation { int jump_w(VmState* st) & override; bool serialize(CellBuilder& cb) const override; static Ref deserialize(CellSlice& cs, int mode = 0); + std::string type() const override; }; class UntilCont : public Continuation { @@ -269,6 +277,7 @@ class UntilCont : public Continuation { int jump_w(VmState* st) & override; bool serialize(CellBuilder& cb) const override; static Ref deserialize(CellSlice& cs, int mode = 0); + std::string type() const override; }; class WhileCont : public Continuation { @@ -284,6 +293,7 @@ class WhileCont : public Continuation { int jump_w(VmState* st) & override; bool serialize(CellBuilder& cb) const override; static Ref deserialize(CellSlice& cs, int mode = 0); + std::string type() const override; }; class ArgContExt : public Continuation { @@ -315,6 +325,7 @@ class ArgContExt : public Continuation { } bool serialize(CellBuilder& cb) const override; static Ref deserialize(CellSlice& cs, int mode = 0); + std::string type() const override; }; class OrdCont : public Continuation { @@ -369,291 +380,10 @@ class OrdCont : public Continuation { } bool serialize(CellBuilder& cb) const override; static Ref deserialize(CellSlice& cs, int mode = 0); + std::string type() const override; }; -struct GasLimits { - static constexpr long long infty = (1ULL << 63) - 1; - long long gas_max, gas_limit, gas_credit, gas_remaining, gas_base; - GasLimits() : gas_max(infty), gas_limit(infty), gas_credit(0), gas_remaining(infty), gas_base(infty) { - } - GasLimits(long long _limit, long long _max = infty, long long _credit = 0) - : gas_max(_max) - , gas_limit(_limit) - , gas_credit(_credit) - , gas_remaining(_limit + _credit) - , gas_base(gas_remaining) { - } - long long gas_consumed() const { - return gas_base - gas_remaining; - } - void set_limits(long long _max, long long _limit, long long _credit = 0); - void change_base(long long _base) { - gas_remaining += _base - gas_base; - gas_base = _base; - } - void change_limit(long long _limit); - void consume(long long amount) { - // LOG(DEBUG) << "consume " << amount << " gas (" << gas_remaining << " remaining)"; - gas_remaining -= amount; - } - bool try_consume(long long amount) { - // LOG(DEBUG) << "try consume " << amount << " gas (" << gas_remaining << " remaining)"; - return (gas_remaining -= amount) >= 0; - } - void gas_exception() const; - void gas_exception(bool cond) const { - if (!cond) { - gas_exception(); - } - } - void consume_chk(long long amount) { - gas_exception(try_consume(amount)); - } - void check() const { - gas_exception(gas_remaining >= 0); - } - bool final_ok() const { - return gas_remaining >= gas_credit; - } -}; - -struct CommittedState { - Ref c4, c5; - bool committed{false}; -}; - -class VmState final : public VmStateInterface { - Ref code; - Ref stack; - ControlRegs cr; - CommittedState cstate; - int cp; - long long steps{0}; - const DispatchTable* dispatch; - Ref quit0, quit1; - VmLog log; - GasLimits gas; - std::vector> libraries; - int stack_trace{0}, debug_off{0}; - - bool chksig_always_succeed{false}; - - public: - static constexpr unsigned cell_load_gas_price = 100, cell_create_gas_price = 500, exception_gas_price = 50, - tuple_entry_gas_price = 1; - VmState(); - VmState(Ref _code); - VmState(Ref _code, Ref _stack, int flags = 0, Ref _data = {}, VmLog log = {}, - std::vector> _libraries = {}, Ref init_c7 = {}); - VmState(Ref _code, Ref _stack, const GasLimits& _gas, int flags = 0, Ref _data = {}, - VmLog log = {}, std::vector> _libraries = {}, Ref init_c7 = {}); - template - VmState(Ref code_cell, Args&&... args) - : VmState(convert_code_cell(std::move(code_cell)), std::forward(args)...) { - } - VmState(const VmState&) = delete; - VmState(VmState&&) = delete; - VmState& operator=(const VmState&) = delete; - VmState& operator=(VmState&&) = delete; - bool set_gas_limits(long long _max, long long _limit, long long _credit = 0); - bool final_gas_ok() const { - return gas.final_ok(); - } - long long gas_consumed() const { - return gas.gas_consumed(); - } - bool committed() const { - return cstate.committed; - } - const CommittedState& get_committed_state() const { - return cstate; - } - void consume_gas(long long amount) { - gas.consume(amount); - } - void consume_tuple_gas(unsigned tuple_len) { - consume_gas(tuple_len * tuple_entry_gas_price); - } - void consume_tuple_gas(const Ref& tup) { - if (tup.not_null()) { - consume_tuple_gas((unsigned)tup->size()); - } - } - GasLimits get_gas_limits() const { - return gas; - } - void change_gas_limit(long long new_limit); - template - void check_underflow(Args... args) { - stack->check_underflow(args...); - } - bool register_library_collection(Ref lib); - Ref load_library( - td::ConstBitPtr hash) override; // may throw a dictionary exception; returns nullptr if library is not found - void register_cell_load() override; - void register_cell_create() override; - bool init_cp(int new_cp); - bool set_cp(int new_cp); - void force_cp(int new_cp); - int get_cp() const { - return cp; - } - int incr_stack_trace(int v) { - return stack_trace += v; - } - long long get_steps_count() const { - return steps; - } - td::BitArray<256> get_state_hash() const; - td::BitArray<256> get_final_state_hash(int exit_code) const; - int step(); - int run(); - Stack& get_stack() { - return stack.write(); - } - const Stack& get_stack_const() const { - return *stack; - } - Ref get_stack_ref() const { - return stack; - } - Ref get_c0() const { - return cr.c[0]; - } - Ref get_c1() const { - return cr.c[1]; - } - Ref get_c2() const { - return cr.c[2]; - } - Ref get_c3() const { - return cr.c[3]; - } - Ref get_c4() const { - return cr.d[0]; - } - Ref get_c7() const { - return cr.c7; - } - Ref get_c(unsigned idx) const { - return cr.get_c(idx); - } - Ref get_d(unsigned idx) const { - return cr.get_d(idx); - } - StackEntry get(unsigned idx) const { - return cr.get(idx); - } - const VmLog& get_log() const { - return log; - } - void define_c0(Ref cont) { - cr.define_c0(std::move(cont)); - } - void set_c0(Ref cont) { - cr.set_c0(std::move(cont)); - } - void set_c1(Ref cont) { - cr.set_c1(std::move(cont)); - } - void set_c2(Ref cont) { - cr.set_c2(std::move(cont)); - } - bool set_c(unsigned idx, Ref val) { - return cr.set_c(idx, std::move(val)); - } - bool set_d(unsigned idx, Ref val) { - return cr.set_d(idx, std::move(val)); - } - void set_c4(Ref val) { - cr.set_c4(std::move(val)); - } - bool set_c7(Ref val) { - return cr.set_c7(std::move(val)); - } - bool set(unsigned idx, StackEntry val) { - return cr.set(idx, std::move(val)); - } - void set_stack(Ref new_stk) { - stack = std::move(new_stk); - } - Ref swap_stack(Ref new_stk) { - stack.swap(new_stk); - return new_stk; - } - void ensure_throw(bool cond) const { - if (!cond) { - fatal(); - } - } - void set_code(Ref _code, int _cp) { - code = std::move(_code); - force_cp(_cp); - } - Ref get_code() const { - return code; - } - void push_code() { - get_stack().push_cellslice(get_code()); - } - void adjust_cr(const ControlRegs& save) { - cr ^= save; - } - void adjust_cr(ControlRegs&& save) { - cr ^= save; - } - void preclear_cr(const ControlRegs& save) { - cr &= save; - } - int call(Ref cont); - int call(Ref cont, int pass_args, int ret_args = -1); - int jump(Ref cont); - int jump(Ref cont, int pass_args); - int ret(); - int ret(int ret_args); - int ret_alt(); - int ret_alt(int ret_args); - int repeat(Ref body, Ref after, long long count); - int again(Ref body); - int until(Ref body, Ref after); - int loop_while(Ref cond, Ref body, Ref after); - int throw_exception(int excno, StackEntry&& arg); - int throw_exception(int excno); - Ref extract_cc(int save_cr = 1, int stack_copy = -1, int cc_args = -1); - void fatal(void) const { - throw VmFatal{}; - } - int jump_to(Ref cont) { - return cont->is_unique() ? cont.unique_write().jump_w(this) : cont->jump(this); - } - static Ref convert_code_cell(Ref code_cell); - void commit() { - cstate.c4 = cr.d[0]; - cstate.c5 = cr.d[1]; - cstate.committed = true; - } - - void set_chksig_always_succeed(bool flag) { - chksig_always_succeed = flag; - } - bool get_chksig_always_succeed() const { - return chksig_always_succeed; - } - - private: - void init_cregs(bool same_c3 = false, bool push_0 = true); -}; - -int run_vm_code(Ref _code, Ref& _stack, int flags = 0, Ref* data_ptr = nullptr, VmLog log = {}, - long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector> libraries = {}, - Ref init_c7 = {}, Ref* actions_ptr = nullptr); -int run_vm_code(Ref _code, Stack& _stack, int flags = 0, Ref* data_ptr = nullptr, VmLog log = {}, - long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector> libraries = {}, - Ref init_c7 = {}, Ref* actions_ptr = nullptr); - ControlData* force_cdata(Ref& cont); ControlRegs* force_cregs(Ref& cont); -Ref lookup_library_in(td::ConstBitPtr key, Ref lib_root); - } // namespace vm diff --git a/crypto/vm/contops.cpp b/crypto/vm/contops.cpp index 6fcda8de..9610e4aa 100644 --- a/crypto/vm/contops.cpp +++ b/crypto/vm/contops.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/contops.h" @@ -24,11 +24,14 @@ #include "vm/continuation.h" #include "vm/cellops.h" #include "vm/excno.hpp" +#include "vm/vm.h" + +using namespace std::literals::string_literals; namespace vm { int exec_execute(VmState* st) { - VM_LOG(st) << "execute EXECUTE\n"; + VM_LOG(st) << "execute EXECUTE"; auto cont = st->get_stack().pop_cont(); return st->call(std::move(cont)); } @@ -149,12 +152,58 @@ int exec_callcc_varargs(VmState* st) { int exec_do_with_ref(VmState* st, CellSlice& cs, int pfx_bits, const std::function)>& func, const char* name) { if (!cs.have_refs(1)) { - throw VmError{Excno::inv_opcode, "no references left for a CALLREF instruction"}; + throw VmError{Excno::inv_opcode, "no references left for a "s + name + " instruction"}; } cs.advance(pfx_bits); auto cell = cs.fetch_ref(); VM_LOG(st) << "execute " << name << " (" << cell->get_hash().to_hex() << ")"; - return func(st, Ref{true, load_cell_slice_ref(std::move(cell)), st->get_cp()}); + return func(st, st->ref_to_cont(std::move(cell))); +} + +int exec_do_with_cell(VmState* st, CellSlice& cs, int pfx_bits, const std::function)>& func, + const char* name) { + if (!cs.have_refs(1)) { + throw VmError{Excno::inv_opcode, "no references left for a "s + name + " instruction"}; + } + cs.advance(pfx_bits); + auto cell = cs.fetch_ref(); + VM_LOG(st) << "execute " << name << " (" << cell->get_hash().to_hex() << ")"; + return func(st, std::move(cell)); +} + +int exec_ifelse_ref(VmState* st, CellSlice& cs, int pfx_bits, bool mode) { + const char* name = mode ? "IFREFELSE" : "IFELSEREF"; + if (!cs.have_refs(1)) { + throw VmError{Excno::inv_opcode, "no references left for a "s + name + " instruction"}; + } + cs.advance(pfx_bits); + auto cell = cs.fetch_ref(); + Stack& stack = st->get_stack(); + VM_LOG(st) << "execute " << name << " (" << cell->get_hash().to_hex() << ")"; + stack.check_underflow(2); + auto cont = stack.pop_cont(); + if (stack.pop_bool() == mode) { + cont = st->ref_to_cont(std::move(cell)); + } else { + cell.clear(); + } + return st->call(std::move(cont)); +} + +int exec_ifref_elseref(VmState* st, CellSlice& cs, unsigned args, int pfx_bits) { + if (!cs.have_refs(2)) { + throw VmError{Excno::inv_opcode, "no references left for a IFREFELSEREF instruction"}; + } + cs.advance(pfx_bits); + auto cell1 = cs.fetch_ref(), cell2 = cs.fetch_ref(); + Stack& stack = st->get_stack(); + VM_LOG(st) << "execute IFREFELSEREF (" << cell1->get_hash().to_hex() << ") (" << cell2->get_hash().to_hex() << ")"; + if (!stack.pop_bool()) { + cell1 = std::move(cell2); + } else { + cell2.clear(); + } + return st->call(st->ref_to_cont(std::move(cell1))); } int exec_ret_data(VmState* st) { @@ -163,6 +212,77 @@ int exec_ret_data(VmState* st) { return st->ret(); } +// Mode: +// +1 = same_c3 (set c3 to code) +// +2 = push_0 (push an implicit 0 before running the code) +// +4 = load c4 (persistent data) from stack and return its final value +// +8 = load gas limit from stack and return consumed gas +// +16 = load c7 (smart-contract context) +// +32 = return c5 (actions) +// +64 = pop hard gas limit (enabled by ACCEPT) from stack as well +// +128 = isolated gas consumption (separate set of visited cells, reset chksgn counter) +// +256 = pop number N, return exactly N values from stack (only if res=0 or 1; if not enough then res=stk_und) +int exec_runvm_common(VmState* st, unsigned mode) { + if (mode >= 512) { + throw VmError{Excno::range_chk, "invalid flags"}; + } + st->consume_gas(VmState::runvm_gas_price); + Stack& stack = st->get_stack(); + bool with_data = mode & 4; + Ref c7; + Ref data, actions; + long long gas_max = (mode & 64) ? stack.pop_long_range(vm::GasLimits::infty) : vm::GasLimits::infty; + long long gas_limit = (mode & 8) ? stack.pop_long_range(vm::GasLimits::infty) : vm::GasLimits::infty; + if (!(mode & 64)) { + gas_max = gas_limit; + } else { + gas_max = std::max(gas_max, gas_limit); + } + if (mode & 16) { + c7 = stack.pop_tuple(); + } + if (with_data) { + data = stack.pop_cell(); + } + int ret_vals = -1; + if (mode & 256) { + ret_vals = stack.pop_smallint_range(1 << 30); + } + auto code = stack.pop_cellslice(); + int stack_size = stack.pop_smallint_range(stack.depth() - 1); + std::vector new_stack_entries(stack_size); + for (int i = 0; i < stack_size; ++i) { + new_stack_entries[stack_size - 1 - i] = stack.pop(); + } + td::Ref new_stack{true, std::move(new_stack_entries)}; + st->consume_stack_gas(new_stack); + gas_max = std::min(gas_max, st->get_gas_limits().gas_remaining); + gas_limit = std::min(gas_limit, st->get_gas_limits().gas_remaining); + vm::GasLimits gas{gas_limit, gas_max}; + + VmStateInterface::Guard guard{nullptr}; // Don't consume gas for creating/loading cells during VM init + VmState new_state{std::move(code), std::move(new_stack), gas, (int)mode & 3, std::move(data), + VmLog{}, std::vector>{}, std::move(c7)}; + new_state.set_chksig_always_succeed(st->get_chksig_always_succeed()); + new_state.set_global_version(st->get_global_version()); + st->run_child_vm(std::move(new_state), with_data, mode & 32, mode & 8, mode & 128, ret_vals); + return 0; +} + +int exec_runvm(VmState* st, unsigned args) { + VM_LOG(st) << "execute RUNVM " << (args & 4095) << "\n"; + return exec_runvm_common(st, args & 4095); +} + +int exec_runvmx(VmState* st) { + VM_LOG(st) << "execute RUNVMX\n"; + return exec_runvm_common(st, st->get_stack().pop_smallint_range(4095)); +} + +std::string dump_runvm(CellSlice&, unsigned args) { + return PSTRING() << "RUNVM " << (args & 4095); +} + void register_continuation_jump_ops(OpcodeTable& cp0) { using namespace std::placeholders; cp0.insert(OpcodeInstr::mksimple(0xd8, 8, "EXECUTE", exec_execute)) @@ -197,7 +317,9 @@ void register_continuation_jump_ops(OpcodeTable& cp0) { }, "JMPREFDATA"), compute_len_push_ref)) - .insert(OpcodeInstr::mksimple(0xdb3f, 16, "RETDATA", exec_ret_data)); + .insert(OpcodeInstr::mksimple(0xdb3f, 16, "RETDATA", exec_ret_data)) + .insert(OpcodeInstr::mkfixed(0xdb4, 12, 12, dump_runvm, exec_runvm)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xdb50, 16, "RUNVMX ", exec_runvmx)->require_version(4)); } int exec_if(VmState* st) { @@ -329,8 +451,8 @@ int exec_if_bit_jmp(VmState* st, unsigned args) { } std::string dump_if_bit_jmp(CellSlice& cs, unsigned args) { - std::ostringstream os{args & 0x20 ? "IFN" : " IF"}; - os << "BITJMP " << (args & 0x1f); + std::ostringstream os; + os << "IF" << (args & 0x20 ? "N" : "") << "BITJMP " << (args & 0x1f); return os.str(); } @@ -348,7 +470,7 @@ int exec_if_bit_jmpref(VmState* st, CellSlice& cs, unsigned args, int pfx_bits) bool val = x->get_bit(bit); stack.push_int(std::move(x)); if (val ^ negate) { - return st->jump(Ref{true, load_cell_slice_ref(std::move(cell)), st->get_cp()}); + return st->jump(st->ref_to_cont(std::move(cell))); } return 0; } @@ -359,71 +481,77 @@ std::string dump_if_bit_jmpref(CellSlice& cs, unsigned args, int pfx_bits) { } cs.advance(pfx_bits); cs.advance_refs(1); - std::ostringstream os{args & 0x20 ? "IFN" : " IF"}; - os << "BITJMPREF " << (args & 0x1f); + std::ostringstream os; + os << "IF" << (args & 0x20 ? "N" : "") << "BITJMPREF " << (args & 0x1f); return os.str(); } -int exec_repeat(VmState* st) { +int exec_repeat(VmState* st, bool brk) { Stack& stack = st->get_stack(); - VM_LOG(st) << "execute REPEAT\n"; + VM_LOG(st) << "execute REPEAT" << (brk ? "BRK" : ""); stack.check_underflow(2); auto cont = stack.pop_cont(); int c = stack.pop_smallint_range(0x7fffffff, 0x80000000); if (c <= 0) { return 0; } - return st->repeat(std::move(cont), st->extract_cc(1), c); + return st->repeat(std::move(cont), st->c1_envelope_if(brk, st->extract_cc(1)), c); } -int exec_repeat_end(VmState* st) { +int exec_repeat_end(VmState* st, bool brk) { Stack& stack = st->get_stack(); - VM_LOG(st) << "execute REPEATEND\n"; + VM_LOG(st) << "execute REPEATEND" << (brk ? "BRK" : ""); stack.check_underflow(1); int c = stack.pop_smallint_range(0x7fffffff, 0x80000000); if (c <= 0) { return st->ret(); } auto cont = st->extract_cc(0); - return st->repeat(std::move(cont), st->get_c0(), c); + return st->repeat(std::move(cont), st->c1_envelope_if(brk, st->get_c0()), c); } -int exec_until(VmState* st) { +int exec_until(VmState* st, bool brk) { Stack& stack = st->get_stack(); - VM_LOG(st) << "execute UNTIL\n"; + VM_LOG(st) << "execute UNTIL" << (brk ? "BRK" : ""); auto cont = stack.pop_cont(); - return st->until(std::move(cont), st->extract_cc(1)); + return st->until(std::move(cont), st->c1_envelope_if(brk, st->extract_cc(1))); } -int exec_until_end(VmState* st) { - VM_LOG(st) << "execute UNTILEND\n"; +int exec_until_end(VmState* st, bool brk) { + VM_LOG(st) << "execute UNTILEND" << (brk ? "BRK" : ""); auto cont = st->extract_cc(0); - return st->until(std::move(cont), st->get_c0()); + return st->until(std::move(cont), st->c1_envelope_if(brk, st->get_c0())); } -int exec_while(VmState* st) { +int exec_while(VmState* st, bool brk) { Stack& stack = st->get_stack(); - VM_LOG(st) << "execute WHILE\n"; + VM_LOG(st) << "execute WHILE" << (brk ? "BRK" : ""); stack.check_underflow(2); auto body = stack.pop_cont(); auto cond = stack.pop_cont(); - return st->loop_while(std::move(cond), std::move(body), st->extract_cc(1)); + return st->loop_while(std::move(cond), std::move(body), st->c1_envelope_if(brk, st->extract_cc(1))); } -int exec_while_end(VmState* st) { - VM_LOG(st) << "execute WHILEEND\n"; +int exec_while_end(VmState* st, bool brk) { + VM_LOG(st) << "execute WHILEEND" << (brk ? "BRK" : ""); auto cond = st->get_stack().pop_cont(); auto body = st->extract_cc(0); - return st->loop_while(std::move(cond), std::move(body), st->get_c0()); + return st->loop_while(std::move(cond), std::move(body), st->c1_envelope_if(brk, st->get_c0())); } -int exec_again(VmState* st) { - VM_LOG(st) << "execute AGAIN\n"; +int exec_again(VmState* st, bool brk) { + VM_LOG(st) << "execute AGAIN" << (brk ? "BRK" : ""); + if (brk) { + st->set_c1(st->extract_cc(3)); + } return st->again(st->get_stack().pop_cont()); } -int exec_again_end(VmState* st) { - VM_LOG(st) << "execute AGAINEND\n"; +int exec_again_end(VmState* st, bool brk) { + VM_LOG(st) << "execute AGAINEND" << (brk ? "BRK" : ""); + if (brk) { + st->c1_save_set(); + } return st->again(st->extract_cc(0)); } @@ -436,44 +564,70 @@ void register_continuation_cond_loop_ops(OpcodeTable& cp0) { .insert(OpcodeInstr::mksimple(0xe0, 8, "IFJMP", exec_if_jmp)) .insert(OpcodeInstr::mksimple(0xe1, 8, "IFNOTJMP", exec_ifnot_jmp)) .insert(OpcodeInstr::mksimple(0xe2, 8, "IFELSE", exec_if_else)) - .insert(OpcodeInstr::mkext( - 0xe300, 16, 0, std::bind(dump_push_ref, _1, _2, _3, "IFREF"), - std::bind(exec_do_with_ref, _1, _2, _4, - [](auto st, auto cont) { return st->get_stack().pop_bool() ? st->call(std::move(cont)) : 0; }, - "IFREF"), - compute_len_push_ref)) - .insert(OpcodeInstr::mkext( - 0xe301, 16, 0, std::bind(dump_push_ref, _1, _2, _3, "IFNOTREF"), - std::bind(exec_do_with_ref, _1, _2, _4, - [](auto st, auto cont) { return st->get_stack().pop_bool() ? 0 : st->call(std::move(cont)); }, - "IFNOTREF"), - compute_len_push_ref)) - .insert(OpcodeInstr::mkext( - 0xe302, 16, 0, std::bind(dump_push_ref, _1, _2, _3, "IFJMPREF"), - std::bind(exec_do_with_ref, _1, _2, _4, - [](auto st, auto cont) { return st->get_stack().pop_bool() ? st->jump(std::move(cont)) : 0; }, - "IFJMPREF"), - compute_len_push_ref)) - .insert(OpcodeInstr::mkext( - 0xe303, 16, 0, std::bind(dump_push_ref, _1, _2, _3, "IFNOTJMPREF"), - std::bind(exec_do_with_ref, _1, _2, _4, - [](auto st, auto cont) { return st->get_stack().pop_bool() ? 0 : st->jump(std::move(cont)); }, - "IFNOTJMPREF"), - compute_len_push_ref)) + .insert(OpcodeInstr::mkext(0xe300, 16, 0, std::bind(dump_push_ref, _1, _2, _3, "IFREF"), + std::bind(exec_do_with_cell, _1, _2, _4, + [](auto st, auto cell) { + return st->get_stack().pop_bool() + ? st->call(st->ref_to_cont(std::move(cell))) + : 0; + }, + "IFREF"), + compute_len_push_ref)) + .insert(OpcodeInstr::mkext(0xe301, 16, 0, std::bind(dump_push_ref, _1, _2, _3, "IFNOTREF"), + std::bind(exec_do_with_cell, _1, _2, _4, + [](auto st, auto cell) { + return st->get_stack().pop_bool() + ? 0 + : st->call(st->ref_to_cont(std::move(cell))); + }, + "IFNOTREF"), + compute_len_push_ref)) + .insert(OpcodeInstr::mkext(0xe302, 16, 0, std::bind(dump_push_ref, _1, _2, _3, "IFJMPREF"), + std::bind(exec_do_with_cell, _1, _2, _4, + [](auto st, auto cell) { + return st->get_stack().pop_bool() + ? st->jump(st->ref_to_cont(std::move(cell))) + : 0; + }, + "IFJMPREF"), + compute_len_push_ref)) + .insert(OpcodeInstr::mkext(0xe303, 16, 0, std::bind(dump_push_ref, _1, _2, _3, "IFNOTJMPREF"), + std::bind(exec_do_with_cell, _1, _2, _4, + [](auto st, auto cell) { + return st->get_stack().pop_bool() + ? 0 + : st->jump(st->ref_to_cont(std::move(cell))); + }, + "IFNOTJMPREF"), + compute_len_push_ref)) .insert(OpcodeInstr::mksimple(0xe304, 16, "CONDSEL", exec_condsel)) .insert(OpcodeInstr::mksimple(0xe305, 16, "CONDSELCHK", exec_condsel_chk)) .insert(OpcodeInstr::mksimple(0xe308, 16, "IFRETALT", exec_ifretalt)) .insert(OpcodeInstr::mksimple(0xe309, 16, "IFNOTRETALT", exec_ifnotretalt)) + .insert(OpcodeInstr::mkext(0xe30d, 16, 0, std::bind(dump_push_ref, _1, _2, _3, "IFREFELSE"), + std::bind(exec_ifelse_ref, _1, _2, _4, true), compute_len_push_ref)) + .insert(OpcodeInstr::mkext(0xe30e, 16, 0, std::bind(dump_push_ref, _1, _2, _3, "IFELSEREF"), + std::bind(exec_ifelse_ref, _1, _2, _4, false), compute_len_push_ref)) + .insert(OpcodeInstr::mkext(0xe30f, 16, 0, std::bind(dump_push_ref2, _1, _2, _3, "IFREFELSEREF"), + exec_ifref_elseref, compute_len_push_ref2)) .insert(OpcodeInstr::mkfixed(0xe380 >> 6, 10, 6, dump_if_bit_jmp, exec_if_bit_jmp)) .insert(OpcodeInstr::mkext(0xe3c0 >> 6, 10, 6, dump_if_bit_jmpref, exec_if_bit_jmpref, compute_len_push_ref)) - .insert(OpcodeInstr::mksimple(0xe4, 8, "REPEAT", exec_repeat)) - .insert(OpcodeInstr::mksimple(0xe5, 8, "REPEATEND", exec_repeat_end)) - .insert(OpcodeInstr::mksimple(0xe6, 8, "UNTIL", exec_until)) - .insert(OpcodeInstr::mksimple(0xe7, 8, "UNTILEND", exec_until_end)) - .insert(OpcodeInstr::mksimple(0xe8, 8, "WHILE", exec_while)) - .insert(OpcodeInstr::mksimple(0xe9, 8, "WHILEEND", exec_while_end)) - .insert(OpcodeInstr::mksimple(0xea, 8, "AGAIN", exec_again)) - .insert(OpcodeInstr::mksimple(0xeb, 8, "AGAINEND", exec_again_end)); + .insert(OpcodeInstr::mksimple(0xe4, 8, "REPEAT", std::bind(exec_repeat, _1, false))) + .insert(OpcodeInstr::mksimple(0xe5, 8, "REPEATEND", std::bind(exec_repeat_end, _1, false))) + .insert(OpcodeInstr::mksimple(0xe6, 8, "UNTIL", std::bind(exec_until, _1, false))) + .insert(OpcodeInstr::mksimple(0xe7, 8, "UNTILEND", std::bind(exec_until_end, _1, false))) + .insert(OpcodeInstr::mksimple(0xe8, 8, "WHILE", std::bind(exec_while, _1, false))) + .insert(OpcodeInstr::mksimple(0xe9, 8, "WHILEEND", std::bind(exec_while_end, _1, false))) + .insert(OpcodeInstr::mksimple(0xea, 8, "AGAIN", std::bind(exec_again, _1, false))) + .insert(OpcodeInstr::mksimple(0xeb, 8, "AGAINEND", std::bind(exec_again_end, _1, false))) + .insert(OpcodeInstr::mksimple(0xe314, 16, "REPEATBRK", std::bind(exec_repeat, _1, true))) + .insert(OpcodeInstr::mksimple(0xe315, 16, "REPEATENDBRK", std::bind(exec_repeat_end, _1, true))) + .insert(OpcodeInstr::mksimple(0xe316, 16, "UNTILBRK", std::bind(exec_until, _1, true))) + .insert(OpcodeInstr::mksimple(0xe317, 16, "UNTILENDBRK", std::bind(exec_until_end, _1, true))) + .insert(OpcodeInstr::mksimple(0xe318, 16, "WHILEBRK", std::bind(exec_while, _1, true))) + .insert(OpcodeInstr::mksimple(0xe319, 16, "WHILEENDBRK", std::bind(exec_while_end, _1, true))) + .insert(OpcodeInstr::mksimple(0xe31a, 16, "AGAINBRK", std::bind(exec_again, _1, true))) + .insert(OpcodeInstr::mksimple(0xe31b, 16, "AGAINENDBRK", std::bind(exec_again_end, _1, true))); } int exec_setcontargs_common(VmState* st, int copy, int more) { @@ -491,6 +645,7 @@ int exec_setcontargs_common(VmState* st, int copy, int more) { } else { cdata->stack.write().move_from_stack(stack, copy); } + st->consume_stack_gas(cdata->stack); if (cdata->nargs >= 0) { cdata->nargs -= copy; } @@ -515,8 +670,8 @@ int exec_setcontargs(VmState* st, unsigned args) { std::string dump_setcontargs(CellSlice& cs, unsigned args, const char* name) { int copy = (args >> 4) & 15, more = ((args + 1) & 15) - 1; - std::ostringstream os{name}; - os << ' ' << copy << ',' << more; + std::ostringstream os; + os << name << ' ' << copy << ',' << more; return os.str(); } @@ -556,6 +711,7 @@ int exec_return_args_common(VmState* st, int count) { cdata->stack.write().move_from_stack(alt_stk.write(), copy); alt_stk.clear(); } + st->consume_stack_gas(cdata->stack); if (cdata->nargs >= 0) { cdata->nargs -= copy; } @@ -586,6 +742,7 @@ int exec_bless_args_common(VmState* st, int copy, int more) { stack.check_underflow(copy + 1); auto cs = stack.pop_cellslice(); auto new_stk = stack.split_top(copy); + st->consume_stack_gas(new_stk); stack.push_cont(Ref{true, std::move(cs), st->get_cp(), std::move(new_stk), more}); return 0; } @@ -702,6 +859,17 @@ int exec_save_ctr(VmState* st, unsigned args) { return 0; } +int exec_samealt(VmState* st, bool save) { + VM_LOG(st) << "execute SAMEALT" << (save ? "SAVE" : ""); + auto c0 = st->get_c0(); + if (save) { + force_cregs(c0)->define_c1(st->get_c1()); + st->set_c0(c0); + } + st->set_c1(std::move(c0)); + return 0; +} + int exec_savealt_ctr(VmState* st, unsigned args) { unsigned idx = args & 15; VM_LOG(st) << "execute SAVEALTCTR c" << idx; @@ -879,6 +1047,8 @@ void register_continuation_change_ops(OpcodeTable& cp0) { .insert(OpcodeInstr::mksimple(0xedf7, 16, "THENRETALT", exec_thenret_alt)) .insert(OpcodeInstr::mksimple(0xedf8, 16, "INVERT", exec_invert)) .insert(OpcodeInstr::mksimple(0xedf9, 16, "BOOLEVAL", exec_booleval)) + .insert(OpcodeInstr::mksimple(0xedfa, 16, "SAMEALT", std::bind(exec_samealt, _1, false))) + .insert(OpcodeInstr::mksimple(0xedfb, 16, "SAMEALTSAVE", std::bind(exec_samealt, _1, true))) .insert(OpcodeInstr::mkfixed(0xee, 8, 8, std::bind(dump_setcontargs, _1, _2, "BLESSARGS"), exec_bless_args)); } @@ -968,8 +1138,8 @@ std::string dump_throw_any(CellSlice& cs, unsigned args) { bool has_param = args & 1; bool has_cond = args & 6; bool throw_cond = args & 2; - std::ostringstream os{has_param ? "THROWARG" : "THROW"}; - os << "ANY"; + std::ostringstream os; + os << "THROW" << (has_param ? "ARG" : "") << "ANY"; if (has_cond) { os << (throw_cond ? "IF" : "IFNOT"); } diff --git a/crypto/vm/contops.h b/crypto/vm/contops.h index 5919b07e..e86a303c 100644 --- a/crypto/vm/contops.h +++ b/crypto/vm/contops.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/crypto/vm/cp0.cpp b/crypto/vm/cp0.cpp index 3e062140..fe5dd8ef 100644 --- a/crypto/vm/cp0.cpp +++ b/crypto/vm/cp0.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "cp0.h" #include "opctable.h" @@ -29,7 +29,8 @@ namespace vm { -const OpcodeTable* init_op_cp0() { +const OpcodeTable* init_op_cp0(bool enable_debug) { + set_debug_enabled(enable_debug); static const OpcodeTable* static_op_cp0 = [] { auto op_cp0 = new OpcodeTable("TEST CODEPAGE", Codepage::test_cp); register_stack_ops(*op_cp0); // stackops.cpp diff --git a/crypto/vm/cp0.h b/crypto/vm/cp0.h index 34fc9711..9a657969 100644 --- a/crypto/vm/cp0.h +++ b/crypto/vm/cp0.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/dispatch.h" @@ -23,6 +23,6 @@ namespace vm { class OpcodeTable; -const OpcodeTable* init_op_cp0(); +const OpcodeTable* init_op_cp0(bool debug_enabled = false); } // namespace vm diff --git a/crypto/vm/db/BlobView.cpp b/crypto/vm/db/BlobView.cpp index 2f0ecb0f..1a879d00 100644 --- a/crypto/vm/db/BlobView.cpp +++ b/crypto/vm/db/BlobView.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/db/BlobView.h" diff --git a/crypto/vm/db/BlobView.h b/crypto/vm/db/BlobView.h index 8ba6f215..113ccf2e 100644 --- a/crypto/vm/db/BlobView.h +++ b/crypto/vm/db/BlobView.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "td/utils/buffer.h" diff --git a/crypto/vm/db/CellHashTable.h b/crypto/vm/db/CellHashTable.h index 057c9070..7d0308b7 100644 --- a/crypto/vm/db/CellHashTable.h +++ b/crypto/vm/db/CellHashTable.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -64,6 +64,13 @@ class CellHashTable { size_t size() const { return set_.size(); } + InfoT* get_if_exists(td::Slice hash) { + auto it = set_.find(hash); + if (it != set_.end()) { + return &const_cast(*it); + } + return nullptr; + } private: std::set> set_; diff --git a/crypto/vm/db/CellStorage.cpp b/crypto/vm/db/CellStorage.cpp index 317fead3..303d4650 100644 --- a/crypto/vm/db/CellStorage.cpp +++ b/crypto/vm/db/CellStorage.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/db/CellStorage.h" #include "vm/db/DynamicBagOfCellsDb.h" @@ -27,16 +27,25 @@ namespace vm { namespace { class RefcntCellStorer { public: - RefcntCellStorer(td::int32 refcnt, const DataCell &cell) : refcnt_(refcnt), cell_(cell) { + RefcntCellStorer(td::int32 refcnt, const td::Ref &cell, bool as_boc) + : refcnt_(refcnt), cell_(cell), as_boc_(as_boc) { } template void store(StorerT &storer) const { using td::store; + if (as_boc_) { + td::int32 tag = -1; + store(tag, storer); + store(refcnt_, storer); + td::BufferSlice data = vm::std_boc_serialize(cell_).move_as_ok(); + storer.store_slice(data); + return; + } store(refcnt_, storer); - store(cell_, storer); - for (unsigned i = 0; i < cell_.size_refs(); i++) { - auto cell = cell_.get_ref(i); + store(*cell_, storer); + for (unsigned i = 0; i < cell_->size_refs(); i++) { + auto cell = cell_->get_ref(i); auto level_mask = cell->get_level_mask(); auto level = level_mask.get_level(); td::uint8 x = static_cast(level_mask.get_mask()); @@ -60,7 +69,8 @@ class RefcntCellStorer { private: td::int32 refcnt_; - const DataCell &cell_; + td::Ref cell_; + bool as_boc_; }; class RefcntCellParser { @@ -69,11 +79,17 @@ class RefcntCellParser { } td::int32 refcnt; Ref cell; + bool stored_boc_; template void parse(ParserT &parser, ExtCellCreator &ext_cell_creator) { using ::td::parse; parse(refcnt, parser); + stored_boc_ = false; + if (refcnt == -1) { + stored_boc_ = true; + parse(refcnt, parser); + } if (!need_data_) { return; } @@ -81,6 +97,12 @@ class RefcntCellParser { TRY_STATUS(parser.get_status()); auto size = parser.get_left_len(); td::Slice data = parser.template fetch_string_raw(size); + if (stored_boc_) { + TRY_RESULT(boc, vm::std_boc_deserialize(data, false, true)); + TRY_RESULT(loaded_cell, boc->load_cell()); + cell = std::move(loaded_cell.data_cell); + return td::Status::OK(); + } CellSerializationInfo info; auto cell_data = data; TRY_STATUS(info.init(cell_data, 0 /*ref_byte_size*/)); @@ -122,7 +144,8 @@ class RefcntCellParser { }; } // namespace -CellLoader::CellLoader(std::shared_ptr reader) : reader_(std::move(reader)) { +CellLoader::CellLoader(std::shared_ptr reader, std::function on_load_callback) + : reader_(std::move(reader)), on_load_callback_(std::move(on_load_callback)) { CHECK(reader_); } @@ -145,7 +168,11 @@ td::Result CellLoader::load(td::Slice hash, bool need_da res.refcnt_ = refcnt_cell.refcnt; res.cell_ = std::move(refcnt_cell.cell); + res.stored_boc_ = refcnt_cell.stored_boc_; //CHECK(res.cell_->get_hash() == hash); + if (on_load_callback_) { + on_load_callback_(res); + } return res; } @@ -157,7 +184,7 @@ td::Status CellStorer::erase(td::Slice hash) { return kv_.erase(hash); } -td::Status CellStorer::set(td::int32 refcnt, const DataCell &cell) { - return kv_.set(cell.get_hash().as_slice(), td::serialize(RefcntCellStorer(refcnt, cell))); +td::Status CellStorer::set(td::int32 refcnt, const td::Ref &cell, bool as_boc) { + return kv_.set(cell->get_hash().as_slice(), td::serialize(RefcntCellStorer(refcnt, cell, as_boc))); } } // namespace vm diff --git a/crypto/vm/db/CellStorage.h b/crypto/vm/db/CellStorage.h index 26e02330..3106ee16 100644 --- a/crypto/vm/db/CellStorage.h +++ b/crypto/vm/db/CellStorage.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "td/db/KeyValue.h" @@ -45,19 +45,21 @@ class CellLoader { Ref cell_; td::int32 refcnt_{0}; + bool stored_boc_{false}; }; - CellLoader(std::shared_ptr reader); + CellLoader(std::shared_ptr reader, std::function on_load_callback = {}); td::Result load(td::Slice hash, bool need_data, ExtCellCreator &ext_cell_creator); private: std::shared_ptr reader_; + std::function on_load_callback_; }; class CellStorer { public: CellStorer(KeyValue &kv); td::Status erase(td::Slice hash); - td::Status set(td::int32 refcnt, const DataCell &cell); + td::Status set(td::int32 refcnt, const td::Ref &cell, bool as_boc); private: KeyValue &kv_; diff --git a/crypto/vm/db/DynamicBagOfCellsDb.cpp b/crypto/vm/db/DynamicBagOfCellsDb.cpp index 732c1691..1aa4e0f5 100644 --- a/crypto/vm/db/DynamicBagOfCellsDb.cpp +++ b/crypto/vm/db/DynamicBagOfCellsDb.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/db/DynamicBagOfCellsDb.h" #include "vm/db/CellStorage.h" @@ -31,12 +31,6 @@ namespace vm { namespace { -class CellDbReader { - public: - virtual ~CellDbReader() = default; - virtual td::Result> load_cell(td::Slice hash) = 0; -}; - struct DynamicBocExtCellExtra { std::shared_ptr reader; }; @@ -92,6 +86,40 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat TRY_RESULT(loaded_cell, get_cell_info_force(hash).cell->load_cell()); return std::move(loaded_cell.data_cell); } + void load_cell_async(td::Slice hash, std::shared_ptr executor, + td::Promise> promise) override { + auto info = hash_table_.get_if_exists(hash); + if (info && info->sync_with_db) { + TRY_RESULT_PROMISE(promise, loaded_cell, info->cell->load_cell()); + promise.set_result(loaded_cell.data_cell); + return; + } + SimpleExtCellCreator ext_cell_creator(cell_db_reader_); + auto promise_ptr = std::make_shared>>(std::move(promise)); + executor->execute_async( + [executor, loader = *loader_, hash = CellHash::from_slice(hash), db = this, + ext_cell_creator = std::move(ext_cell_creator), promise = std::move(promise_ptr)]() mutable { + TRY_RESULT_PROMISE((*promise), res, loader.load(hash.as_slice(), true, ext_cell_creator)); + if (res.status != CellLoader::LoadResult::Ok) { + promise->set_error(td::Status::Error("cell not found")); + return; + } + Ref cell = res.cell(); + executor->execute_sync([hash, db, res = std::move(res), + ext_cell_creator = std::move(ext_cell_creator)]() mutable { + db->hash_table_.apply(hash.as_slice(), [&](CellInfo &info) { + db->update_cell_info_loaded(info, hash.as_slice(), std::move(res)); + }); + for (auto &ext_cell : ext_cell_creator.get_created_cells()) { + auto ext_cell_hash = ext_cell->get_hash(); + db->hash_table_.apply(ext_cell_hash.as_slice(), [&](CellInfo &info) { + db->update_cell_info_created_ext(info, std::move(ext_cell)); + }); + } + }); + promise->set_result(std::move(cell)); + }); + } CellInfo &get_cell_info_force(td::Slice hash) { return hash_table_.apply(hash, [&](CellInfo &info) { update_cell_info_force(info, hash); }); } @@ -110,8 +138,6 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat if (cell->get_virtualization() != 0) { return; } - //LOG(ERROR) << "INC"; - //CellSlice(cell, nullptr).print_rec(std::cout); to_inc_.push_back(cell); } void dec(const Ref &cell) override { @@ -121,8 +147,6 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat if (cell->get_virtualization() != 0) { return; } - //LOG(ERROR) << "DEC"; - //CellSlice(cell, nullptr).print_rec(std::cout); to_dec_.push_back(cell); } @@ -139,25 +163,20 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat if (is_prepared_for_commit()) { return td::Status::OK(); } - //LOG(ERROR) << "dfs_new_cells_in_db"; for (auto &new_cell : to_inc_) { auto &new_cell_info = get_cell_info(new_cell); dfs_new_cells_in_db(new_cell_info); } - //return td::Status::OK(); - //LOG(ERROR) << "dfs_new_cells"; for (auto &new_cell : to_inc_) { auto &new_cell_info = get_cell_info(new_cell); dfs_new_cells(new_cell_info); } - //LOG(ERROR) << "dfs_old_cells"; for (auto &old_cell : to_dec_) { auto &old_cell_info = get_cell_info(old_cell); dfs_old_cells(old_cell_info); } - //LOG(ERROR) << "save_diff_prepare"; save_diff_prepare(); to_inc_.clear(); @@ -176,6 +195,10 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat return td::Status::OK(); } + std::shared_ptr get_cell_db_reader() override { + return cell_db_reader_; + } + td::Status set_loader(std::unique_ptr loader) override { reset_cell_db_reader(); loader_ = std::move(loader); @@ -187,6 +210,14 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat return td::Status::OK(); } + void set_celldb_compress_depth(td::uint32 value) override { + celldb_compress_depth_ = value; + } + + vm::ExtCellCreator& as_ext_cell_creator() override { + return *this; + } + private: std::unique_ptr loader_; std::vector> to_inc_; @@ -194,12 +225,34 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat CellHashTable hash_table_; std::vector visited_; Stats stats_diff_; + td::uint32 celldb_compress_depth_{0}; static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter() { static auto res = td::NamedThreadSafeCounter::get_default().get_counter("DynamicBagOfCellsDb"); return res; } + class SimpleExtCellCreator : public ExtCellCreator { + public: + explicit SimpleExtCellCreator(std::shared_ptr cell_db_reader) : + cell_db_reader_(std::move(cell_db_reader)) {} + + td::Result> ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth) override { + TRY_RESULT(ext_cell, DynamicBocExtCell::create(PrunnedCellInfo{level_mask, hash, depth}, + DynamicBocExtCellExtra{cell_db_reader_})); + created_cells_.push_back(ext_cell); + return std::move(ext_cell); + } + + std::vector>& get_created_cells() { + return created_cells_; + } + + private: + std::vector> created_cells_; + std::shared_ptr cell_db_reader_; + }; + class CellDbReaderImpl : public CellDbReader, private ExtCellCreator, public std::enable_shared_from_this { @@ -240,7 +293,9 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat return db_->load_cell(hash); } TRY_RESULT(load_result, cell_loader_->load(hash, true, *this)); - CHECK(load_result.status == CellLoader::LoadResult::Ok); + if (load_result.status != CellLoader::LoadResult::Ok) { + return td::Status::Error("cell not found"); + } return std::move(load_result.cell()); } @@ -308,7 +363,6 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat info.was = true; visited_.push_back(&info); } - //LOG(ERROR) << "dfs new " << td::format::escaped(info.cell->hash()); if (info.was_dfs_new_cells) { return; @@ -329,7 +383,6 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat info.was = true; visited_.push_back(&info); } - //LOG(ERROR) << "dfs old " << td::format::escaped(info.cell->hash()); load_cell(info); @@ -350,7 +403,6 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat } void save_diff(CellStorer &storer) { - //LOG(ERROR) << hash_table_.size(); for (auto info_ptr : visited_) { save_cell(*info_ptr, storer); } @@ -359,7 +411,6 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat void save_cell_prepare(CellInfo &info) { if (info.refcnt_diff == 0) { - //CellSlice(info.cell, nullptr).print_rec(std::cout); return; } load_cell(info); @@ -395,17 +446,14 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat if (info.db_refcnt == 0) { CHECK(info.in_db); - //LOG(ERROR) << "ERASE"; - //CellSlice(NoVm(), info.cell).print_rec(std::cout); storer.erase(info.cell->get_hash().as_slice()); info.in_db = false; hash_table_.erase(info.cell->get_hash().as_slice()); guard.dismiss(); } else { - //LOG(ERROR) << "SAVE " << info.db_refcnt; - //CellSlice(NoVm(), info.cell).print_rec(std::cout); auto loaded_cell = info.cell->load_cell().move_as_ok(); - storer.set(info.db_refcnt, *loaded_cell.data_cell); + storer.set(info.db_refcnt, loaded_cell.data_cell, + loaded_cell.data_cell->get_depth() == celldb_compress_depth_ && celldb_compress_depth_ != 0); info.in_db = true; } } @@ -427,7 +475,6 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat CHECK(cell->is_loaded()); vm::CellSlice cs(vm::NoVm{}, cell); // FIXME for (unsigned i = 0; i < cs.size_refs(); i++) { - //LOG(ERROR) << "---> " << td::format::escaped(cell->ref(i)->hash()); f(get_cell_info(cs.prefetch_ref(i))); } } @@ -486,6 +533,33 @@ class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreat info.sync_with_db = true; } + // same as update_cell_info_force, but with cell provided by a caller + void update_cell_info_loaded(CellInfo &info, td::Slice hash, CellLoader::LoadResult res) { + if (info.sync_with_db) { + return; + } + DCHECK(res.status == CellLoader::LoadResult::Ok); + info.cell = std::move(res.cell()); + CHECK(info.cell->get_hash().as_slice() == hash); + info.in_db = true; + info.db_refcnt = res.refcnt(); + info.sync_with_db = true; + } + + // same as update_cell_info_lazy, but with cell provided by a caller + void update_cell_info_created_ext(CellInfo &info, Ref cell) { + if (info.sync_with_db) { + CHECK(info.cell.not_null()); + CHECK(info.cell->get_level_mask() == cell->get_level_mask()); + CHECK(info.cell->get_hash() == cell->get_hash()); + return; + } + if (info.cell.is_null()) { + info.cell = std::move(cell); + info.in_db = true; + } + } + td::Result> create_empty_ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth) { TRY_RESULT(res, DynamicBocExtCell::create(PrunnedCellInfo{level_mask, hash, depth}, DynamicBocExtCellExtra{cell_db_reader_})); diff --git a/crypto/vm/db/DynamicBagOfCellsDb.h b/crypto/vm/db/DynamicBagOfCellsDb.h index 54055a25..fa2b44d2 100644 --- a/crypto/vm/db/DynamicBagOfCellsDb.h +++ b/crypto/vm/db/DynamicBagOfCellsDb.h @@ -14,13 +14,14 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells.h" #include "td/utils/Slice.h" #include "td/utils/Status.h" +#include "td/actor/PromiseFuture.h" namespace vm { class CellLoader; @@ -34,6 +35,12 @@ class ExtCellCreator { virtual td::Result> ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth) = 0; }; +class CellDbReader { + public: + virtual ~CellDbReader() = default; + virtual td::Result> load_cell(td::Slice hash) = 0; +}; + class DynamicBagOfCellsDb { public: virtual ~DynamicBagOfCellsDb() = default; @@ -52,11 +59,25 @@ class DynamicBagOfCellsDb { virtual td::Status prepare_commit() = 0; virtual Stats get_stats_diff() = 0; virtual td::Status commit(CellStorer &) = 0; + virtual std::shared_ptr get_cell_db_reader() = 0; // restart with new loader will also reset stats_diff virtual td::Status set_loader(std::unique_ptr loader) = 0; + virtual void set_celldb_compress_depth(td::uint32 value) = 0; + virtual vm::ExtCellCreator& as_ext_cell_creator() = 0; + static std::unique_ptr create(); + + class AsyncExecutor { + public: + virtual ~AsyncExecutor() {} + virtual void execute_async(std::function f) = 0; + virtual void execute_sync(std::function f) = 0; + }; + + virtual void load_cell_async(td::Slice hash, std::shared_ptr executor, + td::Promise> promise) = 0; }; } // namespace vm diff --git a/crypto/vm/db/StaticBagOfCellsDb.cpp b/crypto/vm/db/StaticBagOfCellsDb.cpp index b69b917d..9c00a98c 100644 --- a/crypto/vm/db/StaticBagOfCellsDb.cpp +++ b/crypto/vm/db/StaticBagOfCellsDb.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/db/StaticBagOfCellsDb.h" @@ -187,9 +187,9 @@ class StaticBagOfCellsDbBaselineImpl : public StaticBagOfCellsDb { } }; -td::Result> StaticBagOfCellsDbBaseline::create(std::unique_ptr data) { - std::string buf(data->size(), '\0'); - TRY_RESULT(slice, data->view(buf, 0)); +td::Result> StaticBagOfCellsDbBaseline::create(td::BlobView data) { + std::string buf(data.size(), '\0'); + TRY_RESULT(slice, data.view(buf, 0)); return create(slice); } @@ -211,7 +211,7 @@ td::Result> StaticBagOfCellsDbBaseline::crea // class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb { public: - explicit StaticBagOfCellsDbLazyImpl(std::unique_ptr data, StaticBagOfCellsDbLazy::Options options) + explicit StaticBagOfCellsDbLazyImpl(td::BlobView data, StaticBagOfCellsDbLazy::Options options) : data_(std::move(data)), options_(std::move(options)) { get_thread_safe_counter().add(1); } @@ -240,7 +240,7 @@ class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb { private: std::atomic should_cache_cells_{true}; - std::unique_ptr data_; + td::BlobView data_; StaticBagOfCellsDbLazy::Options options_; bool has_info_{false}; BagOfCells::Info info_; @@ -313,8 +313,8 @@ class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb { char arr[8]; td::RwMutex::ReadLock guard; if (info_.has_index) { - TRY_RESULT(new_offset_view, data_->view(td::MutableSlice(arr, info_.offset_byte_size), - info_.index_offset + idx * info_.offset_byte_size)); + TRY_RESULT(new_offset_view, data_.view(td::MutableSlice(arr, info_.offset_byte_size), + info_.index_offset + idx * info_.offset_byte_size)); offset_view = new_offset_view; } else { guard = index_data_rw_mutex_.lock_read().move_as_ok(); @@ -331,8 +331,8 @@ class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb { return 0; } char arr[8]; - TRY_RESULT(idx_view, data_->view(td::MutableSlice(arr, info_.ref_byte_size), - info_.roots_offset + root_i * info_.ref_byte_size)); + TRY_RESULT(idx_view, data_.view(td::MutableSlice(arr, info_.ref_byte_size), + info_.roots_offset + root_i * info_.ref_byte_size)); CHECK(idx_view.size() == (size_t)info_.ref_byte_size); return info_.read_ref(idx_view.ubegin()); } @@ -369,17 +369,17 @@ class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb { return td::Status::OK(); } std::string header(1000, '\0'); - TRY_RESULT(header_view, data_->view(td::MutableSlice(header).truncate(data_->size()), 0)) + TRY_RESULT(header_view, data_.view(td::MutableSlice(header).truncate(data_.size()), 0)) auto parse_res = info_.parse_serialized_header(header_view); if (parse_res <= 0) { return td::Status::Error("bag-of-cell error: failed to read header"); } - if (info_.total_size < data_->size()) { + if (info_.total_size < data_.size()) { return td::Status::Error("bag-of-cell error: not enough data"); } if (options_.check_crc32c && info_.has_crc32c) { std::string buf(td::narrow_cast(info_.total_size), '\0'); - TRY_RESULT(data, data_->view(td::MutableSlice(buf), 0)); + TRY_RESULT(data, data_.view(td::MutableSlice(buf), 0)); unsigned crc_computed = td::crc32c(td::Slice{data.ubegin(), data.uend() - 4}); unsigned crc_stored = td::as(data.uend() - 4); if (crc_computed != crc_stored) { @@ -407,8 +407,8 @@ class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb { auto buf_slice = td::MutableSlice(buf.data(), buf.size()); for (; index_i_ <= idx; index_i_++) { auto offset = td::narrow_cast(info_.data_offset + index_offset_); - CHECK(data_->size() >= offset); - TRY_RESULT(cell, data_->view(buf_slice.copy().truncate(data_->size() - offset), offset)); + CHECK(data_.size() >= offset); + TRY_RESULT(cell, data_.view(buf_slice.copy().truncate(data_.size() - offset), offset)); CellSerializationInfo cell_info; TRY_STATUS(cell_info.init(cell, info_.ref_byte_size)); index_offset_ += cell_info.end_offset; @@ -455,7 +455,7 @@ class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb { TRY_RESULT(cell_location, get_cell_location(idx)); auto buf = alloc(cell_location.end - cell_location.begin); - TRY_RESULT(cell_slice, data_->view(buf.as_slice(), cell_location.begin)); + TRY_RESULT(cell_slice, data_.view(buf.as_slice(), cell_location.begin)); TRY_RESULT(res, deserialize_any_cell(idx, cell_slice, cell_location.should_cache)); return std::move(res); } @@ -470,7 +470,7 @@ class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb { TRY_RESULT(cell_location, get_cell_location(idx)); auto buf = alloc(cell_location.end - cell_location.begin); - TRY_RESULT(cell_slice, data_->view(buf.as_slice(), cell_location.begin)); + TRY_RESULT(cell_slice, data_.view(buf.as_slice(), cell_location.begin)); TRY_RESULT(res, deserialize_data_cell(idx, cell_slice, cell_location.should_cache)); return std::move(res); } @@ -528,18 +528,17 @@ class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb { } }; -td::Result> StaticBagOfCellsDbLazy::create(std::unique_ptr data, - Options options) { +td::Result> StaticBagOfCellsDbLazy::create(td::BlobView data, Options options) { return std::make_shared(std::move(data), std::move(options)); } td::Result> StaticBagOfCellsDbLazy::create(td::BufferSlice data, Options options) { - return std::make_shared(vm::BufferSliceBlobView::create(std::move(data)), + return std::make_shared(td::BufferSliceBlobView::create(std::move(data)), std::move(options)); } td::Result> StaticBagOfCellsDbLazy::create(std::string data, Options options) { - return create(BufferSliceBlobView::create(td::BufferSlice(data)), std::move(options)); + return create(td::BufferSliceBlobView::create(td::BufferSlice(data)), std::move(options)); } } // namespace vm diff --git a/crypto/vm/db/StaticBagOfCellsDb.h b/crypto/vm/db/StaticBagOfCellsDb.h index 4a129fb7..03c45ee8 100644 --- a/crypto/vm/db/StaticBagOfCellsDb.h +++ b/crypto/vm/db/StaticBagOfCellsDb.h @@ -14,12 +14,12 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells.h" -#include "vm/db/BlobView.h" +#include "td/db/utils/BlobView.h" #include "td/utils/Status.h" @@ -41,7 +41,7 @@ class StaticBagOfCellsDb : public std::enable_shared_from_this> create(std::unique_ptr data); + static td::Result> create(td::BlobView data); static td::Result> create(td::Slice data); }; @@ -52,7 +52,7 @@ class StaticBagOfCellsDbLazy { } bool check_crc32c{false}; }; - static td::Result> create(std::unique_ptr data, Options options = {}); + static td::Result> create(td::BlobView data, Options options = {}); static td::Result> create(td::BufferSlice data, Options options = {}); static td::Result> create(std::string data, Options options = {}); }; diff --git a/crypto/vm/db/TonDb.cpp b/crypto/vm/db/TonDb.cpp index f478c992..ac69973f 100644 --- a/crypto/vm/db/TonDb.cpp +++ b/crypto/vm/db/TonDb.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/db/TonDb.h" @@ -255,15 +255,15 @@ TonDbTransactionImpl::TonDbTransactionImpl(std::shared_ptr kv) : kv_(s } void TonDbTransactionImpl::begin() { - kv_->begin_transaction(); + kv_->begin_write_batch(); generation_++; } void TonDbTransactionImpl::commit() { - kv_->commit_transaction(); + kv_->commit_write_batch(); reader_.reset(kv_->snapshot().release()); } void TonDbTransactionImpl::abort() { - kv_->abort_transaction(); + kv_->abort_write_batch(); } void TonDbTransactionImpl::clear_cache() { contracts_ = {}; diff --git a/crypto/vm/db/TonDb.h b/crypto/vm/db/TonDb.h index 2f4ba090..dfce3110 100644 --- a/crypto/vm/db/TonDb.h +++ b/crypto/vm/db/TonDb.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cellslice.h" diff --git a/crypto/vm/debugops.cpp b/crypto/vm/debugops.cpp index 5c22740a..3f27de23 100644 --- a/crypto/vm/debugops.cpp +++ b/crypto/vm/debugops.cpp @@ -14,19 +14,23 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/debugops.h" #include "vm/log.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" namespace vm { -bool vm_debug_enabled = true; +bool vm_debug_enabled = false; + +void set_debug_enabled(bool enable_debug) { + vm_debug_enabled = enable_debug; +} int exec_dummy_debug(VmState* st, int args) { VM_LOG(st) << "execute DEBUG " << (args & 0xff); @@ -66,6 +70,9 @@ int compute_len_debug_str(const CellSlice& cs, unsigned args, int pfx_bits) { int exec_dump_stack(VmState* st) { VM_LOG(st) << "execute DUMPSTK"; + if (!vm_debug_enabled) { + return 0; + } Stack& stack = st->get_stack(); int d = stack.depth(); std::cerr << "#DEBUG#: stack(" << d << " values) : "; @@ -84,6 +91,9 @@ int exec_dump_stack(VmState* st) { int exec_dump_value(VmState* st, unsigned arg) { arg &= 15; VM_LOG(st) << "execute DUMP s" << arg; + if (!vm_debug_enabled) { + return 0; + } Stack& stack = st->get_stack(); if ((int)arg < stack.depth()) { std::cerr << "#DEBUG#: s" << arg << " = "; @@ -95,6 +105,43 @@ int exec_dump_value(VmState* st, unsigned arg) { return 0; } +int exec_dump_string(VmState* st) { + VM_LOG(st) << "execute STRDUMP"; + if (!vm_debug_enabled) { + return 0; + } + + Stack& stack = st->get_stack(); + + if (stack.depth() > 0){ + auto cs = stack[0].as_slice(); + + if (cs.not_null()) { // wanted t_slice + auto size = cs->size(); + + if (size % 8 == 0) { + auto cnt = size / 8; + + unsigned char tmp[128]; + cs.write().fetch_bytes(tmp, cnt); + std::string s{tmp, tmp + cnt}; + + std::cerr << "#DEBUG#: " << s << std::endl; + } + else { + std::cerr << "#DEBUG#: slice contains not valid bits count" << std::endl; + } + + } else { + std::cerr << "#DEBUG#: is not a slice" << std::endl; + } + } else { + std::cerr << "#DEBUG#: s0 is absent" << std::endl; + } + + return 0; +} + void register_debug_ops(OpcodeTable& cp0) { using namespace std::placeholders; if (!vm_debug_enabled) { @@ -103,7 +150,9 @@ void register_debug_ops(OpcodeTable& cp0) { } else { // NB: all non-redefined opcodes in fe00..feff should be redirected to dummy debug definitions cp0.insert(OpcodeInstr::mksimple(0xfe00, 16, "DUMPSTK", exec_dump_stack)) - .insert(OpcodeInstr::mkfixedrange(0xfe01, 0xfe20, 16, 8, instr::dump_1c_and(0xff, "DEBUG "), exec_dummy_debug)) + .insert(OpcodeInstr::mkfixedrange(0xfe01, 0xfe14, 16, 8, instr::dump_1c_and(0xff, "DEBUG "), exec_dummy_debug)) + .insert(OpcodeInstr::mksimple(0xfe14, 16,"STRDUMP", exec_dump_string)) + .insert(OpcodeInstr::mkfixedrange(0xfe15, 0xfe20, 16, 8, instr::dump_1c_and(0xff, "DEBUG "), exec_dummy_debug)) .insert(OpcodeInstr::mkfixed(0xfe2, 12, 4, instr::dump_1sr("DUMP"), exec_dump_value)) .insert(OpcodeInstr::mkfixedrange(0xfe30, 0xfef0, 16, 8, instr::dump_1c_and(0xff, "DEBUG "), exec_dummy_debug)) .insert(OpcodeInstr::mkext(0xfef, 12, 4, dump_dummy_debug_str, exec_dummy_debug_str, compute_len_debug_str)); diff --git a/crypto/vm/debugops.h b/crypto/vm/debugops.h index 7c7b27d0..ba3d673f 100644 --- a/crypto/vm/debugops.h +++ b/crypto/vm/debugops.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -25,5 +25,6 @@ class OpcodeTable; extern bool vm_debug_enabled; void register_debug_ops(OpcodeTable& cp0); +void set_debug_enabled(bool enable_debug); } // namespace vm diff --git a/crypto/vm/dict.cpp b/crypto/vm/dict.cpp index 0b48f90e..c79924d0 100644 --- a/crypto/vm/dict.cpp +++ b/crypto/vm/dict.cpp @@ -14,13 +14,14 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/dict.h" #include "vm/cells.h" #include "vm/cellslice.h" #include "vm/stack.hpp" #include "common/bitstring.h" +#include "td/utils/Random.h" #include "td/utils/bits.h" @@ -179,7 +180,7 @@ bool DictionaryBase::append_dict_to_bool(CellBuilder& cb) && { return cb.store_maybe_ref(std::move(root_cell)); } -bool DictionaryBase::append_dict_to_bool(CellBuilder& cb) const& { +bool DictionaryBase::append_dict_to_bool(CellBuilder& cb) const & { return is_valid() && cb.store_maybe_ref(root_cell); } @@ -1308,6 +1309,294 @@ Ref Dictionary::extract_minmax_key_ref(td::BitPtr key_buffer, int key_len, return extract_value_ref(extract_minmax_key(key_buffer, key_len, fetch_max, invert_first)); } +/* + * + * ITERATORS (to be moved into a separate file) + * + */ + +bool DictIterator::prevalidate(int mode) { + if (key_bits_ <= 0 || key_bits_ > Dictionary::max_key_bits) { + reset(); + flags_ &= ~f_valid; + key_bits_ = 0; + return false; + } else { + if (mode >= 0) { + order_ = -(mode & 1) ^ ((mode >> 1) & 1); + } + flags_ |= f_valid; + return true; + } +} + +bool DictIterator::bind(const DictionaryFixed& dict, int do_rewind) { + if (!is_valid() || !is_bound_to(dict)) { + return false; + } + dict_ = &dict; + label_mode_ = dict.label_mode(); + return !do_rewind || rewind(do_rewind < 0); +} + +bool DictIterator::rebind_to(const DictionaryFixed& dict, int do_rewind) { + reset(); + dict_ = &dict; + label_mode_ = dict.label_mode(); + root_ = dict.get_root_cell(); + key_bits_ = dict.get_key_bits(); + flags_ &= 3; + return prevalidate() && (!do_rewind || rewind(do_rewind < 0)); +} + +int DictIterator::compare_keys(td::ConstBitPtr a, td::ConstBitPtr b) const { + if (!key_bits_) { + return 0; + } + int c = (int)*a - (int)*b; + if (c) { + return (order_ & 1) ? -c : c; + } + c = a.compare(b, key_bits_); + return order_ >= 0 ? c : -c; +} + +bool DictIterator::dive(int mode) { + int n = key_bits_, m = 0; + Ref node = path_.empty() ? root_ : path_.back().next; + if (!path_.empty()) { + m = path_.back().pos + 1; + n -= m; + mode >>= 1; + } + // similar to dict_lookup_minmax: create new path down until the leaf + while (1) { + LabelParser label{std::move(node), n, label_mode_}; + int l = label.extract_label_to(key(m)); + assert(l >= 0 && l <= n); + m += l; + n -= l; + if (!n) { + leaf_ = std::move(label.remainder); + return true; + } + if (l) { + mode >>= 1; + } + int bit = mode & 1; + node = label.remainder->prefetch_ref(bit); + auto alt = label.remainder->prefetch_ref(1 - bit); + path_.emplace_back(node, std::move(alt), m, bit); + *key(m++) = (bool)bit; + --n; + mode >>= 1; + } +} + +bool DictIterator::rewind(bool to_end) { + if (!is_valid()) { + return false; + } + if (root_.is_null()) { + return true; + } + auto node = root_; + int k = 0, mode = order_ ^ -(int)to_end; + // NB: can optimize by reusing several first entries of current path_ + while (k < (int)path_.size()) { + auto& pe = path_[k++]; + assert(pe.pos >= 0 && pe.pos < key_bits_); + if (pe.pos) { + mode >>= 1; + } + if (pe.v != (bool)(mode & 1)) { + // went different way at this node before, rotate and stop going down + pe.rotate(key()); + leaf_.clear(); + path_.resize(k); // drop the remainder of the original path after first incorrect branch + return dive(mode); + } + mode >>= 1; + } + return !eof() || dive(mode); +} + +bool DictIterator::next(bool go_back) { + if (!is_valid() || root_.is_null() || eof()) { + return false; + } + leaf_.clear(); + int mode = order_ ^ -(int)go_back; + while (!path_.empty()) { + auto& pe = path_.back(); + int bit = (mode >> (pe.pos > 0)) & 1; + if (pe.v == bit) { + pe.rotate(key()); + return dive(mode); + } + path_.pop_back(); + } + return false; +} + +bool DictIterator::lookup(td::ConstBitPtr pos, int pos_bits, bool strict_after, bool backw) { + if (!is_valid() || root_.is_null() || pos_bits < 0 || pos_bits > key_bits_) { + return false; + } + int fill_mode = -(strict_after ^ backw) ^ order_; + if (!eof()) { + // reuse part of current path + std::size_t bp0 = 0; + int bp; + if (!key().compare(pos, pos_bits, &bp0)) { + bp = pos_bits; + if (bp >= key_bits_) { + // already at the desired element + return !strict_after || next(backw); + } + } else { + bp = (int)bp0; + } + int k = 0; + while (k < (int)path_.size() && path_[k].pos <= bp) { + auto& pe = path_[k]; + if (pe.pos == bp) { + if (bp < pos_bits || pe.v != ((fill_mode >> (bp > 0)) & 1)) { + // rotate the last path element if it branched in incorrect direction + path_[k++].rotate(key()); + } + break; + } + ++k; + } + path_.resize(k); // drop the remainder of the path + } + int m = 0, n = key_bits_; + auto node = path_.empty() ? root_ : path_.back().next; + if (!path_.empty()) { + m = path_.back().pos + 1; // m <= pos_bits + 1 + n -= m; + } + int mode = -backw ^ order_, action = 0; + while (m < pos_bits && !action) { + LabelParser label{std::move(node), n, label_mode_}; + int pfx_len = label.common_prefix_len(pos + m, pos_bits - m); + int l = label.extract_label_to(key() + m); + assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n); + if (pfx_len == pos_bits - m) { + // end of given position prefix + if (strict_after) { + // have to backtrace + action = 2; + break; + } else { + // label has correct prefix, register and dive down + action = 1; + } + } else if (pfx_len < l) { + // all subtree is either smaller or larger than required + if (pos[m + pfx_len] != ((mode >> (int)(m + pfx_len > 0)) & 1)) { + // label smaller than required, have to backtrace + action = 2; + break; + } else { + // label larger than required, register node and dive down + action = 1; + } + } + // if we are here, then either action=1 (dive down activated) + // ... or l = pfx_len < pos_bits - m + // continue going down + m += l; + n -= l; + if (!n) { + // key found in a leaf + leaf_ = std::move(label.remainder); + return true; + } + bool bit = action ? ((mode >> (m > 0)) & 1) : pos[m]; + node = label.remainder->prefetch_ref(bit); + auto alt = label.remainder->prefetch_ref(1 - bit); + path_.emplace_back(node, std::move(alt), m, bit); + *key(m++) = (bool)bit; + --n; + } + if (!action) { + action = (strict_after ? 2 : 1); + } + if (action == 2) { + // have to backtrace to the "next" larger branch + // similar to next() + leaf_.clear(); + while (!path_.empty()) { + auto& pe = path_.back(); + int bit = (mode >> (pe.pos > 0)) & 1; + if (pe.v == bit) { + pe.rotate(key()); + return dive(mode); + } + path_.pop_back(); + } + return false; // eof: no suitable element + } + // action=1, dive down + return dive(mode); +} + +DictIterator DictionaryFixed::null_iterator() { + force_validate(); + return DictIterator{*this}; +} + +DictIterator DictionaryFixed::make_iterator(int mode) { + force_validate(); + DictIterator it{*this, mode}; + it.rewind(); + return it; +} + +DictIterator DictionaryFixed::init_iterator(bool backw, bool invert_first) { + return make_iterator((int)backw + 2 * (int)invert_first); +} + +DictIterator DictionaryFixed::begin() { + return init_iterator(); +} + +DictIterator DictionaryFixed::end() { + return null_iterator(); +} + +DictIterator DictionaryFixed::cbegin() { + return begin(); +} + +DictIterator DictionaryFixed::cend() { + return end(); +} + +DictIterator DictionaryFixed::rbegin() { + return init_iterator(true); +} + +DictIterator DictionaryFixed::rend() { + return null_iterator(); +} + +DictIterator DictionaryFixed::crbegin() { + return rbegin(); +} + +DictIterator DictionaryFixed::crend() { + return rend(); +} + +/* + * + * END (ITERATORS) + * + */ + std::pair, bool> DictionaryFixed::extract_prefix_subdict_internal(Ref dict, td::ConstBitPtr prefix, int prefix_len, bool remove_prefix) const { if (is_empty() || prefix_len <= 0) { @@ -1382,13 +1671,18 @@ Ref DictionaryFixed::extract_prefix_subdict_root(td::ConstBitPtr prefi } std::pair, int> DictionaryFixed::dict_filter(Ref dict, td::BitPtr key, int n, - const DictionaryFixed::filter_func_t& check_leaf) const { + const DictionaryFixed::filter_func_t& check_leaf, + int& skip_rest) const { // std::cerr << "dictionary filter for " << n << "-bit key = " << (key + n - key_bits).to_hex(key_bits - n) // << std::endl; if (dict.is_null()) { // empty dictionary, return unchanged return {{}, 0}; } + if (skip_rest >= 0) { + // either drop subtree completely (if skip_rest>0), or retain it completely (if skip_rest=0) + return {{}, skip_rest}; + } LabelParser label{std::move(dict), n, label_mode()}; assert(label.l_bits >= 0 && label.l_bits <= n); label.extract_label_to(key); @@ -1396,6 +1690,11 @@ std::pair, int> DictionaryFixed::dict_filter(Ref dict, td::BitPt if (label.l_bits == n) { // leaf int res = check_leaf(label.remainder.write(), key - key_bits, key_bits); + if (res >= (1 << 30)) { + // skip all, or retain all + res &= (1 << 30) - 1; + skip_rest = (res ? 0 : (1 << 30)); + } return {{}, res < 0 ? res : !res}; } // fork, process left and right subtrees @@ -1403,19 +1702,20 @@ std::pair, int> DictionaryFixed::dict_filter(Ref dict, td::BitPt key[-1] = false; int delta = label.l_bits + 1; n -= delta; - auto left_res = dict_filter(label.remainder->prefetch_ref(0), key, n, check_leaf); + auto left_res = dict_filter(label.remainder->prefetch_ref(0), key, n, check_leaf, skip_rest); if (left_res.second < 0) { return left_res; } key[-1] = true; - auto right_res = dict_filter(label.remainder->prefetch_ref(1), key, n, check_leaf); + auto right_res = dict_filter(label.remainder->prefetch_ref(1), key, n, check_leaf, skip_rest); if ((left_res.second | right_res.second) <= 0) { // error in right, or both left and right unchanged return right_res; } auto left = left_res.second ? std::move(left_res.first) : label.remainder->prefetch_ref(0); auto right = right_res.second ? std::move(right_res.first) : label.remainder->prefetch_ref(1); - auto changes = left_res.second + right_res.second; + // 2^30 is effectively infinity, meaning that we dropped whole branches with unknown # of nodes + auto changes = ((left_res.second | right_res.second) & (1 << 30)) ? (1 << 30) : left_res.second + right_res.second; label.clear(); if (left.is_null()) { if (right.is_null()) { @@ -1447,8 +1747,9 @@ std::pair, int> DictionaryFixed::dict_filter(Ref dict, td::BitPt int DictionaryFixed::filter(DictionaryFixed::filter_func_t check_leaf) { force_validate(); + int skip_rest = -1; unsigned char buffer[DictionaryFixed::max_key_bytes]; - auto res = dict_filter(get_root_cell(), td::BitPtr{buffer}, key_bits, check_leaf); + auto res = dict_filter(get_root_cell(), td::BitPtr{buffer}, key_bits, check_leaf, skip_rest); if (res.second > 0) { // std::cerr << "after filter (" << res.second << " changes): new augmented dictionary root is:\n"; // vm::load_cell_slice(res.first).print_rec(std::cerr); @@ -1707,7 +2008,7 @@ bool DictionaryFixed::combine_with(DictionaryFixed& dict2) { bool DictionaryFixed::dict_check_for_each(Ref dict, td::BitPtr key_buffer, int n, int total_key_len, const DictionaryFixed::foreach_func_t& foreach_func, - bool invert_first) const { + bool invert_first, bool shuffle) const { if (dict.is_null()) { return true; } @@ -1726,26 +2027,29 @@ bool DictionaryFixed::dict_check_for_each(Ref dict, td::BitPtr key_buffer, key_buffer += l + 1; if (l) { invert_first = false; - } else if (invert_first) { + } + bool invert = shuffle ? td::Random::fast(0, 1) == 1: invert_first; + if (invert) { std::swap(c1, c2); } - key_buffer[-1] = invert_first; + key_buffer[-1] = invert; // recursive check_foreach applied to both children - if (!dict_check_for_each(std::move(c1), key_buffer, n - l - 1, total_key_len, foreach_func)) { + if (!dict_check_for_each(std::move(c1), key_buffer, n - l - 1, total_key_len, foreach_func, false, shuffle)) { return false; } - key_buffer[-1] = !invert_first; - return dict_check_for_each(std::move(c2), key_buffer, n - l - 1, total_key_len, foreach_func); + key_buffer[-1] = !invert; + return dict_check_for_each(std::move(c2), key_buffer, n - l - 1, total_key_len, foreach_func, false, shuffle); } -bool DictionaryFixed::check_for_each(const foreach_func_t& foreach_func, bool invert_first) { +bool DictionaryFixed::check_for_each(const foreach_func_t& foreach_func, bool invert_first, bool shuffle) { force_validate(); if (is_empty()) { return true; } int key_len = get_key_bits(); unsigned char key_buffer[max_key_bytes]; - return dict_check_for_each(get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len, foreach_func, invert_first); + return dict_check_for_each(get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len, foreach_func, invert_first, + shuffle); } static inline bool set_bit(td::BitPtr ptr, bool value = true) { @@ -2240,7 +2544,7 @@ Ref AugmentedDictionary::extract_root() && { return std::move(root); } -bool AugmentedDictionary::append_dict_to_bool(CellBuilder& cb) const& { +bool AugmentedDictionary::append_dict_to_bool(CellBuilder& cb) const & { if (!is_valid()) { return false; } @@ -2309,6 +2613,14 @@ Ref AugmentedDictionary::get_node_extra(Ref cell_ref, int n) co return {}; } +Ref AugmentedDictionary::extract_leaf_value(Ref leaf) const { + if (leaf.not_null() && aug.skip_extra(leaf.write())) { + return std::move(leaf); + } else { + return Ref{}; + } +} + Ref AugmentedDictionary::get_root_extra() const { return get_node_extra(root_cell, key_bits); } @@ -2535,7 +2847,7 @@ bool AugmentedDictionary::set(td::ConstBitPtr key, int key_len, const CellSlice& } auto res = dict_set(get_root_cell(), key, key_len, value, mode); if (res.second) { - //vm::CellSlice cs{vm::NoVm{}, res.first}; + //vm::CellSlice cs{vm::NoVmOrd(), res.first}; //std::cerr << "new augmented dictionary root is:\n"; //cs.print_rec(std::cerr); set_root_cell(std::move(res.first)); diff --git a/crypto/vm/dict.h b/crypto/vm/dict.h index 9bb11be3..c4044963 100644 --- a/crypto/vm/dict.h +++ b/crypto/vm/dict.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/bitstring.h" @@ -171,6 +171,13 @@ class DictionaryBase { } }; +class DictIterator; + +template +std::pair dict_range(T&& dict, bool rev = false, bool sgnd = false) { + return std::pair{std::forward(dict), (int)rev + 2 * (int)sgnd}; +} + class DictionaryFixed : public DictionaryBase { public: typedef std::function filter_func_t; @@ -199,6 +206,9 @@ class DictionaryFixed : public DictionaryBase { static BitSlice integer_key(td::RefInt256 x, unsigned n, bool sgnd = true, unsigned char buffer[128] = 0, bool quiet = false); static bool integer_key_simple(td::RefInt256 x, unsigned n, bool sgnd, td::BitPtr buffer, bool quiet = false); + td::RefInt256 key_as_integer(td::ConstBitPtr key, bool sgnd = false) const { + return td::bits_to_refint(key, key_bits, sgnd); + } bool key_exists(td::ConstBitPtr key, int key_len); bool int_key_exists(long long key); bool uint_key_exists(unsigned long long key); @@ -213,7 +223,7 @@ class DictionaryFixed : public DictionaryBase { int get_common_prefix(td::BitPtr buffer, unsigned buffer_len); bool cut_prefix_subdict(td::ConstBitPtr prefix, int prefix_len, bool remove_prefix = false); Ref extract_prefix_subdict_root(td::ConstBitPtr prefix, int prefix_len, bool remove_prefix = false); - bool check_for_each(const foreach_func_t& foreach_func, bool invert_first = false); + bool check_for_each(const foreach_func_t& foreach_func, bool invert_first = false, bool shuffle = false); int filter(filter_func_t check); bool combine_with(DictionaryFixed& dict2, const combine_func_t& combine_func, int mode = 0); bool combine_with(DictionaryFixed& dict2, const simple_combine_func_t& simple_combine_func, int mode = 0); @@ -221,6 +231,17 @@ class DictionaryFixed : public DictionaryBase { bool scan_diff(DictionaryFixed& dict2, const scan_diff_func_t& diff_func, int check_augm = 0); bool validate_check(const foreach_func_t& foreach_func, bool invert_first = false); bool validate_all(); + DictIterator null_iterator(); + DictIterator init_iterator(bool backw = false, bool invert_first = false); + DictIterator make_iterator(int mode); + DictIterator begin(); + DictIterator end(); + DictIterator cbegin(); + DictIterator cend(); + DictIterator rbegin(); + DictIterator rend(); + DictIterator crbegin(); + DictIterator crend(); template bool key_exists(const T& key) { return key_exists(key.bits(), key.size()); @@ -247,6 +268,9 @@ class DictionaryFixed : public DictionaryBase { virtual int label_mode() const { return dict::LabelParser::chk_all; } + virtual Ref extract_leaf_value(Ref leaf) const { + return leaf; + } virtual Ref finish_create_leaf(CellBuilder& cb, const CellSlice& value) const; virtual Ref finish_create_fork(CellBuilder& cb, Ref c1, Ref c2, int n) const; virtual bool check_fork(CellSlice& cs, Ref c1, Ref c2, int n) const { @@ -259,6 +283,7 @@ class DictionaryFixed : public DictionaryBase { return check_leaf(cs_ref.write(), key, key_len); } bool check_fork_raw(Ref cs_ref, int n) const; + friend class DictIterator; private: std::pair, Ref> dict_lookup_delete(Ref dict, td::ConstBitPtr key, int n) const; @@ -267,8 +292,9 @@ class DictionaryFixed : public DictionaryBase { std::pair, bool> extract_prefix_subdict_internal(Ref dict, td::ConstBitPtr prefix, int prefix_len, bool remove_prefix = false) const; bool dict_check_for_each(Ref dict, td::BitPtr key_buffer, int n, int total_key_len, - const foreach_func_t& foreach_func, bool invert_first = false) const; - std::pair, int> dict_filter(Ref dict, td::BitPtr key, int n, const filter_func_t& check_leaf) const; + const foreach_func_t& foreach_func, bool invert_first = false, bool shuffle = false) const; + std::pair, int> dict_filter(Ref dict, td::BitPtr key, int n, const filter_func_t& check_leaf, + int& skip_rest) const; Ref dict_combine_with(Ref dict1, Ref dict2, td::BitPtr key_buffer, int n, int total_key_len, const combine_func_t& combine_func, int mode = 0, int skip1 = 0, int skip2 = 0) const; bool dict_scan_diff(Ref dict1, Ref dict2, td::BitPtr key_buffer, int n, int total_key_len, @@ -277,6 +303,148 @@ class DictionaryFixed : public DictionaryBase { const foreach_func_t& foreach_func, bool invert_first = false) const; }; +class DictIterator { + const DictionaryFixed* dict_{nullptr}; + Ref root_; + int label_mode_{dict::LabelParser::chk_size}; + int key_bits_; + int flags_; + int order_; + unsigned char key_buffer[DictionaryBase::max_key_bytes]; + bool prevalidate(int mode = -1); + enum { f_valid = 4 }; + + protected: + struct Fork { + Ref next, alt; + int pos; + bool v; + Fork() : pos(-1) { + } + Fork(Ref _next, Ref _alt, int _pos, bool _v) + : next(std::move(_next)), alt(std::move(_alt)), pos(_pos), v(_v) { + } + void rotate(td::BitPtr key) { + std::swap(next, alt); + key[pos] = (v ^= true); + } + }; + std::vector path_; + Ref leaf_; + + td::BitPtr key(int offs = 0) { + return td::BitPtr{key_buffer, offs}; + } + td::ConstBitPtr key(int offs = 0) const { + return td::ConstBitPtr{key_buffer, offs}; + } + td::ConstBitPtr ckey(int offs = 0) const { + return td::ConstBitPtr{key_buffer, offs}; + } + + public: + DictIterator() : key_bits_(0), flags_(0), order_(0) { + } + // mode: 0 = bidir, +4 = fwd only, +8 = back only; +1 = reverse directions, +2 = signed int keys + enum { it_reverse = 1, it_signed = 2 }; + DictIterator(Ref root_cell, int key_bits, int mode = 0) + : root_(std::move(root_cell)), key_bits_(key_bits), flags_(mode >> 2) { + prevalidate(mode & 3); + } + DictIterator(const DictionaryFixed& dict, int mode = 0) + : DictIterator(dict.get_root_cell(), dict.get_key_bits(), mode) { + dict_ = &dict; + label_mode_ = dict.label_mode(); + } + bool is_valid() const { + return flags_ & f_valid; + } + bool eof() const { + return leaf_.is_null(); + } + bool reset() { + dict_ = nullptr; + root_.clear(); + path_.clear(); + leaf_.clear(); + return true; + } + td::ConstBitPtr cur_pos() const { + return eof() ? td::ConstBitPtr{nullptr} : key(); + } + Ref get_root_cell() const { + return root_; + } + int get_key_bits() const { + return key_bits_; + } + bool is_bound() const { + return dict_; + } + bool is_bound_to(const DictionaryFixed& dict) const { + return root_.not_null() == dict.get_root_cell().not_null() && + (root_.not_null() ? root_.get() == dict.get_root_cell().get() : key_bits_ == dict.get_key_bits()); + } + bool bind(const DictionaryFixed& dict, int do_rewind = 0); + bool rebind_to(const DictionaryFixed& dict, int do_rewind = 0); + bool rewind(bool to_end = false); + bool next(bool backw = false); + bool prev() { + return next(true); + } + bool lookup(td::ConstBitPtr pos, int pos_bits, bool strict_after = false, bool backw = false); + template + bool lookup(const T& key, bool strict_after = false, bool backw = false) { + return lookup(key.bits(), key.size(), strict_after, backw); + } + Ref cur_value() const { + return dict_ ? dict_->extract_leaf_value(leaf_) : Ref{}; + } + Ref cur_value_raw() const { + return leaf_; + } + std::pair> operator*() const { + return std::make_pair(cur_pos(), cur_value()); + } + bool bound_to_same(const DictIterator& other) const { + return dict_ && dict_ == other.dict_; + } + bool operator==(const DictIterator& other) const { + return bound_to_same(other) && eof() == other.eof() && (eof() || key().equals(other.key(), key_bits_)); + } + int compare_keys(td::ConstBitPtr a, td::ConstBitPtr b) const; + bool operator<(const DictIterator& other) const { + return bound_to_same(other) && !eof() && (other.eof() || compare_keys(key(), other.key()) < 0); + } + bool operator!=(const DictIterator& other) const { + return !(operator==(other)); + } + bool operator>(const DictIterator& other) const { + return other < *this; + } + DictIterator& operator++() { + next(); + return *this; + } + DictIterator& operator--() { + next(true); + return *this; + } + + private: + bool dive(int mode); +}; + +template +DictIterator begin(std::pair dictm) { + return dictm.first.make_iterator(dictm.second); +} + +template +DictIterator end(std::pair dictm) { + return dictm.first.null_iterator(); +} + class Dictionary final : public DictionaryFixed { public: typedef std::function)> simple_map_func_t; @@ -356,6 +524,9 @@ class Dictionary final : public DictionaryFixed { Ref lookup_set_builder(const T& key, Ref val_ref, SetMode mode = SetMode::Set) { return lookup_set_builder(key.bits(), key.size(), std::move(val_ref), mode); } + auto range(bool rev = false, bool sgnd = false) { + return dict_range(*this, rev, sgnd); + } private: bool check_fork(CellSlice& cs, Ref c1, Ref c2, int n) const override { @@ -454,6 +625,9 @@ class AugmentedDictionary final : public DictionaryFixed { Ref lookup_delete_ref(const T& key) { return lookup_delete_ref(key.bits(), key.size()); } + auto range(bool rev = false, bool sgnd = false) { + return dict_range(*this, rev, sgnd); + } Ref extract_value(Ref value_extra) const; Ref extract_value_ref(Ref value_extra) const; @@ -463,6 +637,7 @@ class AugmentedDictionary final : public DictionaryFixed { private: bool compute_root() const; Ref get_node_extra(Ref cell_ref, int n) const; + Ref extract_leaf_value(Ref leaf) const override; bool check_leaf(CellSlice& cs, td::ConstBitPtr key, int key_len) const override; bool check_fork(CellSlice& cs, Ref c1, Ref c2, int n) const override; Ref finish_create_leaf(CellBuilder& cb, const CellSlice& value) const override; diff --git a/crypto/vm/dictops.cpp b/crypto/vm/dictops.cpp index 9f4faed9..d0ea8daa 100644 --- a/crypto/vm/dictops.cpp +++ b/crypto/vm/dictops.cpp @@ -14,14 +14,14 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/log.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" #include "common/bigint.hpp" #include "common/refint.h" #include "vm/dictops.h" @@ -172,7 +172,8 @@ int exec_load_dict(VmState* st, unsigned args) { } std::string dump_dictop(unsigned args, const char* name) { - std::ostringstream os{"DICT"}; + std::ostringstream os; + os << "DICT"; if (args & 4) { os << (args & 2 ? 'U' : 'I'); } @@ -184,7 +185,8 @@ std::string dump_dictop(unsigned args, const char* name) { } std::string dump_dictop2(unsigned args, const char* name) { - std::ostringstream os{"DICT"}; + std::ostringstream os; + os << "DICT"; if (args & 2) { os << (args & 1 ? 'U' : 'I'); } @@ -193,7 +195,8 @@ std::string dump_dictop2(unsigned args, const char* name) { } std::string dump_subdictop2(unsigned args, const char* name) { - std::ostringstream os{"SUBDICT"}; + std::ostringstream os; + os << "SUBDICT"; if (args & 2) { os << (args & 1 ? 'U' : 'I'); } @@ -210,7 +213,7 @@ int exec_dict_get(VmState* st, unsigned args) { BitSlice key; unsigned char buffer[Dictionary::max_key_bytes]; if (args & 4) { - key = dict.integer_key(stack.pop_int(), n, !(args & 2), buffer, true); + key = dict.integer_key(stack.pop_int_finite(), n, !(args & 2), buffer, true); if (!key.is_valid()) { stack.push_smallint(0); return 0; @@ -250,7 +253,7 @@ int exec_dict_get_optref(VmState* st, unsigned args) { BitSlice key; unsigned char buffer[Dictionary::max_key_bytes]; if (args & 2) { - key = dict.integer_key(stack.pop_int(), n, !(args & 1), buffer, true); + key = dict.integer_key(stack.pop_int_finite(), n, !(args & 1), buffer, true); if (!key.is_valid()) { stack.push_null(); return 0; @@ -377,7 +380,7 @@ int exec_dict_delete(VmState* st, unsigned args) { BitSlice key; unsigned char buffer[Dictionary::max_key_bytes]; if (args & 2) { - key = dict.integer_key(stack.pop_int(), n, !(args & 1), buffer); + key = dict.integer_key(stack.pop_int_finite(), n, !(args & 1), buffer); if (!key.is_valid()) { push_dict(stack, std::move(dict)); stack.push_smallint(0); @@ -404,7 +407,7 @@ int exec_dict_deleteget(VmState* st, unsigned args) { BitSlice key; unsigned char buffer[Dictionary::max_key_bytes]; if (args & 4) { - key = dict.integer_key(stack.pop_int(), n, !(args & 2), buffer); + key = dict.integer_key(stack.pop_int_finite(), n, !(args & 2), buffer); if (!key.is_valid()) { push_dict(stack, std::move(dict)); stack.push_smallint(0); @@ -508,7 +511,8 @@ int exec_dict_getmin(VmState* st, unsigned args) { } std::string dump_dictop_getnear(CellSlice& cs, unsigned args) { - std::ostringstream os{"DICT"}; + std::ostringstream os; + os << "DICT"; if (args & 8) { os << (args & 4 ? 'U' : 'I'); } @@ -588,23 +592,29 @@ int exec_pfx_dict_delete(VmState* st) { int exec_dict_get_exec(VmState* st, unsigned args) { Stack& stack = st->get_stack(); - VM_LOG(st) << "execute DICT" << (args & 1 ? 'U' : 'I') << "GET" << (args & 2 ? "EXEC\n" : "JMP\n"); + VM_LOG(st) << "execute DICT" << (args & 1 ? 'U' : 'I') << "GET" << (args & 2 ? "EXEC" : "JMP") + << (args & 4 ? "Z" : ""); stack.check_underflow(3); int n = stack.pop_smallint_range(Dictionary::max_key_bits); Dictionary dict{stack.pop_maybe_cell(), n}; unsigned char buffer[Dictionary::max_key_bytes]; - dict.integer_key_simple(stack.pop_int(), n, !(args & 1), td::BitPtr{buffer}); - auto value = dict.lookup(td::BitPtr{buffer}, n); - if (value.not_null()) { - Ref cont{true, std::move(value), st->get_cp()}; - return (args & 2) ? st->call(std::move(cont)) : st->jump(std::move(cont)); - } else { - return 0; + auto idx = stack.pop_int_finite(); + if (dict.integer_key_simple(idx, n, !(args & 1), td::BitPtr{buffer}, true)) { + auto value = dict.lookup(td::BitPtr{buffer}, n); + if (value.not_null()) { + Ref cont{true, std::move(value), st->get_cp()}; + return (args & 2) ? st->call(std::move(cont)) : st->jump(std::move(cont)); + } } + // key not found or out of range + if (args & 4) { + stack.push_int(std::move(idx)); + } + return 0; } std::string dump_dict_get_exec(CellSlice& cs, unsigned args) { - return std::string{"DICT"} + (args & 1 ? 'U' : 'I') + "GET" + (args & 2 ? "EXEC" : "JMP"); + return std::string{"DICT"} + (args & 1 ? 'U' : 'I') + "GET" + (args & 2 ? "EXEC" : "JMP") + (args & 4 ? "Z" : ""); } int exec_push_const_dict(VmState* st, CellSlice& cs, unsigned args, int pfx_bits) { @@ -631,8 +641,8 @@ std::string dump_push_const_dict(CellSlice& cs, int pfx_bits, const char* name) cs.advance(pfx_bits - 11); auto slice = cs.fetch_subslice(1, 1); int n = (int)cs.fetch_ulong(10); - std::ostringstream os{name}; - os << ' ' << n << " ("; + std::ostringstream os; + os << name << ' ' << n << " ("; slice->dump_hex(os, false); os << ')'; return os.str(); @@ -720,7 +730,7 @@ int exec_subdict_get(VmState* st, unsigned args) { BitSlice key; unsigned char buffer[Dictionary::max_key_bytes]; if (args & 2) { - key = dict.integer_key(stack.pop_int(), k, !(args & 1), buffer, true); + key = dict.integer_key(stack.pop_int_finite(), k, !(args & 1), buffer, true); } else { key = stack.pop_cellslice()->prefetch_bits(k); } @@ -805,7 +815,8 @@ void register_dictionary_ops(OpcodeTable& cp0) { exec_const_pfx_dict_switch, compute_len_push_const_dict)) .insert(OpcodeInstr::mkfixedrange(0xf4b1, 0xf4b4, 16, 3, std::bind(dump_subdictop2, _2, "GET"), exec_subdict_get)) .insert( - OpcodeInstr::mkfixedrange(0xf4b5, 0xf4b8, 16, 3, std::bind(dump_subdictop2, _2, "RPGET"), exec_subdict_get)); + OpcodeInstr::mkfixedrange(0xf4b5, 0xf4b8, 16, 3, std::bind(dump_subdictop2, _2, "RPGET"), exec_subdict_get)) + .insert(OpcodeInstr::mkfixed(0xf4bc >> 2, 14, 2, dump_dict_get_exec, exec_dict_get_exec)); } } // namespace vm diff --git a/crypto/vm/dictops.h b/crypto/vm/dictops.h index d555cf7a..7d46213f 100644 --- a/crypto/vm/dictops.h +++ b/crypto/vm/dictops.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/crypto/vm/dispatch.cpp b/crypto/vm/dispatch.cpp index 741982dc..53999c7b 100644 --- a/crypto/vm/dispatch.cpp +++ b/crypto/vm/dispatch.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/dispatch.h" #include "vm/excno.hpp" diff --git a/crypto/vm/dispatch.h b/crypto/vm/dispatch.h index 509ecfca..fde0890f 100644 --- a/crypto/vm/dispatch.h +++ b/crypto/vm/dispatch.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include diff --git a/crypto/vm/excno.hpp b/crypto/vm/excno.hpp index a18f890a..3be48168 100644 --- a/crypto/vm/excno.hpp +++ b/crypto/vm/excno.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -45,6 +45,7 @@ const char* get_exception_msg(Excno exc_no); class VmError { Excno exc_no; + bool msg_alloc = false; const char* msg; long long arg; @@ -55,6 +56,18 @@ class VmError { } VmError(Excno _excno, const char* _msg, long long _arg) : exc_no(_excno), msg(_msg), arg(_arg) { } + VmError(Excno _excno, std::string _msg, long long _arg = 0) : exc_no(_excno), msg_alloc(true), arg(_arg) { + msg_alloc = true; + char* p = (char*)malloc(_msg.size() + 1); + memcpy(p, _msg.data(), _msg.size()); + p[_msg.size()] = 0; + msg = p; + } + ~VmError() { + if (msg_alloc) { + free(const_cast(msg)); + } + } int get_errno() const { return static_cast(exc_no); } @@ -64,6 +77,13 @@ class VmError { long long get_arg() const { return arg; } + td::Status as_status() const { + return td::Status::Error(td::Slice{get_msg()}); + } + template + td::Status as_status(T pfx) const { + return td::Status::Error(PSLICE() << pfx << get_msg()); + } }; struct VmNoGas { @@ -77,6 +97,13 @@ struct VmNoGas { operator VmError() const { return VmError{Excno::out_of_gas, "out of gas"}; } + td::Status as_status() const { + return td::Status::Error(td::Slice{get_msg()}); + } + template + td::Status as_status(T pfx) const { + return td::Status::Error(PSLICE() << pfx << get_msg()); + } }; struct VmVirtError { @@ -93,6 +120,13 @@ struct VmVirtError { operator VmError() const { return VmError{Excno::virt_err, "prunned branch", virtualization}; } + td::Status as_status() const { + return td::Status::Error(td::Slice{get_msg()}); + } + template + td::Status as_status(T pfx) const { + return td::Status::Error(PSLICE() << pfx << get_msg()); + } }; struct VmFatal {}; @@ -101,12 +135,12 @@ template auto try_f(F&& f) noexcept -> decltype(f()) { try { return f(); - } catch (vm::VmError error) { - return td::Status::Error(PSLICE() << "Got a vm exception: " << error.get_msg()); - } catch (vm::VmVirtError error) { - return td::Status::Error(PSLICE() << "Got a vm virtualization exception: " << error.get_msg()); - } catch (vm::VmNoGas error) { - return td::Status::Error(PSLICE() << "Got a vm no gas exception: " << error.get_msg()); + } catch (vm::VmError& error) { + return error.as_status("Got a vm exception: "); + } catch (vm::VmVirtError& error) { + return error.as_status("Got a vm virtualization exception: "); + } catch (vm::VmNoGas& error) { + return error.as_status("Got a vm no gas exception: "); } } diff --git a/crypto/vm/fmt.hpp b/crypto/vm/fmt.hpp index 13096fbe..dff7290d 100644 --- a/crypto/vm/fmt.hpp +++ b/crypto/vm/fmt.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/refint.h" diff --git a/crypto/vm/large-boc-serializer.cpp b/crypto/vm/large-boc-serializer.cpp new file mode 100644 index 00000000..fe16b767 --- /dev/null +++ b/crypto/vm/large-boc-serializer.cpp @@ -0,0 +1,412 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#include +#include "vm/boc.h" +#include "vm/boc-writers.h" +#include "vm/cellslice.h" +#include "td/utils/misc.h" + +namespace vm { + +namespace { +// LargeBocSerializer implements serialization of the bag of cells in the standard way +// (equivalent to the implementation in crypto/vm/boc.cpp) +// Changes in this file may require corresponding changes in boc.cpp +class LargeBocSerializer { + public: + using Hash = Cell::Hash; + + explicit LargeBocSerializer(std::shared_ptr reader) : reader(std::move(reader)) {} + + void add_root(Hash root); + td::Status import_cells(); + td::Status serialize(td::FileFd& fd, int mode); + + private: + std::shared_ptr reader; + struct CellInfo { + std::array ref_idx; + int idx; + unsigned short serialized_size; + unsigned char wt; + unsigned char hcnt : 6; + bool should_cache : 1; + bool is_root_cell : 1; + CellInfo(int idx, const std::array& ref_list) : ref_idx(ref_list), idx(idx) { + hcnt = 0; + should_cache = is_root_cell = 0; + } + bool is_special() const { + return !wt; + } + unsigned get_ref_num() const { + for (unsigned i = 0; i < 4; ++i) { + if (ref_idx[i] == -1) { + return i; + } + } + return 4; + } + }; + std::map cells; + std::vector*> cell_list; + struct RootInfo { + RootInfo(Hash hash, int idx) : hash(hash), idx(idx) {} + Hash hash; + int idx; + }; + std::vector roots; + int cell_count = 0, int_refs = 0, int_hashes = 0, top_hashes = 0; + int rv_idx = 0; + unsigned long long data_bytes = 0; + + td::Result import_cell(Hash hash, int depth = 0); + void reorder_cells(); + int revisit(int cell_idx, int force = 0); + td::uint64 compute_sizes(int mode, int& r_size, int& o_size); +}; + +void LargeBocSerializer::add_root(Hash root) { + roots.emplace_back(root, -1); +} + +td::Status LargeBocSerializer::import_cells() { + for (auto& root : roots) { + TRY_RESULT(idx, import_cell(root.hash)); + root.idx = idx; + } + reorder_cells(); + CHECK(!cell_list.empty()); + return td::Status::OK(); +} + +td::Result LargeBocSerializer::import_cell(Hash hash, int depth) { + if (depth > Cell::max_depth) { + return td::Status::Error("error while importing a cell into a bag of cells: cell depth too large"); + } + auto it = cells.find(hash); + if (it != cells.end()) { + it->second.should_cache = true; + return it->second.idx; + } + TRY_RESULT(cell, reader->load_cell(hash.as_slice())); + if (cell->get_virtualization() != 0) { + return td::Status::Error( + "error while importing a cell into a bag of cells: cell has non-zero virtualization level"); + } + CellSlice cs(std::move(cell)); + std::array refs; + std::fill(refs.begin(), refs.end(), -1); + DCHECK(cs.size_refs() <= 4); + unsigned sum_child_wt = 1; + for (unsigned i = 0; i < cs.size_refs(); i++) { + TRY_RESULT(ref, import_cell(cs.prefetch_ref(i)->get_hash(), depth + 1)); + refs[i] = ref; + sum_child_wt += cell_list[ref]->second.wt; + ++int_refs; + } + auto dc = cs.move_as_loaded_cell().data_cell; + auto res = cells.emplace(hash, CellInfo(cell_count, refs)); + DCHECK(res.second); + cell_list.push_back(&*res.first); + CellInfo& dc_info = res.first->second; + dc_info.wt = (unsigned char)std::min(0xffU, sum_child_wt); + unsigned hcnt = dc->get_level_mask().get_hashes_count(); + DCHECK(hcnt <= 4); + dc_info.hcnt = (unsigned char)hcnt; + TRY_RESULT(serialized_size, td::narrow_cast_safe(dc->get_serialized_size())); + data_bytes += dc_info.serialized_size = serialized_size; + return cell_count++; +} + +void LargeBocSerializer::reorder_cells() { + for (auto ptr : cell_list) { + ptr->second.idx = -1; + } + int_hashes = 0; + for (int i = cell_count - 1; i >= 0; --i) { + CellInfo& dci = cell_list[i]->second; + int s = dci.get_ref_num(), c = s, sum = BagOfCells::max_cell_whs - 1, mask = 0; + for (int j = 0; j < s; ++j) { + CellInfo& dcj = cell_list[dci.ref_idx[j]]->second; + int limit = (BagOfCells::max_cell_whs - 1 + j) / s; + if (dcj.wt <= limit) { + sum -= dcj.wt; + --c; + mask |= (1 << j); + } + } + if (c) { + for (int j = 0; j < s; ++j) { + if (!(mask & (1 << j))) { + CellInfo& dcj = cell_list[dci.ref_idx[j]]->second; + int limit = sum++ / c; + if (dcj.wt > limit) { + dcj.wt = (unsigned char)limit; + } + } + } + } + } + for (int i = 0; i < cell_count; i++) { + CellInfo& dci = cell_list[i]->second; + int s = dci.get_ref_num(), sum = 1; + for (int j = 0; j < s; ++j) { + sum += cell_list[dci.ref_idx[j]]->second.wt; + } + DCHECK(sum <= BagOfCells::max_cell_whs); + if (sum <= dci.wt) { + dci.wt = (unsigned char)sum; + } else { + dci.wt = 0; + int_hashes += dci.hcnt; + } + } + top_hashes = 0; + for (auto& root_info : roots) { + auto& cell_info = cell_list[root_info.idx]->second; + if (cell_info.is_root_cell) { + cell_info.is_root_cell = true; + if (cell_info.wt) { + top_hashes += cell_info.hcnt; + } + } + } + if (cell_count > 0) { + rv_idx = 0; + + for (const auto& root_info : roots) { + revisit(root_info.idx, 0); + revisit(root_info.idx, 1); + } + for (const auto& root_info : roots) { + revisit(root_info.idx, 2); + } + for (auto& root_info : roots) { + root_info.idx = cell_list[root_info.idx]->second.idx; + } + + DCHECK(rv_idx == cell_count); + for (int i = 0; i < cell_count; ++i) { + while (cell_list[i]->second.idx != i) { + std::swap(cell_list[i], cell_list[cell_list[i]->second.idx]); + } + } + } +} + +int LargeBocSerializer::revisit(int cell_idx, int force) { + DCHECK(cell_idx >= 0 && cell_idx < cell_count); + CellInfo& dci = cell_list[cell_idx]->second; + if (dci.idx >= 0) { + return dci.idx; + } + if (!force) { + // previsit + if (dci.idx != -1) { + // already previsited or visited + return dci.idx; + } + int n = dci.get_ref_num(); + for (int j = n - 1; j >= 0; --j) { + int child_idx = dci.ref_idx[j]; + // either previsit or visit child, depending on whether it is special + revisit(dci.ref_idx[j], cell_list[child_idx]->second.is_special()); + } + return dci.idx = -2; // mark as previsited + } + if (force > 1) { + // time to allocate + auto i = dci.idx = rv_idx++; + return i; + } + if (dci.idx == -3) { + // already visited + return dci.idx; + } + if (dci.is_special()) { + // if current cell is special, previsit it first + revisit(cell_idx, 0); + } + // visit children + int n = dci.get_ref_num(); + for (int j = n - 1; j >= 0; --j) { + revisit(dci.ref_idx[j], 1); + } + // allocate children + for (int j = n - 1; j >= 0; --j) { + dci.ref_idx[j] = revisit(dci.ref_idx[j], 2); + } + return dci.idx = -3; // mark as visited (and all children processed) +} + +td::uint64 LargeBocSerializer::compute_sizes(int mode, int& r_size, int& o_size) { + using Mode = BagOfCells::Mode; + int rs = 0, os = 0; + if (roots.empty() || !data_bytes) { + r_size = o_size = 0; + return 0; + } + while (cell_count >= (1LL << (rs << 3))) { + rs++; + } + td::uint64 hashes = + (((mode & Mode::WithTopHash) ? top_hashes : 0) + ((mode & Mode::WithIntHashes) ? int_hashes : 0)) * + (Cell::hash_bytes + Cell::depth_bytes); + td::uint64 data_bytes_adj = data_bytes + (unsigned long long)int_refs * rs + hashes; + td::uint64 max_offset = (mode & Mode::WithCacheBits) ? data_bytes_adj * 2 : data_bytes_adj; + while (max_offset >= (1ULL << (os << 3))) { + os++; + } + if (rs > 4 || os > 8) { + r_size = o_size = 0; + return 0; + } + r_size = rs; + o_size = os; + return data_bytes_adj; +} + +td::Status LargeBocSerializer::serialize(td::FileFd& fd, int mode) { + using Mode = BagOfCells::Mode; + BagOfCells::Info info; + if ((mode & Mode::WithCacheBits) && !(mode & Mode::WithIndex)) { + return td::Status::Error("invalid flags"); + } + auto data_bytes_adj = compute_sizes(mode, info.ref_byte_size, info.offset_byte_size); + if (data_bytes_adj == 0) { + return td::Status::Error("no cells to serialize"); + } + info.valid = true; + info.has_crc32c = mode & Mode::WithCRC32C; + info.has_index = mode & Mode::WithIndex; + info.has_cache_bits = mode & Mode::WithCacheBits; + info.root_count = (int)roots.size(); + info.cell_count = cell_count; + info.absent_count = 0; + int crc_size = info.has_crc32c ? 4 : 0; + info.roots_offset = 4 + 1 + 1 + 3 * info.ref_byte_size + info.offset_byte_size; + info.index_offset = info.roots_offset + info.root_count * info.ref_byte_size; + info.data_offset = info.index_offset; + if (info.has_index) { + info.data_offset += (long long)cell_count * info.offset_byte_size; + } + info.magic = BagOfCells::Info::boc_generic; + info.data_size = data_bytes_adj; + info.total_size = info.data_offset + data_bytes_adj + crc_size; + auto res = td::narrow_cast_safe(info.total_size); + if (res.is_error()) { + return td::Status::Error("bag of cells is too large"); + } + + boc_writers::FileWriter writer{fd, (size_t) info.total_size}; + auto store_ref = [&](unsigned long long value) { + writer.store_uint(value, info.ref_byte_size); + }; + auto store_offset = [&](unsigned long long value) { + writer.store_uint(value, info.offset_byte_size); + }; + + writer.store_uint(info.magic, 4); + + td::uint8 byte{0}; + if (info.has_index) { + byte |= 1 << 7; + } + if (info.has_crc32c) { + byte |= 1 << 6; + } + if (info.has_cache_bits) { + byte |= 1 << 5; + } + byte |= (td::uint8)info.ref_byte_size; + writer.store_uint(byte, 1); + + writer.store_uint(info.offset_byte_size, 1); + store_ref(cell_count); + store_ref(roots.size()); + store_ref(0); + store_offset(info.data_size); + for (const auto& root_info : roots) { + int k = cell_count - 1 - root_info.idx; + DCHECK(k >= 0 && k < cell_count); + store_ref(k); + } + DCHECK(writer.position() == info.index_offset); + DCHECK((unsigned)cell_count == cell_list.size()); + if (info.has_index) { + std::size_t offs = 0; + for (int i = cell_count - 1; i >= 0; --i) { + const auto& dc_info = cell_list[i]->second; + bool with_hash = (mode & Mode::WithIntHashes) && !dc_info.wt; + if (dc_info.is_root_cell && (mode & Mode::WithTopHash)) { + with_hash = true; + } + int hash_size = 0; + if (with_hash) { + hash_size = (Cell::hash_bytes + Cell::depth_bytes) * dc_info.hcnt; + } + offs += dc_info.serialized_size + hash_size + dc_info.get_ref_num() * info.ref_byte_size; + auto fixed_offset = offs; + if (info.has_cache_bits) { + fixed_offset = offs * 2 + dc_info.should_cache; + } + store_offset(fixed_offset); + } + DCHECK(offs == info.data_size); + } + DCHECK(writer.position() == info.data_offset); + size_t keep_position = writer.position(); + for (int i = 0; i < cell_count; ++i) { + auto hash = cell_list[cell_count - 1 - i]->first; + const auto& dc_info = cell_list[cell_count - 1 - i]->second; + TRY_RESULT(dc, reader->load_cell(hash.as_slice())); + bool with_hash = (mode & Mode::WithIntHashes) && !dc_info.wt; + if (dc_info.is_root_cell && (mode & Mode::WithTopHash)) { + with_hash = true; + } + unsigned char buf[256]; + int s = dc->serialize(buf, 256, with_hash); + writer.store_bytes(buf, s); + DCHECK(dc->size_refs() == dc_info.get_ref_num()); + unsigned ref_num = dc_info.get_ref_num(); + for (unsigned j = 0; j < ref_num; ++j) { + int k = cell_count - 1 - dc_info.ref_idx[j]; + DCHECK(k > i && k < cell_count); + store_ref(k); + } + } + DCHECK(writer.position() - keep_position == info.data_size); + if (info.has_crc32c) { + unsigned crc = writer.get_crc32(); + writer.store_uint(td::bswap32(crc), 4); + } + DCHECK(writer.empty()); + return writer.finalize(); +} +} + +td::Status std_boc_serialize_to_file_large(std::shared_ptr reader, Cell::Hash root_hash, + td::FileFd& fd, int mode) { + CHECK(reader != nullptr) + LargeBocSerializer serializer(reader); + serializer.add_root(root_hash); + TRY_STATUS(serializer.import_cells()); + return serializer.serialize(fd, mode); +} + +} diff --git a/crypto/vm/log.h b/crypto/vm/log.h index e0631411..dc0199b5 100644 --- a/crypto/vm/log.h +++ b/crypto/vm/log.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -31,8 +31,14 @@ namespace vm { struct VmLog { td::LogInterface *log_interface{td::log_interface}; td::LogOptions log_options{td::log_options}; - enum { DumpStack = 2 }; + enum { DumpStack = 2, ExecLocation = 4, GasRemaining = 8, DumpStackVerbose = 16 }; int log_mask{1}; + static VmLog Null() { + VmLog res; + res.log_options.level = 0; + res.log_mask = 0; + return res; + } }; template diff --git a/crypto/vm/memo.cpp b/crypto/vm/memo.cpp new file mode 100644 index 00000000..015bab6d --- /dev/null +++ b/crypto/vm/memo.cpp @@ -0,0 +1,48 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2020 Telegram Systems LLP +*/ +#include "vm/memo.h" +#include "vm/excno.hpp" +#include "vm/vm.h" + +namespace vm { +using td::Ref; + +bool FakeVmStateLimits::register_op(int op_units) { + bool ok = (ops_remaining -= op_units) >= 0; + if (!ok && !quiet) { + throw VmError{Excno::out_of_gas, "too many operations"}; + } + return ok; +} + +Ref DummyVmState::load_library(td::ConstBitPtr hash) { + std::unique_ptr tmp_ctx; + // install temporary dummy vm state interface to prevent charging for cell load operations during library lookup + VmStateInterface::Guard guard{global_version >= 4 ? tmp_ctx.get() : VmStateInterface::get()}; + for (const auto& lib_collection : libraries) { + auto lib = lookup_library_in(hash, lib_collection); + if (lib.not_null()) { + return lib; + } + } + missing_library = td::Bits256{hash}; + return {}; +} + +} // namespace vm diff --git a/crypto/vm/memo.h b/crypto/vm/memo.h new file mode 100644 index 00000000..db6dc8e2 --- /dev/null +++ b/crypto/vm/memo.h @@ -0,0 +1,57 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2020 Telegram Systems LLP +*/ +#pragma once +#include "common/refcnt.hpp" +#include "vm/cells.h" +#include "vm/vmstate.h" +#include "td/utils/optional.h" + +namespace vm { +using td::Ref; + +class FakeVmStateLimits : public VmStateInterface { + long long ops_remaining; + bool quiet; + + public: + FakeVmStateLimits(long long max_ops = 1LL << 62, bool _quiet = true) : ops_remaining(max_ops), quiet(_quiet) { + } + bool register_op(int op_units = 1) override; +}; + +class DummyVmState : public VmStateInterface { + public: + explicit DummyVmState(std::vector> libraries, int global_version = ton::SUPPORTED_VERSION) + : libraries(std::move(libraries)), global_version(global_version) { + } + Ref load_library(td::ConstBitPtr hash) override; + int get_global_version() const override { + return global_version; + } + td::optional get_missing_library() const { + return missing_library; + } + + private: + std::vector> libraries; + int global_version; + td::optional missing_library; +}; + +} // namespace vm diff --git a/crypto/vm/opctable.cpp b/crypto/vm/opctable.cpp index fbdc2578..0521a763 100644 --- a/crypto/vm/opctable.cpp +++ b/crypto/vm/opctable.cpp @@ -14,14 +14,14 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include #include "vm/opctable.h" #include "vm/cellslice.h" #include "vm/excno.hpp" -#include "vm/continuation.h" +#include "vm/vm.h" #include #include #include @@ -357,32 +357,32 @@ using namespace std::placeholders; dump_arg_instr_func_t dump_1sr(std::string prefix, std::string suffix) { return [prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << 's' << (args & 15) << suffix; + std::ostringstream os; + os << prefix << 's' << (args & 15) << suffix; return os.str(); }; } dump_arg_instr_func_t dump_1sr_l(std::string prefix, std::string suffix) { return [prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << 's' << (args & 255) << suffix; + std::ostringstream os; + os << prefix << 's' << (args & 255) << suffix; return os.str(); }; } dump_arg_instr_func_t dump_2sr(std::string prefix, std::string suffix) { return [prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << 's' << ((args >> 4) & 15) << ",s" << (args & 15) << suffix; + std::ostringstream os; + os << prefix << 's' << ((args >> 4) & 15) << ",s" << (args & 15) << suffix; return os.str(); }; } dump_arg_instr_func_t dump_2sr_adj(unsigned adj, std::string prefix, std::string suffix) { return [adj, prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << 's' << (int)((args >> 4) & 15) - (int)((adj >> 4) & 15) << ",s" << (int)(args & 15) - (int)(adj & 15) + std::ostringstream os; + os << prefix << 's' << (int)((args >> 4) & 15) - (int)((adj >> 4) & 15) << ",s" << (int)(args & 15) - (int)(adj & 15) << suffix; return os.str(); }; @@ -390,16 +390,16 @@ dump_arg_instr_func_t dump_2sr_adj(unsigned adj, std::string prefix, std::string dump_arg_instr_func_t dump_3sr(std::string prefix, std::string suffix) { return [prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << 's' << ((args >> 8) & 15) << ",s" << ((args >> 4) & 15) << ",s" << (args & 15) << suffix; + std::ostringstream os; + os << prefix << 's' << ((args >> 8) & 15) << ",s" << ((args >> 4) & 15) << ",s" << (args & 15) << suffix; return os.str(); }; } dump_arg_instr_func_t dump_3sr_adj(unsigned adj, std::string prefix, std::string suffix) { return [adj, prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << 's' << (int)((args >> 8) & 15) - (int)((adj >> 8) & 15) << ",s" + std::ostringstream os; + os << prefix << 's' << (int)((args >> 8) & 15) - (int)((adj >> 8) & 15) << ",s" << (int)((args >> 4) & 15) - (int)((adj >> 4) & 15) << ",s" << (int)(args & 15) - (int)(adj & 15) << suffix; return os.str(); }; @@ -407,44 +407,64 @@ dump_arg_instr_func_t dump_3sr_adj(unsigned adj, std::string prefix, std::string dump_arg_instr_func_t dump_1c(std::string prefix, std::string suffix) { return [prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << (args & 15) << suffix; + std::ostringstream os; + os << prefix << (args & 15) << suffix; return os.str(); }; } dump_arg_instr_func_t dump_1c_l_add(int adj, std::string prefix, std::string suffix) { return [adj, prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << (int)(args & 255) + adj << suffix; + std::ostringstream os; + os << prefix << (int)(args & 255) + adj << suffix; return os.str(); }; } dump_arg_instr_func_t dump_1c_and(unsigned mask, std::string prefix, std::string suffix) { return [mask, prefix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << (args & mask) << suffix; + std::ostringstream os; + os << prefix << (args & mask) << suffix; return os.str(); }; } dump_arg_instr_func_t dump_2c(std::string prefix, std::string interfix, std::string suffix) { return [prefix, interfix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << ((args >> 4) & 15) << interfix << (args & 15) << suffix; + std::ostringstream os; + os << prefix << ((args >> 4) & 15) << interfix << (args & 15) << suffix; return os.str(); }; } dump_arg_instr_func_t dump_2c_add(unsigned add, std::string prefix, std::string interfix, std::string suffix) { return [add, prefix, interfix, suffix](CellSlice&, unsigned args) -> std::string { - std::ostringstream os{prefix}; - os << ((args >> 4) & 15) + ((add >> 4) & 15) << interfix << (args & 15) + (add & 15) << suffix; + std::ostringstream os; + os << prefix << ((args >> 4) & 15) + ((add >> 4) & 15) << interfix << (args & 15) + (add & 15) << suffix; return os.str(); }; } } // namespace instr +OpcodeInstr* OpcodeInstr::require_version(int required_version) { + return new OpcodeInstrWithVersion(this, required_version); +} + +int OpcodeInstrWithVersion::dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const { + if (st->get_global_version() < required_version) { + st->consume_gas(gas_per_instr); + throw VmError{Excno::inv_opcode, "invalid opcode", opcode}; + } + return instr->dispatch(st, cs, opcode, bits); +} + +std::string OpcodeInstrWithVersion::dump(CellSlice& cs, unsigned opcode, unsigned bits) const { + return instr->dump(cs, opcode, bits); +} + +int OpcodeInstrWithVersion::instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const { + return instr->instr_len(cs, opcode, bits); +} + } // namespace vm diff --git a/crypto/vm/opctable.h b/crypto/vm/opctable.h index d441fd78..34d2ef0a 100644 --- a/crypto/vm/opctable.h +++ b/crypto/vm/opctable.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/dispatch.h" @@ -57,6 +57,9 @@ class OpcodeInstr { std::pair get_opcode_range() const { return {min_opcode, max_opcode}; } + + OpcodeInstr* require_version(int required_version); + //static OpcodeInstr* mksimple(unsigned opcode, unsigned opc_bits, std::string _name, exec_instr_func_t exec); static OpcodeInstr* mksimple(unsigned opcode, unsigned opc_bits, std::string _name, exec_simple_instr_func_t exec); static OpcodeInstr* mkfixed(unsigned opcode, unsigned opc_bits, unsigned arg_bits, dump_arg_instr_func_t dump, @@ -188,4 +191,19 @@ class OpcodeInstrExt : public OpcodeInstr { int instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const override; }; +class OpcodeInstrWithVersion : public OpcodeInstr { + public: + OpcodeInstrWithVersion() = delete; + OpcodeInstrWithVersion(OpcodeInstr* instr, int required_version) : + OpcodeInstr(instr->get_opcode_min(), instr->get_opcode_max()), instr(instr), required_version(required_version) { + } + ~OpcodeInstrWithVersion() override = default; + int dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const override; + std::string dump(CellSlice& cs, unsigned opcode, unsigned bits) const override; + int instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const override; + private: + OpcodeInstr* instr; + int required_version; +}; + } // namespace vm diff --git a/crypto/vm/stack.cpp b/crypto/vm/stack.cpp index 7327df60..69760524 100644 --- a/crypto/vm/stack.cpp +++ b/crypto/vm/stack.cpp @@ -14,12 +14,15 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/stack.hpp" #include "vm/continuation.h" #include "vm/box.hpp" #include "vm/atom.h" +#include "vm/vmstate.h" +#include "vm/boc.h" +#include "td/utils/misc.h" namespace td { template class td::Cnt; @@ -80,7 +83,7 @@ std::string StackEntry::to_lisp_string() const { return std::move(os).str(); } -void StackEntry::dump(std::ostream& os) const { +void StackEntry::dump(std::ostream& os, bool verbose) const { switch (tp) { case t_null: os << "(null)"; @@ -89,15 +92,36 @@ void StackEntry::dump(std::ostream& os) const { os << dec_string(as_int()); break; case t_cell: - os << "C{" << static_cast>(ref)->get_hash().to_hex() << "}"; + if (ref.not_null()) { + if (verbose) { + std::string serialized = "???"; + auto boc = vm::std_boc_serialize(as_cell()); + if (boc.is_ok()) { + serialized = td::buffer_to_hex(boc.move_as_ok().as_slice()); + } + os << "C{" << serialized << "}"; + } else { + os << "C{" << *as_cell() << "}"; + } + } else { + os << "C{null}"; + } break; case t_builder: - os << "BC{" << static_cast>(ref)->to_hex() << "}"; + if (ref.not_null()) { + os << "BC{" << *as_builder() << "}"; + } else { + os << "BC{null}"; + } break; case t_slice: { - os << "CS{"; - static_cast>(ref)->dump(os, 1, false); - os << '}'; + if (ref.not_null()) { + os << "CS{"; + static_cast>(ref)->dump(os, 1, false); + os << '}'; + } else { + os << "CS{null}"; + } break; } case t_string: @@ -136,12 +160,24 @@ void StackEntry::dump(std::ostream& os) const { os << "Object{" << (const void*)&*ref << "}"; break; } + case t_vmcont: { + if (ref.not_null()) { + if (verbose) { + os << "Cont{" << *as_cont() << "}"; + } else { + os << "Cont{" << as_cont()->type() << "}"; + } + } else { + os << "Cont{null}"; + } + break; + } default: os << "???"; } } -void StackEntry::print_list(std::ostream& os) const { +void StackEntry::print_list(std::ostream& os, bool verbose) const { switch (tp) { case t_null: os << "()"; @@ -150,7 +186,7 @@ void StackEntry::print_list(std::ostream& os) const { const auto& tuple = *static_cast>(ref); if (is_list()) { os << '('; - tuple[0].print_list(os); + tuple[0].print_list(os, verbose); print_list_tail(os, &tuple[1]); break; } @@ -159,7 +195,7 @@ void StackEntry::print_list(std::ostream& os) const { os << "[]"; } else if (n == 1) { os << "["; - tuple[0].print_list(os); + tuple[0].print_list(os, verbose); os << "]"; } else { os << "["; @@ -168,14 +204,14 @@ void StackEntry::print_list(std::ostream& os) const { if (c++) { os << " "; } - entry.print_list(os); + entry.print_list(os, verbose); } os << ']'; } break; } default: - dump(os); + dump(os, verbose); } } @@ -221,7 +257,7 @@ StackEntry::StackEntry(Ref stack_ref) : ref(std::move(stack_ref)), tp(t_s StackEntry::StackEntry(Ref cont_ref) : ref(std::move(cont_ref)), tp(t_vmcont) { } -Ref StackEntry::as_cont() const & { +Ref StackEntry::as_cont() const& { return as(); } @@ -232,7 +268,7 @@ Ref StackEntry::as_cont() && { StackEntry::StackEntry(Ref box_ref) : ref(std::move(box_ref)), tp(t_box) { } -Ref StackEntry::as_box() const & { +Ref StackEntry::as_box() const& { return as(); } @@ -251,7 +287,7 @@ StackEntry::StackEntry(std::vector&& tuple_components) : ref(Ref{true, std::move(tuple_components)}), tp(t_tuple) { } -Ref StackEntry::as_tuple() const & { +Ref StackEntry::as_tuple() const& { return as(); } @@ -259,7 +295,7 @@ Ref StackEntry::as_tuple() && { return move_as(); } -Ref StackEntry::as_tuple_range(unsigned max_len, unsigned min_len) const & { +Ref StackEntry::as_tuple_range(unsigned max_len, unsigned min_len) const& { auto t = as(); if (t.not_null() && t->size() <= max_len && t->size() >= min_len) { return t; @@ -280,7 +316,7 @@ Ref StackEntry::as_tuple_range(unsigned max_len, unsigned min_len) && { StackEntry::StackEntry(Ref atom_ref) : ref(std::move(atom_ref)), tp(t_atom) { } -Ref StackEntry::as_atom() const & { +Ref StackEntry::as_atom() const& { return as(); } @@ -288,7 +324,32 @@ Ref StackEntry::as_atom() && { return move_as(); } -const StackEntry& tuple_index(const Tuple& tup, unsigned idx) { +bool StackEntry::for_each_scalar(const std::function& func) const { + auto t = as(); + if (t.not_null()) { + for (const auto& entry : *t) { + if (!entry.for_each_scalar(func)) { + return false; + } + } + return true; + } else { + return func(*this); + } +} + +void StackEntry::for_each_scalar(const std::function& func) const { + auto t = as(); + if (t.not_null()) { + for (const auto& entry : *t) { + entry.for_each_scalar(func); + } + } else { + func(*this); + } +} + +const StackEntry& tuple_index(const Ref& tup, unsigned idx) { if (idx >= tup->size()) { throw VmError{Excno::range_chk, "tuple index out of range"}; } @@ -555,7 +616,7 @@ void Stack::push_int_quiet(td::RefInt256 val, bool quiet) { if (!quiet) { throw VmError{Excno::int_ov}; } else if (val->is_valid()) { - push(td::RefInt256{true}); + push(td::make_refint()); return; } } @@ -591,7 +652,7 @@ void Stack::push_builder(Ref cb) { } void Stack::push_smallint(long long val) { - push(td::RefInt256{true, val}); + push(td::make_refint(val)); } void Stack::push_bool(bool val) { @@ -649,12 +710,12 @@ void Stack::dump(std::ostream& os, int mode) const { os << " [ "; if (mode & 2) { for (const auto& x : stack) { - x.print_list(os); + x.print_list(os, mode & 4); os << ' '; } } else { for (const auto& x : stack) { - x.dump(os); + x.dump(os, mode & 4); os << ' '; } } @@ -671,6 +732,21 @@ void Stack::push_maybe_cellslice(Ref cs) { push_maybe(std::move(cs)); } +bool Stack::for_each_scalar(const std::function& func) const { + for (const auto& v : stack) { + if (!v.for_each_scalar(func)) { + return false; + } + } + return true; +} + +void Stack::for_each_scalar(const std::function& func) const { + for (const auto& v : stack) { + v.for_each_scalar(func); + } +} + /* * * SERIALIZE/DESERIALIZE STACK VALUES @@ -678,6 +754,10 @@ void Stack::push_maybe_cellslice(Ref cs) { */ bool StackEntry::serialize(vm::CellBuilder& cb, int mode) const { + auto* vsi = VmStateInterface::get(); + if (vsi && !vsi->register_op()) { + return false; + } switch (tp) { case t_null: return cb.store_long_bool(0, 8); // vm_stk_null#00 = VmStackValue; @@ -739,6 +819,10 @@ bool StackEntry::serialize(vm::CellBuilder& cb, int mode) const { } bool StackEntry::deserialize(CellSlice& cs, int mode) { + auto* vsi = VmStateInterface::get(); + if (vsi && !vsi->register_op()) { + return false; + } clear(); int t = (mode & 0xf000) ? ((mode >> 12) & 15) : (int)cs.prefetch_ulong(8); switch (t) { @@ -754,7 +838,7 @@ bool StackEntry::deserialize(CellSlice& cs, int mode) { t = (int)cs.prefetch_ulong(16) & 0x1ff; if (t == 0xff) { // vm_stk_nan#02ff = VmStackValue; - return cs.advance(16) && set_int(td::RefInt256{true}); + return cs.advance(16) && set_int(td::make_refint()); } else { // vm_stk_int#0201_ value:int257 = VmStackValue; td::RefInt256 val; @@ -824,7 +908,7 @@ bool StackEntry::deserialize(CellSlice& cs, int mode) { return false; } } else if (n == 1) { - return cs.have_refs() && t[0].deserialize(cs.fetch_ref(), mode); + return cs.have_refs() && t[0].deserialize(cs.fetch_ref(), mode) && set(t_tuple, std::move(tuple)); } return set(t_tuple, std::move(tuple)); } @@ -843,26 +927,40 @@ bool StackEntry::deserialize(Ref cell, int mode) { } bool Stack::serialize(vm::CellBuilder& cb, int mode) const { - // vm_stack#_ depth:(## 24) stack:(VmStackList depth) = VmStack; - unsigned n = depth(); - if (!cb.store_ulong_rchk_bool(n, 24)) { // vm_stack#_ depth:(## 24) + auto* vsi = VmStateInterface::get(); + if (vsi && !vsi->register_op()) { return false; } - if (!n) { - return true; - } - vm::CellBuilder cb2; - Ref rest = cb2.finalize(); // vm_stk_nil#_ = VmStackList 0; - for (unsigned i = 0; i < n - 1; i++) { - // vm_stk_cons#_ {n:#} rest:^(VmStackList n) tos:VmStackValue = VmStackList (n + 1); - if (!(cb2.store_ref_bool(std::move(rest)) && stack[i].serialize(cb2, mode) && cb2.finalize_to(rest))) { + try { + // vm_stack#_ depth:(## 24) stack:(VmStackList depth) = VmStack; + unsigned n = depth(); + if (!cb.store_ulong_rchk_bool(n, 24)) { // vm_stack#_ depth:(## 24) return false; } + if (!n) { + return true; + } + vm::CellBuilder cb2; + Ref rest = cb2.finalize(); // vm_stk_nil#_ = VmStackList 0; + for (unsigned i = 0; i < n - 1; i++) { + // vm_stk_cons#_ {n:#} rest:^(VmStackList n) tos:VmStackValue = VmStackList (n + 1); + if (!(cb2.store_ref_bool(std::move(rest)) && stack[i].serialize(cb2, mode) && cb2.finalize_to(rest))) { + return false; + } + } + return cb.store_ref_bool(std::move(rest)) && stack[n - 1].serialize(cb, mode); + } catch (CellBuilder::CellCreateError) { + return false; + } catch (CellBuilder::CellWriteError) { + return false; } - return cb.store_ref_bool(std::move(rest)) && stack[n - 1].serialize(cb, mode); } bool Stack::deserialize(vm::CellSlice& cs, int mode) { + auto* vsi = VmStateInterface::get(); + if (vsi && !vsi->register_op()) { + return false; + } clear(); // vm_stack#_ depth:(## 24) stack:(VmStackList depth) = VmStack; int n; diff --git a/crypto/vm/stack.hpp b/crypto/vm/stack.hpp index 5174ad7f..6a52e4a2 100644 --- a/crypto/vm/stack.hpp +++ b/crypto/vm/stack.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -35,6 +35,8 @@ #include "td/utils/Span.h" +#include + namespace td { extern template class td::Cnt; extern template class td::Ref>; @@ -101,6 +103,9 @@ class StackEntry { } StackEntry(td::RefInt256 int_ref) : ref(std::move(int_ref)), tp(t_int) { } + StackEntry(Ref> str_ref, bool bytes = false) + : ref(std::move(str_ref)), tp(bytes ? t_bytes : t_string) { + } StackEntry(std::string str, bool bytes = false) : ref(), tp(bytes ? t_bytes : t_string) { ref = Ref>{true, std::move(str)}; } @@ -147,6 +152,15 @@ class StackEntry { bool is_atom() const { return tp == t_atom; } + bool is_int() const { + return tp == t_int; + } + bool is_cell() const { + return tp == t_cell; + } + bool is_null() const { + return tp == t_null; + } bool is(int wanted) const { return tp == wanted; } @@ -177,7 +191,7 @@ class StackEntry { private: static bool is_list(const StackEntry* se); template - Ref dynamic_as() const & { + Ref dynamic_as() const& { return tp == tag ? static_cast>(ref) : td::Ref{}; } template @@ -189,7 +203,7 @@ class StackEntry { return tp == tag ? static_cast>(std::move(ref)) : td::Ref{}; } template - Ref as() const & { + Ref as() const& { return tp == tag ? Ref{td::static_cast_ref(), ref} : td::Ref{}; } template @@ -221,31 +235,31 @@ class StackEntry { return ref; } } - td::RefInt256 as_int() const & { + td::RefInt256 as_int() const& { return as(); } td::RefInt256 as_int() && { return move_as(); } - Ref as_cell() const & { + Ref as_cell() const& { return as(); } Ref as_cell() && { return move_as(); } - Ref as_builder() const & { + Ref as_builder() const& { return as(); } Ref as_builder() && { return move_as(); } - Ref as_slice() const & { + Ref as_slice() const& { return as(); } Ref as_slice() && { return move_as(); } - Ref as_cont() const &; + Ref as_cont() const&; Ref as_cont() &&; Ref> as_string_ref() const { return as, t_string>(); @@ -260,24 +274,26 @@ class StackEntry { std::string as_bytes() const { return tp == t_bytes ? *as_bytes_ref() : ""; } - Ref as_box() const &; + Ref as_box() const&; Ref as_box() &&; - Ref as_tuple() const &; + Ref as_tuple() const&; Ref as_tuple() &&; - Ref as_tuple_range(unsigned max_len = 255, unsigned min_len = 0) const &; + Ref as_tuple_range(unsigned max_len = 255, unsigned min_len = 0) const&; Ref as_tuple_range(unsigned max_len = 255, unsigned min_len = 0) &&; - Ref as_atom() const &; + Ref as_atom() const&; Ref as_atom() &&; template - Ref as_object() const & { + Ref as_object() const& { return dynamic_as(); } template Ref as_object() && { return dynamic_move_as(); } - void dump(std::ostream& os) const; - void print_list(std::ostream& os) const; + bool for_each_scalar(const std::function& func) const; + void for_each_scalar(const std::function& func) const; + void dump(std::ostream& os, bool verbose = false) const; + void print_list(std::ostream& os, bool verbose = false) const; std::string to_string() const; std::string to_lisp_string() const; @@ -289,7 +305,7 @@ inline void swap(StackEntry& se1, StackEntry& se2) { se1.swap(se2); } -const StackEntry& tuple_index(const Tuple& tup, unsigned idx); +const StackEntry& tuple_index(const Ref& tup, unsigned idx); StackEntry tuple_extend_index(const Ref& tup, unsigned idx); unsigned tuple_extend_set_index(Ref& tup, unsigned idx, StackEntry&& value, bool force = false); @@ -307,7 +323,7 @@ class Stack : public td::CntObject { Stack(const Stack& old_stack, unsigned copy_elem, unsigned skip_top); Stack(Stack&& old_stack, unsigned copy_elem, unsigned skip_top); td::CntObject* make_copy() const override { - std::cerr << "copy stack at " << (const void*)this << " (" << depth() << " entries)\n"; + //std::cerr << "copy stack at " << (const void*)this << " (" << depth() << " entries)\n"; return new Stack{stack}; } void push_from_stack(const Stack& old_stack, unsigned copy_elem, unsigned skip_top = 0); @@ -348,6 +364,10 @@ class Stack : public td::CntObject { void pop_many(int count) { stack.resize(stack.size() - count); } + void pop_many(int count, int offs) { + std::move(stack.cend() - offs, stack.cend(), stack.end() - (count + offs)); + pop_many(count); + } void drop_bottom(int count) { std::move(stack.cbegin() + count, stack.cend(), stack.begin()); pop_many(count); @@ -429,6 +449,12 @@ class Stack : public td::CntObject { } return *this; } + std::vector extract_contents() const& { + return stack; + } + std::vector extract_contents() && { + return std::move(stack); + } template const Stack& check_underflow(Args... args) const { if (!at_least(args...)) { @@ -530,7 +556,9 @@ class Stack : public td::CntObject { push(std::move(val)); } } - // mode: +1 = add eoln, +2 = Lisp-style lists + bool for_each_scalar(const std::function& func) const; + void for_each_scalar(const std::function& func) const; + // mode: +1 = add eoln, +2 = Lisp-style lists, +4 = serialized bocs void dump(std::ostream& os, int mode = 1) const; bool serialize(vm::CellBuilder& cb, int mode = 0) const; bool deserialize(vm::CellSlice& cs, int mode = 0); diff --git a/crypto/vm/stackops.cpp b/crypto/vm/stackops.cpp index 7fdbcba0..c8180f1a 100644 --- a/crypto/vm/stackops.cpp +++ b/crypto/vm/stackops.cpp @@ -14,14 +14,14 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/log.h" #include "vm/stackops.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" namespace vm { @@ -75,8 +75,8 @@ std::string dump_xchg(CellSlice&, unsigned args) { if (!x || x >= y) { return ""; } - std::ostringstream os{"XCHG s"}; - os << x << ",s" << y; + std::ostringstream os; + os << "XCHG s" << x << ",s" << y; return os.str(); } @@ -92,7 +92,7 @@ int exec_xchg1(VmState* st, unsigned args) { int exec_dup(VmState* st) { Stack& stack = st->get_stack(); - VM_LOG(st) << "execute DUP\n"; + VM_LOG(st) << "execute DUP"; stack.check_underflow(1); stack.push(stack.fetch(0)); return 0; @@ -100,7 +100,7 @@ int exec_dup(VmState* st) { int exec_over(VmState* st) { Stack& stack = st->get_stack(); - VM_LOG(st) << "execute OVER\n"; + VM_LOG(st) << "execute OVER"; stack.check_underflow(2); stack.push(stack.fetch(1)); return 0; @@ -126,7 +126,7 @@ int exec_push_l(VmState* st, unsigned args) { int exec_drop(VmState* st) { Stack& stack = st->get_stack(); - VM_LOG(st) << "execute DROP\n"; + VM_LOG(st) << "execute DROP"; stack.check_underflow(1); stack.pop(); return 0; @@ -134,7 +134,7 @@ int exec_drop(VmState* st) { int exec_nip(VmState* st) { Stack& stack = st->get_stack(); - VM_LOG(st) << "execute NIP\n"; + VM_LOG(st) << "execute NIP"; stack.check_underflow(2); stack.pop(stack[1]); return 0; @@ -301,9 +301,7 @@ int exec_blkswap(VmState* st, unsigned args) { Stack& stack = st->get_stack(); VM_LOG(st) << "execute BLKSWAP " << x << ',' << y; stack.check_underflow(x + y); - std::reverse(stack.from_top(x + y), stack.from_top(y)); - std::reverse(stack.from_top(y), stack.top()); - std::reverse(stack.from_top(x + y), stack.top()); + std::rotate(stack.from_top(x + y), stack.from_top(y), stack.top()); return 0; } @@ -379,6 +377,15 @@ int exec_blkdrop(VmState* st, unsigned args) { return 0; } +int exec_blkdrop2(VmState* st, unsigned args) { + int x = ((args >> 4) & 15), y = (args & 15); + Stack& stack = st->get_stack(); + VM_LOG(st) << "execute BLKDROP2 " << x << ',' << y; + stack.check_underflow(x + y); + stack.pop_many(x, y); + return 0; +} + int exec_blkpush(VmState* st, unsigned args) { int x = ((args >> 4) & 15), y = (args & 15); Stack& stack = st->get_stack(); @@ -394,7 +401,7 @@ int exec_pick(VmState* st) { Stack& stack = st->get_stack(); VM_LOG(st) << "execute PICK\n"; stack.check_underflow(1); - int x = stack.pop_smallint_range(255); + int x = stack.pop_smallint_range(st->get_global_version() >= 4 ? (1 << 30) - 1 : 255); stack.check_underflow_p(x); stack.push(stack.fetch(x)); return 0; @@ -404,8 +411,9 @@ int exec_roll(VmState* st) { Stack& stack = st->get_stack(); VM_LOG(st) << "execute ROLL\n"; stack.check_underflow(1); - int x = stack.pop_smallint_range(255); + int x = stack.pop_smallint_range(st->get_global_version() >= 4 ? (1 << 30) - 1 : 255); stack.check_underflow_p(x); + st->consume_gas(std::max(x - 255, 0)); while (--x >= 0) { swap(stack[x], stack[x + 1]); } @@ -416,8 +424,9 @@ int exec_rollrev(VmState* st) { Stack& stack = st->get_stack(); VM_LOG(st) << "execute ROLLREV\n"; stack.check_underflow(1); - int x = stack.pop_smallint_range(255); + int x = stack.pop_smallint_range(st->get_global_version() >= 4 ? (1 << 30) - 1 : 255); stack.check_underflow_p(x); + st->consume_gas(std::max(x - 255, 0)); for (int i = 0; i < x; i++) { swap(stack[i], stack[i + 1]); } @@ -428,13 +437,14 @@ int exec_blkswap_x(VmState* st) { Stack& stack = st->get_stack(); VM_LOG(st) << "execute BLKSWX\n"; stack.check_underflow(2); - int y = stack.pop_smallint_range(255); - int x = stack.pop_smallint_range(255); + int y = stack.pop_smallint_range(st->get_global_version() >= 4 ? (1 << 30) - 1 : 255); + int x = stack.pop_smallint_range(st->get_global_version() >= 4 ? (1 << 30) - 1 : 255); stack.check_underflow(x + y); if (x > 0 && y > 0) { - std::reverse(stack.from_top(x + y), stack.from_top(y)); - std::reverse(stack.from_top(y), stack.top()); - std::reverse(stack.from_top(x + y), stack.top()); + if (st->get_global_version() >= 4) { + st->consume_gas(std::max(x + y - 255, 0)); + } + std::rotate(stack.from_top(x + y), stack.from_top(y), stack.top()); } return 0; } @@ -443,9 +453,10 @@ int exec_reverse_x(VmState* st) { Stack& stack = st->get_stack(); VM_LOG(st) << "execute REVX\n"; stack.check_underflow(2); - int y = stack.pop_smallint_range(255); - int x = stack.pop_smallint_range(255); + int y = stack.pop_smallint_range(st->get_global_version() >= 4 ? (1 << 30) - 1 : 255); + int x = stack.pop_smallint_range(st->get_global_version() >= 4 ? (1 << 30) - 1 : 255); stack.check_underflow(x + y); + st->consume_gas(std::max(x - 255, 0)); std::reverse(stack.from_top(x + y), stack.from_top(y)); return 0; } @@ -454,7 +465,7 @@ int exec_drop_x(VmState* st) { Stack& stack = st->get_stack(); VM_LOG(st) << "execute DROPX\n"; stack.check_underflow(1); - int x = stack.pop_smallint_range(255); + int x = stack.pop_smallint_range(st->get_global_version() >= 4 ? (1 << 30) - 1 : 255); stack.check_underflow(x); stack.pop_many(x); return 0; @@ -473,7 +484,7 @@ int exec_xchg_x(VmState* st) { Stack& stack = st->get_stack(); VM_LOG(st) << "execute XCHGX\n"; stack.check_underflow(1); - int x = stack.pop_smallint_range(255); + int x = stack.pop_smallint_range(st->get_global_version() >= 4 ? (1 << 30) - 1 : 255); stack.check_underflow_p(x); swap(stack[0], stack[x]); return 0; @@ -490,7 +501,7 @@ int exec_chkdepth(VmState* st) { Stack& stack = st->get_stack(); VM_LOG(st) << "execute CHKDEPTH\n"; stack.check_underflow(1); - int x = stack.pop_smallint_range(255); + int x = stack.pop_smallint_range(st->get_global_version() >= 4 ? (1 << 30) - 1 : 255); stack.check_underflow(x); return 0; } @@ -499,10 +510,11 @@ int exec_onlytop_x(VmState* st) { Stack& stack = st->get_stack(); VM_LOG(st) << "execute ONLYTOPX\n"; stack.check_underflow(1); - int x = stack.pop_smallint_range(255); + int x = stack.pop_smallint_range(st->get_global_version() >= 4 ? (1 << 30) - 1 : 255); stack.check_underflow(x); int n = stack.depth(), d = n - x; if (d > 0) { + st->consume_gas(std::max(x - 255, 0)); for (int i = n - 1; i >= d; i--) { stack[i] = std::move(stack[i - d]); } @@ -515,7 +527,7 @@ int exec_only_x(VmState* st) { Stack& stack = st->get_stack(); VM_LOG(st) << "execute ONLYX\n"; stack.check_underflow(1); - int x = stack.pop_smallint_range(255); + int x = stack.pop_smallint_range(st->get_global_version() >= 4 ? (1 << 30) - 1 : 255); stack.check_underflow(x); stack.pop_many(stack.depth() - x); return 0; @@ -570,7 +582,8 @@ void register_stack_ops(OpcodeTable& cp0) { .insert(OpcodeInstr::mksimple(0x68, 8, "DEPTH", exec_depth)) .insert(OpcodeInstr::mksimple(0x69, 8, "CHKDEPTH", exec_chkdepth)) .insert(OpcodeInstr::mksimple(0x6a, 8, "ONLYTOPX", exec_onlytop_x)) - .insert(OpcodeInstr::mksimple(0x6b, 8, "ONLYX", exec_only_x)); + .insert(OpcodeInstr::mksimple(0x6b, 8, "ONLYX", exec_only_x)) + .insert(OpcodeInstr::mkfixedrange(0x6c10, 0x6d00, 16, 8, instr::dump_2c("BLKDROP2 ", ","), exec_blkdrop2)); } } // namespace vm diff --git a/crypto/vm/stackops.h b/crypto/vm/stackops.h index 7ae940d3..27921a55 100644 --- a/crypto/vm/stackops.h +++ b/crypto/vm/stackops.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/crypto/vm/tonops.cpp b/crypto/vm/tonops.cpp index 5dcd0d54..4b2d1734 100644 --- a/crypto/vm/tonops.cpp +++ b/crypto/vm/tonops.cpp @@ -14,17 +14,28 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/tonops.h" #include "vm/log.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" #include "vm/dict.h" +#include "vm/boc.h" #include "Ed25519.h" +#include "vm/Hasher.h" +#include "block/block-auto.h" +#include "block/block-parse.h" +#include "crypto/ellcurve/secp256k1.h" +#include "crypto/ellcurve/p256.h" + +#include "openssl/digest.hpp" +#include +#include "bls.h" +#include "mc-config.h" namespace vm { @@ -57,6 +68,10 @@ int exec_set_gas_generic(VmState* st, long long new_gas_limit) { throw VmNoGas{}; } st->change_gas_limit(new_gas_limit); + if (st->get_stop_on_accept_message()) { + VM_LOG(st) << "External message is accepted, stopping TVM"; + return st->jump(td::Ref{true, 0}); + } return 0; } @@ -75,9 +90,15 @@ int exec_set_gas_limit(VmState* st) { return exec_set_gas_generic(st, gas); } +int exec_gas_consumed(VmState* st) { + VM_LOG(st) << "execute GASCONSUMED"; + st->get_stack().push_smallint(st->gas_consumed()); + return 0; +} + int exec_commit(VmState* st) { VM_LOG(st) << "execute COMMIT"; - st->commit(); + st->force_commit(); return 0; } @@ -85,6 +106,7 @@ void register_basic_gas_ops(OpcodeTable& cp0) { using namespace std::placeholders; cp0.insert(OpcodeInstr::mksimple(0xf800, 16, "ACCEPT", exec_accept)) .insert(OpcodeInstr::mksimple(0xf801, 16, "SETGASLIMIT", exec_set_gas_limit)) + .insert(OpcodeInstr::mksimple(0xf807, 16, "GASCONSUMED", exec_gas_consumed)->require_version(4)) .insert(OpcodeInstr::mksimple(0xf80f, 16, "COMMIT", exec_commit)); } @@ -92,17 +114,35 @@ void register_ton_gas_ops(OpcodeTable& cp0) { using namespace std::placeholders; } +static const StackEntry& get_param(VmState* st, unsigned idx) { + auto tuple = st->get_c7(); + auto t1 = tuple_index(tuple, 0).as_tuple_range(255); + if (t1.is_null()) { + throw VmError{Excno::type_chk, "intermediate value is not a tuple"}; + } + return tuple_index(t1, idx); +} + +// ConfigParams: 18 (only one entry), 19, 20, 21, 24, 25, 43 +static td::Ref get_unpacked_config_tuple(VmState* st) { + auto tuple = st->get_c7(); + auto t1 = tuple_index(tuple, 0).as_tuple_range(255); + if (t1.is_null()) { + throw VmError{Excno::type_chk, "intermediate value is not a tuple"}; + } + auto t2 = tuple_index(t1, 14).as_tuple_range(255); + if (t2.is_null()) { + throw VmError{Excno::type_chk, "intermediate value is not a tuple"}; + } + return t2; +} + int exec_get_param(VmState* st, unsigned idx, const char* name) { if (name) { VM_LOG(st) << "execute " << name; } Stack& stack = st->get_stack(); - auto tuple = st->get_c7(); - auto t1 = tuple_index(*tuple, 0).as_tuple_range(255); - if (t1.is_null()) { - throw VmError{Excno::type_chk, "intermediate value is not a tuple"}; - } - stack.push(tuple_index(*t1, idx)); + stack.push(get_param(st, idx)); return 0; } @@ -189,6 +229,129 @@ int exec_set_global_var(VmState* st) { return exec_set_global_common(st, args); } +int exec_get_prev_blocks_info(VmState* st, unsigned idx, const char* name) { + idx &= 3; + VM_LOG(st) << "execute " << name; + Stack& stack = st->get_stack(); + auto tuple = st->get_c7(); + auto t1 = tuple_index(tuple, 0).as_tuple_range(255); + if (t1.is_null()) { + throw VmError{Excno::type_chk, "intermediate value is not a tuple"}; + } + auto t2 = tuple_index(t1, 13).as_tuple_range(255); + if (t2.is_null()) { + throw VmError{Excno::type_chk, "intermediate value is not a tuple"}; + } + stack.push(tuple_index(t2, idx)); + return 0; +} + +int exec_get_global_id(VmState* st) { + VM_LOG(st) << "execute GLOBALID"; + if (st->get_global_version() >= 6) { + Ref cs = tuple_index(get_unpacked_config_tuple(st), 1).as_slice(); + if (cs.is_null()) { + throw VmError{Excno::type_chk, "intermediate value is not a slice"}; + } + if (cs->size() < 32) { + throw VmError{Excno::cell_und, "invalid global-id config"}; + } + st->get_stack().push_smallint(cs->prefetch_long(32)); + } else { + Ref config = get_param(st, 19).as_cell(); + if (config.is_null()) { + throw VmError{Excno::type_chk, "intermediate value is not a cell"}; + } + Dictionary config_dict{std::move(config), 32}; + Ref cell = config_dict.lookup_ref(td::BitArray<32>{19}); + if (cell.is_null()) { + throw VmError{Excno::unknown, "invalid global-id config"}; + } + CellSlice cs = load_cell_slice(cell); + if (cs.size() < 32) { + throw VmError{Excno::unknown, "invalid global-id config"}; + } + st->get_stack().push_smallint(cs.fetch_long(32)); + } + return 0; +} + +int exec_get_gas_fee(VmState* st) { + VM_LOG(st) << "execute GETGASFEE"; + Stack& stack = st->get_stack(); + bool is_masterchain = stack.pop_bool(); + td::uint64 gas = stack.pop_long_range(std::numeric_limits::max(), 0); + block::GasLimitsPrices prices = util::get_gas_prices(get_unpacked_config_tuple(st), is_masterchain); + stack.push_int(prices.compute_gas_price(gas)); + return 0; +} + +int exec_get_storage_fee(VmState* st) { + VM_LOG(st) << "execute GETSTORAGEFEE"; + Stack& stack = st->get_stack(); + bool is_masterchain = stack.pop_bool(); + td::int64 delta = stack.pop_long_range(std::numeric_limits::max(), 0); + td::uint64 bits = stack.pop_long_range(std::numeric_limits::max(), 0); + td::uint64 cells = stack.pop_long_range(std::numeric_limits::max(), 0); + td::optional maybe_prices = + util::get_storage_prices(get_unpacked_config_tuple(st)); + stack.push_int(util::calculate_storage_fee(maybe_prices, is_masterchain, delta, bits, cells)); + return 0; +} + +int exec_get_forward_fee(VmState* st) { + VM_LOG(st) << "execute GETFORWARDFEE"; + Stack& stack = st->get_stack(); + bool is_masterchain = stack.pop_bool(); + td::uint64 bits = stack.pop_long_range(std::numeric_limits::max(), 0); + td::uint64 cells = stack.pop_long_range(std::numeric_limits::max(), 0); + block::MsgPrices prices = util::get_msg_prices(get_unpacked_config_tuple(st), is_masterchain); + stack.push_int(prices.compute_fwd_fees256(cells, bits)); + return 0; +} + +int exec_get_precompiled_gas(VmState* st) { + VM_LOG(st) << "execute GETPRECOMPILEDGAS"; + Stack& stack = st->get_stack(); + stack.push(get_param(st, 16)); + return 0; +} + +int exec_get_original_fwd_fee(VmState* st) { + VM_LOG(st) << "execute GETORIGINALFWDFEE"; + Stack& stack = st->get_stack(); + bool is_masterchain = stack.pop_bool(); + td::RefInt256 fwd_fee = stack.pop_int_finite(); + if (fwd_fee->sgn() < 0) { + throw VmError{Excno::range_chk, "fwd_fee is negative"}; + } + block::MsgPrices prices = util::get_msg_prices(get_unpacked_config_tuple(st), is_masterchain); + stack.push_int(td::muldiv(fwd_fee, td::make_refint(1 << 16), td::make_refint((1 << 16) - prices.first_frac))); + return 0; +} + +int exec_get_gas_fee_simple(VmState* st) { + VM_LOG(st) << "execute GETGASFEESIMPLE"; + Stack& stack = st->get_stack(); + bool is_masterchain = stack.pop_bool(); + td::uint64 gas = stack.pop_long_range(std::numeric_limits::max(), 0); + block::GasLimitsPrices prices = util::get_gas_prices(get_unpacked_config_tuple(st), is_masterchain); + stack.push_int(td::rshift(td::make_refint(prices.gas_price) * gas, 16, 1)); + return 0; +} + +int exec_get_forward_fee_simple(VmState* st) { + VM_LOG(st) << "execute GETFORWARDFEESIMPLE"; + Stack& stack = st->get_stack(); + bool is_masterchain = stack.pop_bool(); + td::uint64 bits = stack.pop_long_range(std::numeric_limits::max(), 0); + td::uint64 cells = stack.pop_long_range(std::numeric_limits::max(), 0); + block::MsgPrices prices = util::get_msg_prices(get_unpacked_config_tuple(st), is_masterchain); + stack.push_int(td::rshift(td::make_refint(prices.bit_price) * bits + td::make_refint(prices.cell_price) * cells, 16, + 1)); // divide by 2^16 with ceil rounding + return 0; +} + void register_ton_config_ops(OpcodeTable& cp0) { using namespace std::placeholders; cp0.insert(OpcodeInstr::mkfixedrange(0xf820, 0xf823, 16, 4, instr::dump_1c("GETPARAM "), exec_get_var_param)) @@ -199,10 +362,25 @@ void register_ton_config_ops(OpcodeTable& cp0) { .insert(OpcodeInstr::mksimple(0xf827, 16, "BALANCE", std::bind(exec_get_param, _1, 7, "BALANCE"))) .insert(OpcodeInstr::mksimple(0xf828, 16, "MYADDR", std::bind(exec_get_param, _1, 8, "MYADDR"))) .insert(OpcodeInstr::mksimple(0xf829, 16, "CONFIGROOT", std::bind(exec_get_param, _1, 9, "CONFIGROOT"))) - .insert(OpcodeInstr::mkfixedrange(0xf82a, 0xf830, 16, 4, instr::dump_1c("GETPARAM "), exec_get_var_param)) + .insert(OpcodeInstr::mksimple(0xf82a, 16, "MYCODE", std::bind(exec_get_param, _1, 10, "MYCODE"))) + .insert(OpcodeInstr::mksimple(0xf82b, 16, "INCOMINGVALUE", std::bind(exec_get_param, _1, 11, "INCOMINGVALUE"))) + .insert(OpcodeInstr::mksimple(0xf82c, 16, "STORAGEFEES", std::bind(exec_get_param, _1, 12, "STORAGEFEES"))) + .insert(OpcodeInstr::mksimple(0xf82d, 16, "PREVBLOCKSINFOTUPLE", std::bind(exec_get_param, _1, 13, "PREVBLOCKSINFOTUPLE"))) + .insert(OpcodeInstr::mksimple(0xf82e, 16, "UNPACKEDCONFIGTUPLE", std::bind(exec_get_param, _1, 14, "UNPACKEDCONFIGTUPLE"))) + .insert(OpcodeInstr::mksimple(0xf82f, 16, "DUEPAYMENT", std::bind(exec_get_param, _1, 15, "DUEPAYMENT"))) .insert(OpcodeInstr::mksimple(0xf830, 16, "CONFIGDICT", exec_get_config_dict)) .insert(OpcodeInstr::mksimple(0xf832, 16, "CONFIGPARAM", std::bind(exec_get_config_param, _1, false))) .insert(OpcodeInstr::mksimple(0xf833, 16, "CONFIGOPTPARAM", std::bind(exec_get_config_param, _1, true))) + .insert(OpcodeInstr::mksimple(0xf83400, 24, "PREVMCBLOCKS", std::bind(exec_get_prev_blocks_info, _1, 0, "PREVMCBLOCKS"))->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf83401, 24, "PREVKEYBLOCK", std::bind(exec_get_prev_blocks_info, _1, 1, "PREVKEYBLOCK"))->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf835, 16, "GLOBALID", exec_get_global_id)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf836, 16, "GETGASFEE", exec_get_gas_fee)->require_version(6)) + .insert(OpcodeInstr::mksimple(0xf837, 16, "GETSTORAGEFEE", exec_get_storage_fee)->require_version(6)) + .insert(OpcodeInstr::mksimple(0xf838, 16, "GETFORWARDFEE", exec_get_forward_fee)->require_version(6)) + .insert(OpcodeInstr::mksimple(0xf839, 16, "GETPRECOMPILEDGAS", exec_get_precompiled_gas)->require_version(6)) + .insert(OpcodeInstr::mksimple(0xf83a, 16, "GETORIGINALFWDFEE", exec_get_original_fwd_fee)->require_version(6)) + .insert(OpcodeInstr::mksimple(0xf83b, 16, "GETGASFEESIMPLE", exec_get_gas_fee_simple)->require_version(6)) + .insert(OpcodeInstr::mksimple(0xf83c, 16, "GETFORWARDFEESIMPLE", exec_get_forward_fee_simple)->require_version(6)) .insert(OpcodeInstr::mksimple(0xf840, 16, "GETGLOBVAR", exec_get_global_var)) .insert(OpcodeInstr::mkfixedrange(0xf841, 0xf860, 16, 5, instr::dump_1c_and(31, "GETGLOB "), exec_get_global)) .insert(OpcodeInstr::mksimple(0xf860, 16, "SETGLOBVAR", exec_set_global_var)) @@ -213,11 +391,11 @@ static constexpr int randseed_idx = 6; td::RefInt256 generate_randu256(VmState* st) { auto tuple = st->get_c7(); - auto t1 = tuple_index(*tuple, 0).as_tuple_range(255); + auto t1 = tuple_index(tuple, 0).as_tuple_range(255); if (t1.is_null()) { throw VmError{Excno::type_chk, "intermediate value is not a tuple"}; } - auto seedv = tuple_index(*t1, randseed_idx).as_int(); + auto seedv = tuple_index(t1, randseed_idx).as_int(); if (seedv.is_null()) { throw VmError{Excno::type_chk, "random seed is not an integer"}; } @@ -260,7 +438,7 @@ int exec_rand_int(VmState* st) { typename td::BigInt256::DoubleInt tmp{0}; tmp.add_mul(*x, *y); tmp.rshift(256, -1).normalize(); - stack.push_int(td::RefInt256{true, tmp}); + stack.push_int(td::make_refint(tmp)); return 0; } @@ -273,12 +451,12 @@ int exec_set_rand(VmState* st, bool mix) { throw VmError{Excno::range_chk, "new random seed out of range"}; } auto tuple = st->get_c7(); - auto t1 = tuple_index(*tuple, 0).as_tuple_range(255); + auto t1 = tuple_index(tuple, 0).as_tuple_range(255); if (t1.is_null()) { throw VmError{Excno::type_chk, "intermediate value is not a tuple"}; } if (mix) { - auto seedv = tuple_index(*t1, randseed_idx).as_int(); + auto seedv = tuple_index(t1, randseed_idx).as_int(); if (seedv.is_null()) { throw VmError{Excno::type_chk, "random seed is not an integer"}; } @@ -353,6 +531,77 @@ int exec_compute_sha256(VmState* st) { return 0; } +int exec_hash_ext(VmState* st, unsigned args) { + bool rev = (args >> 8) & 1; + bool append = (args >> 9) & 1; + int hash_id = args & 255; + VM_LOG(st) << "execute HASHEXT" << (append ? "A" : "") << (rev ? "R" : "") << " " << (hash_id == 255 ? -1 : hash_id); + Stack& stack = st->get_stack(); + if (hash_id == 255) { + hash_id = stack.pop_smallint_range(254); + } + int cnt = stack.pop_smallint_range(stack.depth() - 1); + Hasher hasher{hash_id}; + size_t total_bits = 0; + long long gas_consumed = 0; + for (int i = 0; i < cnt; ++i) { + td::ConstBitPtr data{nullptr}; + unsigned size; + int idx = rev ? i : cnt - 1 - i; + auto slice = stack[idx].as_slice(); + if (slice.not_null()) { + data = slice->data_bits(); + size = slice->size(); + } else { + auto builder = stack[idx].as_builder(); + if (builder.not_null()) { + data = builder->data_bits(); + size = builder->size(); + } else { + stack.pop_many(cnt); + throw VmError{Excno::type_chk, "expected slice or builder"}; + } + } + total_bits += size; + long long gas_total = (i + 1) * VmState::hash_ext_entry_gas_price + total_bits / 8 / hasher.bytes_per_gas_unit(); + st->consume_gas(gas_total - gas_consumed); + gas_consumed = gas_total; + hasher.append(data, size); + } + stack.pop_many(cnt); + td::BufferSlice hash = hasher.finish(); + if (append) { + Ref builder = stack.pop_builder(); + if (!builder->can_extend_by(hash.size() * 8)) { + throw VmError{Excno::cell_ov}; + } + builder.write().store_bytes(hash.as_slice()); + stack.push_builder(std::move(builder)); + } else { + if (hash.size() <= 32) { + td::RefInt256 res{true}; + CHECK(res.write().import_bytes((unsigned char*)hash.data(), hash.size(), false)); + stack.push_int(std::move(res)); + } else { + std::vector res; + for (size_t i = 0; i < hash.size(); i += 32) { + td::RefInt256 x{true}; + CHECK(x.write().import_bytes((unsigned char*)hash.data() + i, std::min(hash.size() - i, 32), false)); + res.push_back(std::move(x)); + } + stack.push_tuple(std::move(res)); + } + } + return 0; +} + +std::string dump_hash_ext(CellSlice& cs, unsigned args) { + bool rev = (args >> 8) & 1; + bool append = (args >> 9) & 1; + int hash_id = args & 255; + return PSTRING() << "HASHEXT" << (append ? "A" : "") << (rev ? "R" : "") << " " << (hash_id == 255 ? -1 : hash_id); +} + int exec_ed25519_check_signature(VmState* st, bool from_slice) { VM_LOG(st) << "execute CHKSIGN" << (from_slice ? 'S' : 'U'); Stack& stack = st->get_stack(); @@ -382,19 +631,671 @@ int exec_ed25519_check_signature(VmState* st, bool from_slice) { if (!key_int->export_bytes(key, 32, false)) { throw VmError{Excno::range_chk, "Ed25519 public key must fit in an unsigned 256-bit integer"}; } + st->register_chksgn_call(); td::Ed25519::PublicKey pub_key{td::SecureString(td::Slice{key, 32})}; auto res = pub_key.verify_signature(td::Slice{data, data_len}, td::Slice{signature, 64}); stack.push_bool(res.is_ok() || st->get_chksig_always_succeed()); return 0; } +int exec_ecrecover(VmState* st) { + VM_LOG(st) << "execute ECRECOVER"; + Stack& stack = st->get_stack(); + stack.check_underflow(4); + auto s = stack.pop_int(); + auto r = stack.pop_int(); + auto v = (td::uint8)stack.pop_smallint_range(255); + auto hash = stack.pop_int(); + + unsigned char signature[65]; + if (!r->export_bytes(signature, 32, false)) { + throw VmError{Excno::range_chk, "r must fit in an unsigned 256-bit integer"}; + } + if (!s->export_bytes(signature + 32, 32, false)) { + throw VmError{Excno::range_chk, "s must fit in an unsigned 256-bit integer"}; + } + signature[64] = v; + unsigned char hash_bytes[32]; + if (!hash->export_bytes(hash_bytes, 32, false)) { + throw VmError{Excno::range_chk, "data hash must fit in an unsigned 256-bit integer"}; + } + st->consume_gas(VmState::ecrecover_gas_price); + unsigned char public_key[65]; + if (td::ecrecover(hash_bytes, signature, public_key)) { + td::uint8 h = public_key[0]; + td::RefInt256 x1{true}, x2{true}; + CHECK(x1.write().import_bytes(public_key + 1, 32, false)); + CHECK(x2.write().import_bytes(public_key + 33, 32, false)); + stack.push_smallint(h); + stack.push_int(std::move(x1)); + stack.push_int(std::move(x2)); + stack.push_bool(true); + } else { + stack.push_bool(false); + } + return 0; +} + +int exec_p256_chksign(VmState* st, bool from_slice) { + VM_LOG(st) << "execute P256_CHKSIGN" << (from_slice ? 'S' : 'U'); + Stack& stack = st->get_stack(); + stack.check_underflow(3); + auto key_cs = stack.pop_cellslice(); + auto signature_cs = stack.pop_cellslice(); + unsigned char data[128], key[33], signature[64]; + unsigned data_len; + if (from_slice) { + auto cs = stack.pop_cellslice(); + if (cs->size() & 7) { + throw VmError{Excno::cell_und, "Slice does not consist of an integer number of bytes"}; + } + data_len = (cs->size() >> 3); + CHECK(data_len <= sizeof(data)); + CHECK(cs->prefetch_bytes(data, data_len)); + } else { + auto hash_int = stack.pop_int(); + data_len = 32; + if (!hash_int->export_bytes(data, data_len, false)) { + throw VmError{Excno::range_chk, "data hash must fit in an unsigned 256-bit integer"}; + } + } + if (!signature_cs->prefetch_bytes(signature, 64)) { + throw VmError{Excno::cell_und, "P256 signature must contain at least 512 data bits"}; + } + if (!key_cs->prefetch_bytes(key, 33)) { + throw VmError{Excno::cell_und, "P256 public key must contain at least 33 data bytes"}; + } + st->consume_gas(VmState::p256_chksgn_gas_price); + auto res = td::p256_check_signature(td::Slice{data, data_len}, td::Slice{key, 33}, td::Slice{signature, 64}); + if (res.is_error()) { + VM_LOG(st) << "P256_CHKSIGN: " << res.error().message(); + } + stack.push_bool(res.is_ok() || st->get_chksig_always_succeed()); + return 0; +} + +static_assert(crypto_scalarmult_ristretto255_BYTES == 32, "Unexpected value of ristretto255 constant"); +static_assert(crypto_scalarmult_ristretto255_SCALARBYTES == 32, "Unexpected value of ristretto255 constant"); +static_assert(crypto_core_ristretto255_BYTES == 32, "Unexpected value of ristretto255 constant"); +static_assert(crypto_core_ristretto255_HASHBYTES == 64, "Unexpected value of ristretto255 constant"); +static_assert(crypto_core_ristretto255_SCALARBYTES == 32, "Unexpected value of ristretto255 constant"); +static_assert(crypto_core_ristretto255_NONREDUCEDSCALARBYTES == 64, "Unexpected value of ristretto255 constant"); + +int exec_ristretto255_from_hash(VmState* st) { + VM_LOG(st) << "execute RIST255_FROMHASH"; + Stack& stack = st->get_stack(); + stack.check_underflow(2); + auto x2 = stack.pop_int(); + auto x1 = stack.pop_int(); + st->consume_gas(VmState::rist255_fromhash_gas_price); + unsigned char xb[64], rb[32]; + if (!x1->export_bytes(xb, 32, false)) { + throw VmError{Excno::range_chk, "x1 must fit in an unsigned 256-bit integer"}; + } + if (!x2->export_bytes(xb + 32, 32, false)) { + throw VmError{Excno::range_chk, "x2 must fit in an unsigned 256-bit integer"}; + } + crypto_core_ristretto255_from_hash(rb, xb); + td::RefInt256 r{true}; + CHECK(r.write().import_bytes(rb, 32, false)); + stack.push_int(std::move(r)); + return 0; +} + +int exec_ristretto255_validate(VmState* st, bool quiet) { + VM_LOG(st) << "execute RIST255_VALIDATE"; + Stack& stack = st->get_stack(); + auto x = stack.pop_int(); + st->consume_gas(VmState::rist255_validate_gas_price); + unsigned char xb[32]; + if (!x->export_bytes(xb, 32, false) || !crypto_core_ristretto255_is_valid_point(xb)) { + if (quiet) { + stack.push_bool(false); + return 0; + } + throw VmError{Excno::range_chk, "x is not a valid encoded element"}; + } + if (quiet) { + stack.push_bool(true); + } + return 0; +} + +int exec_ristretto255_add(VmState* st, bool quiet) { + VM_LOG(st) << "execute RIST255_ADD"; + Stack& stack = st->get_stack(); + stack.check_underflow(2); + auto y = stack.pop_int(); + auto x = stack.pop_int(); + st->consume_gas(VmState::rist255_add_gas_price); + unsigned char xb[32], yb[32], rb[32]; + if (!x->export_bytes(xb, 32, false) || !y->export_bytes(yb, 32, false) || crypto_core_ristretto255_add(rb, xb, yb)) { + if (quiet) { + stack.push_bool(false); + return 0; + } + throw VmError{Excno::range_chk, "x and/or y are not valid encoded elements"}; + } + td::RefInt256 r{true}; + CHECK(r.write().import_bytes(rb, 32, false)); + stack.push_int(std::move(r)); + if (quiet) { + stack.push_bool(true); + } + return 0; +} + +int exec_ristretto255_sub(VmState* st, bool quiet) { + VM_LOG(st) << "execute RIST255_SUB"; + Stack& stack = st->get_stack(); + stack.check_underflow(2); + auto y = stack.pop_int(); + auto x = stack.pop_int(); + st->consume_gas(VmState::rist255_add_gas_price); + unsigned char xb[32], yb[32], rb[32]; + if (!x->export_bytes(xb, 32, false) || !y->export_bytes(yb, 32, false) || crypto_core_ristretto255_sub(rb, xb, yb)) { + if (quiet) { + stack.push_bool(false); + return 0; + } + throw VmError{Excno::range_chk, "x and/or y are not valid encoded elements"}; + } + td::RefInt256 r{true}; + CHECK(r.write().import_bytes(rb, 32, false)); + stack.push_int(std::move(r)); + if (quiet) { + stack.push_bool(true); + } + return 0; +} + +static bool export_bytes_little(const td::RefInt256& n, unsigned char* nb) { + if (!n->export_bytes(nb, 32, false)) { + return false; + } + std::reverse(nb, nb + 32); + return true; +} + +static td::RefInt256 get_ristretto256_l() { + static td::RefInt256 l = + (td::make_refint(1) << 252) + td::dec_string_to_int256(td::Slice("27742317777372353535851937790883648493")); + return l; +} + +int exec_ristretto255_mul(VmState* st, bool quiet) { + VM_LOG(st) << "execute RIST255_MUL"; + Stack& stack = st->get_stack(); + stack.check_underflow(2); + auto n = stack.pop_int() % get_ristretto256_l(); + auto x = stack.pop_int(); + st->consume_gas(VmState::rist255_mul_gas_price); + if (n->sgn() == 0) { + stack.push_smallint(0); + if (quiet) { + stack.push_bool(true); + } + return 0; + } + unsigned char xb[32], nb[32], rb[32]; + if (!x->export_bytes(xb, 32, false) || !export_bytes_little(n, nb) || crypto_scalarmult_ristretto255(rb, nb, xb)) { + if (quiet) { + stack.push_bool(false); + return 0; + } + throw VmError{Excno::range_chk, "invalid x or n"}; + } + td::RefInt256 r{true}; + CHECK(r.write().import_bytes(rb, 32, false)); + stack.push_int(std::move(r)); + if (quiet) { + stack.push_bool(true); + } + return 0; +} + +int exec_ristretto255_mul_base(VmState* st, bool quiet) { + VM_LOG(st) << "execute RIST255_MULBASE"; + Stack& stack = st->get_stack(); + auto n = stack.pop_int() % get_ristretto256_l(); + st->consume_gas(VmState::rist255_mulbase_gas_price); + unsigned char nb[32], rb[32]; + memset(rb, 255, sizeof(rb)); + if (!export_bytes_little(n, nb) || crypto_scalarmult_ristretto255_base(rb, nb)) { + if (std::all_of(rb, rb + 32, [](unsigned char c) { return c == 255; })) { + if (quiet) { + stack.push_bool(false); + return 0; + } + throw VmError{Excno::range_chk, "invalid n"}; + } + } + td::RefInt256 r{true}; + CHECK(r.write().import_bytes(rb, 32, false)); + stack.push_int(std::move(r)); + if (quiet) { + stack.push_bool(true); + } + return 0; +} + +int exec_ristretto255_push_l(VmState* st) { + VM_LOG(st) << "execute RIST255_PUSHL"; + Stack& stack = st->get_stack(); + stack.push_int(get_ristretto256_l()); + return 0; +} + +static bls::P1 slice_to_bls_p1(const CellSlice& cs) { + bls::P1 p1; + if (!cs.prefetch_bytes(p1.as_slice())) { + throw VmError{Excno::cell_und, PSTRING() << "slice must contain at least " << bls::P1_SIZE << " bytes"}; + } + return p1; +} + +static bls::P2 slice_to_bls_p2(const CellSlice& cs) { + bls::P2 p2; + if (!cs.prefetch_bytes(p2.as_slice())) { + throw VmError{Excno::cell_und, PSTRING() << "slice must contain at least " << bls::P2_SIZE << " bytes"}; + } + return p2; +} + +static bls::FP slice_to_bls_fp(const CellSlice& cs) { + bls::FP fp; + if (!cs.prefetch_bytes(fp.as_slice())) { + throw VmError{Excno::cell_und, PSTRING() << "slice must contain at least " << bls::FP_SIZE << " bytes"}; + } + return fp; +} + +static bls::FP2 slice_to_bls_fp2(const CellSlice& cs) { + bls::FP2 fp2; + if (!cs.prefetch_bytes(fp2.as_slice())) { + throw VmError{Excno::cell_und, PSTRING() << "slice must contain at least " << bls::FP_SIZE * 2 << " bytes"}; + } + return fp2; +} + +static td::BufferSlice slice_to_bls_msg(const CellSlice& cs) { + if (cs.size() % 8 != 0) { + throw VmError{Excno::cell_und, "message does not consist of an integer number of bytes"}; + } + size_t msg_size = cs.size() / 8; + td::BufferSlice s(msg_size); + cs.prefetch_bytes((td::uint8*)s.data(), (int)msg_size); + return s; +} + +static Ref bls_to_slice(td::Slice s) { + VmStateInterface::Guard guard{nullptr}; // Don't consume gas for finalize and load_cell_slice + CellBuilder cb; + return load_cell_slice_ref(cb.store_bytes(s).finalize()); +} + +static long long bls_calculate_multiexp_gas(int n, long long base, long long coef1, long long coef2) { + int l = 4; + while ((1LL << (l + 1)) <= n) { + ++l; + } + return base + n * coef1 + n * coef2 / l; +} + +int exec_bls_verify(VmState* st) { + VM_LOG(st) << "execute BLS_VERIFY"; + Stack& stack = st->get_stack(); + stack.check_underflow(3); + st->consume_gas(VmState::bls_verify_gas_price); + bls::P2 sig = slice_to_bls_p2(*stack.pop_cellslice()); + td::BufferSlice msg = slice_to_bls_msg(*stack.pop_cellslice()); + bls::P1 pub = slice_to_bls_p1(*stack.pop_cellslice()); + stack.push_bool(bls::verify(pub, msg, sig)); + return 0; +} + +int exec_bls_aggregate(VmState* st) { + VM_LOG(st) << "execute BLS_AGGREGATE"; + Stack& stack = st->get_stack(); + int n = stack.pop_smallint_range(stack.depth() - 1, 1); + st->consume_gas(VmState::bls_aggregate_base_gas_price + (long long)n * VmState::bls_aggregate_element_gas_price); + std::vector sigs(n); + for (int i = n - 1; i >= 0; --i) { + sigs[i] = slice_to_bls_p2(*stack.pop_cellslice()); + } + bls::P2 aggregated = bls::aggregate(sigs); + stack.push_cellslice(bls_to_slice(aggregated.as_slice())); + return 0; +} + +int exec_bls_fast_aggregate_verify(VmState* st) { + VM_LOG(st) << "execute BLS_FASTAGGREGATEVERIFY"; + Stack& stack = st->get_stack(); + stack.check_underflow(3); + Ref sig = stack.pop_cellslice(); + Ref msg = stack.pop_cellslice(); + int n = stack.pop_smallint_range(stack.depth() - 1); + st->consume_gas(VmState::bls_fast_aggregate_verify_base_gas_price + + (long long)n * VmState::bls_fast_aggregate_verify_element_gas_price); + std::vector pubs(n); + for (int i = n - 1; i >= 0; --i) { + pubs[i] = slice_to_bls_p1(*stack.pop_cellslice()); + } + stack.push_bool(bls::fast_aggregate_verify(pubs, slice_to_bls_msg(*msg), slice_to_bls_p2(*sig))); + return 0; +} + +int exec_bls_aggregate_verify(VmState* st) { + VM_LOG(st) << "execute BLS_AGGREGATEVERIFY"; + Stack& stack = st->get_stack(); + stack.check_underflow(2); + Ref sig = stack.pop_cellslice(); + int n = stack.pop_smallint_range((stack.depth() - 1) / 2); + st->consume_gas(VmState::bls_aggregate_verify_base_gas_price + + (long long)n * VmState::bls_aggregate_verify_element_gas_price); + std::vector> vec(n); + for (int i = n - 1; i >= 0; --i) { + vec[i].second = slice_to_bls_msg(*stack.pop_cellslice()); + vec[i].first = slice_to_bls_p1(*stack.pop_cellslice()); + } + stack.push_bool(bls::aggregate_verify(vec, slice_to_bls_p2(*sig))); + return 0; +} + +int exec_bls_g1_add(VmState* st) { + VM_LOG(st) << "execute BLS_G1_ADD"; + Stack& stack = st->get_stack(); + stack.check_underflow(2); + st->consume_gas(VmState::bls_g1_add_sub_gas_price); + bls::P1 b = slice_to_bls_p1(*stack.pop_cellslice()); + bls::P1 a = slice_to_bls_p1(*stack.pop_cellslice()); + stack.push_cellslice(bls_to_slice(bls::g1_add(a, b).as_slice())); + return 0; +} + +int exec_bls_g1_sub(VmState* st) { + VM_LOG(st) << "execute BLS_G1_SUB"; + Stack& stack = st->get_stack(); + stack.check_underflow(2); + st->consume_gas(VmState::bls_g1_add_sub_gas_price); + bls::P1 b = slice_to_bls_p1(*stack.pop_cellslice()); + bls::P1 a = slice_to_bls_p1(*stack.pop_cellslice()); + stack.push_cellslice(bls_to_slice(bls::g1_sub(a, b).as_slice())); + return 0; +} + +int exec_bls_g1_neg(VmState* st) { + VM_LOG(st) << "execute BLS_G1_NEG"; + Stack& stack = st->get_stack(); + st->consume_gas(VmState::bls_g1_neg_gas_price); + bls::P1 a = slice_to_bls_p1(*stack.pop_cellslice()); + stack.push_cellslice(bls_to_slice(bls::g1_neg(a).as_slice())); + return 0; +} + +int exec_bls_g1_mul(VmState* st) { + VM_LOG(st) << "execute BLS_G1_MUL"; + Stack& stack = st->get_stack(); + stack.check_underflow(2); + st->consume_gas(VmState::bls_g1_mul_gas_price); + td::RefInt256 x = stack.pop_int_finite(); + bls::P1 p = slice_to_bls_p1(*stack.pop_cellslice()); + stack.push_cellslice(bls_to_slice(bls::g1_mul(p, x).as_slice())); + return 0; +} + +int exec_bls_g1_multiexp(VmState* st) { + VM_LOG(st) << "execute BLS_G1_MULTIEXP"; + Stack& stack = st->get_stack(); + int n = stack.pop_smallint_range((stack.depth() - 1) / 2); + st->consume_gas(bls_calculate_multiexp_gas(n, VmState::bls_g1_multiexp_base_gas_price, + VmState::bls_g1_multiexp_coef1_gas_price, + VmState::bls_g1_multiexp_coef2_gas_price)); + std::vector> ps(n); + for (int i = n - 1; i >= 0; --i) { + ps[i].second = stack.pop_int_finite(); + ps[i].first = slice_to_bls_p1(*stack.pop_cellslice()); + } + stack.push_cellslice(bls_to_slice(bls::g1_multiexp(ps).as_slice())); + return 0; +} + +int exec_bls_g1_zero(VmState* st) { + VM_LOG(st) << "execute BLS_G1_ZERO"; + Stack& stack = st->get_stack(); + stack.push_cellslice(bls_to_slice(bls::g1_zero().as_slice())); + return 0; +} + +int exec_bls_map_to_g1(VmState* st) { + VM_LOG(st) << "execute BLS_MAP_TO_G1"; + Stack& stack = st->get_stack(); + st->consume_gas(VmState::bls_map_to_g1_gas_price); + bls::FP a = slice_to_bls_fp(*stack.pop_cellslice()); + stack.push_cellslice(bls_to_slice(bls::map_to_g1(a).as_slice())); + return 0; +} + +int exec_bls_g1_in_group(VmState* st) { + VM_LOG(st) << "execute BLS_G1_INGROUP"; + Stack& stack = st->get_stack(); + st->consume_gas(VmState::bls_g1_in_group_gas_price); + bls::P1 a = slice_to_bls_p1(*stack.pop_cellslice()); + stack.push_bool(bls::g1_in_group(a)); + return 0; +} + +int exec_bls_g1_is_zero(VmState* st) { + VM_LOG(st) << "execute BLS_G1_ISZERO"; + Stack& stack = st->get_stack(); + bls::P1 a = slice_to_bls_p1(*stack.pop_cellslice()); + stack.push_bool(bls::g1_is_zero(a)); + return 0; +} + +int exec_bls_g2_add(VmState* st) { + VM_LOG(st) << "execute BLS_G2_ADD"; + Stack& stack = st->get_stack(); + stack.check_underflow(2); + st->consume_gas(VmState::bls_g2_add_sub_gas_price); + bls::P2 b = slice_to_bls_p2(*stack.pop_cellslice()); + bls::P2 a = slice_to_bls_p2(*stack.pop_cellslice()); + stack.push_cellslice(bls_to_slice(bls::g2_add(a, b).as_slice())); + return 0; +} + +int exec_bls_g2_sub(VmState* st) { + VM_LOG(st) << "execute BLS_G2_SUB"; + Stack& stack = st->get_stack(); + stack.check_underflow(2); + st->consume_gas(VmState::bls_g2_add_sub_gas_price); + bls::P2 b = slice_to_bls_p2(*stack.pop_cellslice()); + bls::P2 a = slice_to_bls_p2(*stack.pop_cellslice()); + stack.push_cellslice(bls_to_slice(bls::g2_sub(a, b).as_slice())); + return 0; +} + +int exec_bls_g2_neg(VmState* st) { + VM_LOG(st) << "execute BLS_G2_NEG"; + Stack& stack = st->get_stack(); + st->consume_gas(VmState::bls_g2_neg_gas_price); + bls::P2 a = slice_to_bls_p2(*stack.pop_cellslice()); + stack.push_cellslice(bls_to_slice(bls::g2_neg(a).as_slice())); + return 0; +} + +int exec_bls_g2_mul(VmState* st) { + VM_LOG(st) << "execute BLS_G2_MUL"; + Stack& stack = st->get_stack(); + stack.check_underflow(2); + st->consume_gas(VmState::bls_g2_mul_gas_price); + td::RefInt256 x = stack.pop_int_finite(); + bls::P2 p = slice_to_bls_p2(*stack.pop_cellslice()); + stack.push_cellslice(bls_to_slice(bls::g2_mul(p, x).as_slice())); + return 0; +} + +int exec_bls_g2_multiexp(VmState* st) { + VM_LOG(st) << "execute BLS_G2_MULTIEXP"; + Stack& stack = st->get_stack(); + int n = stack.pop_smallint_range((stack.depth() - 1) / 2); + st->consume_gas(bls_calculate_multiexp_gas(n, VmState::bls_g2_multiexp_base_gas_price, + VmState::bls_g2_multiexp_coef1_gas_price, + VmState::bls_g2_multiexp_coef2_gas_price)); + std::vector> ps(n); + for (int i = n - 1; i >= 0; --i) { + ps[i].second = stack.pop_int_finite(); + ps[i].first = slice_to_bls_p2(*stack.pop_cellslice()); + } + stack.push_cellslice(bls_to_slice(bls::g2_multiexp(ps).as_slice())); + return 0; +} + +int exec_bls_g2_zero(VmState* st) { + VM_LOG(st) << "execute BLS_G2_ZERO"; + Stack& stack = st->get_stack(); + stack.push_cellslice(bls_to_slice(bls::g2_zero().as_slice())); + return 0; +} + +int exec_bls_map_to_g2(VmState* st) { + VM_LOG(st) << "execute BLS_MAP_TO_G2"; + Stack& stack = st->get_stack(); + st->consume_gas(VmState::bls_map_to_g2_gas_price); + bls::FP2 a = slice_to_bls_fp2(*stack.pop_cellslice()); + stack.push_cellslice(bls_to_slice(bls::map_to_g2(a).as_slice())); + return 0; +} + +int exec_bls_g2_in_group(VmState* st) { + VM_LOG(st) << "execute BLS_G2_INGROUP"; + Stack& stack = st->get_stack(); + st->consume_gas(VmState::bls_g2_in_group_gas_price); + bls::P2 a = slice_to_bls_p2(*stack.pop_cellslice()); + stack.push_bool(bls::g2_in_group(a)); + return 0; +} + +int exec_bls_g2_is_zero(VmState* st) { + VM_LOG(st) << "execute BLS_G2_ISZERO"; + Stack& stack = st->get_stack(); + bls::P2 a = slice_to_bls_p2(*stack.pop_cellslice()); + stack.push_bool(bls::g2_is_zero(a)); + return 0; +} + +int exec_bls_pairing(VmState* st) { + VM_LOG(st) << "execute BLS_PAIRING"; + Stack& stack = st->get_stack(); + int n = stack.pop_smallint_range((stack.depth() - 1) / 2); + st->consume_gas(VmState::bls_pairing_base_gas_price + (long long)n * VmState::bls_pairing_element_gas_price); + std::vector> ps(n); + for (int i = n - 1; i >= 0; --i) { + ps[i].second = slice_to_bls_p2(*stack.pop_cellslice()); + ps[i].first = slice_to_bls_p1(*stack.pop_cellslice()); + } + stack.push_bool(bls::pairing(ps)); + return 0; +} + +int exec_bls_push_r(VmState* st) { + VM_LOG(st) << "execute BLS_PUSHR"; + Stack& stack = st->get_stack(); + stack.push_int(bls::get_r()); + return 0; +} + void register_ton_crypto_ops(OpcodeTable& cp0) { using namespace std::placeholders; cp0.insert(OpcodeInstr::mksimple(0xf900, 16, "HASHCU", std::bind(exec_compute_hash, _1, 0))) .insert(OpcodeInstr::mksimple(0xf901, 16, "HASHSU", std::bind(exec_compute_hash, _1, 1))) .insert(OpcodeInstr::mksimple(0xf902, 16, "SHA256U", exec_compute_sha256)) + .insert(OpcodeInstr::mkfixed(0xf904 >> 2, 14, 10, dump_hash_ext, exec_hash_ext)->require_version(4)) .insert(OpcodeInstr::mksimple(0xf910, 16, "CHKSIGNU", std::bind(exec_ed25519_check_signature, _1, false))) - .insert(OpcodeInstr::mksimple(0xf911, 16, "CHKSIGNS", std::bind(exec_ed25519_check_signature, _1, true))); + .insert(OpcodeInstr::mksimple(0xf911, 16, "CHKSIGNS", std::bind(exec_ed25519_check_signature, _1, true))) + .insert(OpcodeInstr::mksimple(0xf912, 16, "ECRECOVER", exec_ecrecover)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf914, 16, "P256_CHKSIGNU", std::bind(exec_p256_chksign, _1, false))->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf915, 16, "P256_CHKSIGNS", std::bind(exec_p256_chksign, _1, true))->require_version(4)) + + .insert(OpcodeInstr::mksimple(0xf920, 16, "RIST255_FROMHASH", exec_ristretto255_from_hash)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf921, 16, "RIST255_VALIDATE", std::bind(exec_ristretto255_validate, _1, false))->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf922, 16, "RIST255_ADD", std::bind(exec_ristretto255_add, _1, false))->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf923, 16, "RIST255_SUB", std::bind(exec_ristretto255_sub, _1, false))->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf924, 16, "RIST255_MUL", std::bind(exec_ristretto255_mul, _1, false))->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf925, 16, "RIST255_MULBASE", std::bind(exec_ristretto255_mul_base, _1, false))->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf926, 16, "RIST255_PUSHL", exec_ristretto255_push_l)->require_version(4)) + + .insert(OpcodeInstr::mksimple(0xb7f921, 24, "RIST255_QVALIDATE", std::bind(exec_ristretto255_validate, _1, true))->require_version(4)) + .insert(OpcodeInstr::mksimple(0xb7f922, 24, "RIST255_QADD", std::bind(exec_ristretto255_add, _1, true))->require_version(4)) + .insert(OpcodeInstr::mksimple(0xb7f923, 24, "RIST255_QSUB", std::bind(exec_ristretto255_sub, _1, true))->require_version(4)) + .insert(OpcodeInstr::mksimple(0xb7f924, 24, "RIST255_QMUL", std::bind(exec_ristretto255_mul, _1, true))->require_version(4)) + .insert(OpcodeInstr::mksimple(0xb7f925, 24, "RIST255_QMULBASE", std::bind(exec_ristretto255_mul_base, _1, true))->require_version(4)) + + .insert(OpcodeInstr::mksimple(0xf93000, 24, "BLS_VERIFY", exec_bls_verify)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93001, 24, "BLS_AGGREGATE", exec_bls_aggregate)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93002, 24, "BLS_FASTAGGREGATEVERIFY", exec_bls_fast_aggregate_verify)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93003, 24, "BLS_AGGREGATEVERIFY", exec_bls_aggregate_verify)->require_version(4)) + + .insert(OpcodeInstr::mksimple(0xf93010, 24, "BLS_G1_ADD", exec_bls_g1_add)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93011, 24, "BLS_G1_SUB", exec_bls_g1_sub)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93012, 24, "BLS_G1_NEG", exec_bls_g1_neg)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93013, 24, "BLS_G1_MUL", exec_bls_g1_mul)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93014, 24, "BLS_G1_MULTIEXP", exec_bls_g1_multiexp)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93015, 24, "BLS_G1_ZERO", exec_bls_g1_zero)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93016, 24, "BLS_MAP_TO_G1", exec_bls_map_to_g1)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93017, 24, "BLS_G1_INGROUP", exec_bls_g1_in_group)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93018, 24, "BLS_G1_ISZERO", exec_bls_g1_is_zero)->require_version(4)) + + .insert(OpcodeInstr::mksimple(0xf93020, 24, "BLS_G2_ADD", exec_bls_g2_add)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93021, 24, "BLS_G2_SUB", exec_bls_g2_sub)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93022, 24, "BLS_G2_NEG", exec_bls_g2_neg)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93023, 24, "BLS_G2_MUL", exec_bls_g2_mul)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93024, 24, "BLS_G2_MULTIEXP", exec_bls_g2_multiexp)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93025, 24, "BLS_G2_ZERO", exec_bls_g2_zero)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93026, 24, "BLS_MAP_TO_G2", exec_bls_map_to_g2)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93027, 24, "BLS_G2_INGROUP", exec_bls_g2_in_group)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93028, 24, "BLS_G2_ISZERO", exec_bls_g2_is_zero)->require_version(4)) + + .insert(OpcodeInstr::mksimple(0xf93030, 24, "BLS_PAIRING", exec_bls_pairing)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf93031, 24, "BLS_PUSHR", exec_bls_push_r)->require_version(4)); +} + +int exec_compute_data_size(VmState* st, int mode) { + VM_LOG(st) << (mode & 2 ? 'S' : 'C') << "DATASIZE" << (mode & 1 ? "Q" : ""); + Stack& stack = st->get_stack(); + stack.check_underflow(2); + auto bound = stack.pop_int(); + Ref cell; + Ref cs; + if (mode & 2) { + cs = stack.pop_cellslice(); + } else { + cell = stack.pop_maybe_cell(); + } + if (!bound->is_valid() || bound->sgn() < 0) { + throw VmError{Excno::range_chk, "finite non-negative integer expected"}; + } + VmStorageStat stat{bound->unsigned_fits_bits(63) ? bound->to_long() : (1ULL << 63) - 1}; + bool ok = (mode & 2 ? stat.add_storage(cs.write()) : stat.add_storage(std::move(cell))); + if (ok) { + stack.push_smallint(stat.cells); + stack.push_smallint(stat.bits); + stack.push_smallint(stat.refs); + } else if (!(mode & 1)) { + throw VmError{Excno::cell_ov, "scanned too many cells"}; + } + if (mode & 1) { + stack.push_bool(ok); + } + return 0; +} + +void register_ton_misc_ops(OpcodeTable& cp0) { + using namespace std::placeholders; + cp0.insert(OpcodeInstr::mksimple(0xf940, 16, "CDATASIZEQ", std::bind(exec_compute_data_size, _1, 1))) + .insert(OpcodeInstr::mksimple(0xf941, 16, "CDATASIZE", std::bind(exec_compute_data_size, _1, 0))) + .insert(OpcodeInstr::mksimple(0xf942, 16, "SDATASIZEQ", std::bind(exec_compute_data_size, _1, 3))) + .insert(OpcodeInstr::mksimple(0xf943, 16, "SDATASIZE", std::bind(exec_compute_data_size, _1, 2))); } int exec_load_var_integer(VmState* st, int len_bits, bool sgnd, bool quiet) { @@ -406,19 +1307,14 @@ int exec_load_var_integer(VmState* st, int len_bits, bool sgnd, bool quiet) { Stack& stack = st->get_stack(); auto csr = stack.pop_cellslice(); td::RefInt256 x; - int len; - if (!(csr.write().fetch_uint_to(len_bits, len) && csr.unique_write().fetch_int256_to(len * 8, x, sgnd))) { - if (quiet) { - stack.push_bool(false); - } else { - throw VmError{Excno::cell_und, "cannot deserialize a variable-length integer"}; - } - } else { + if (util::load_var_integer_q(csr.write(), x, len_bits, sgnd, quiet)) { stack.push_int(std::move(x)); stack.push_cellslice(std::move(csr)); if (quiet) { stack.push_bool(true); } + } else { + stack.push_bool(false); } return 0; } @@ -433,21 +1329,13 @@ int exec_store_var_integer(VmState* st, int len_bits, bool sgnd, bool quiet) { stack.check_underflow(2); auto x = stack.pop_int(); auto cbr = stack.pop_builder(); - unsigned len = ((x->bit_size(sgnd) + 7) >> 3); - if (len >= (1u << len_bits)) { - throw VmError{Excno::range_chk}; - } - if (!(cbr.write().store_long_bool(len, len_bits) && cbr.unique_write().store_int256_bool(*x, len * 8, sgnd))) { - if (quiet) { - stack.push_bool(false); - } else { - throw VmError{Excno::cell_ov, "cannot serialize a variable-length integer"}; - } - } else { + if (util::store_var_integer(cbr.write(), x, len_bits, sgnd, quiet)) { stack.push_builder(std::move(cbr)); if (quiet) { stack.push_bool(true); } + } else { + stack.push_bool(false); } return 0; } @@ -490,22 +1378,17 @@ bool skip_message_addr(CellSlice& cs) { int exec_load_message_addr(VmState* st, bool quiet) { VM_LOG(st) << "execute LDMSGADDR" << (quiet ? "Q" : ""); Stack& stack = st->get_stack(); - auto csr = stack.pop_cellslice(), csr_copy = csr; - auto& cs = csr.write(); - if (!(skip_message_addr(cs) && csr_copy.write().cut_tail(cs))) { - csr.clear(); - if (quiet) { - stack.push_cellslice(std::move(csr_copy)); - stack.push_bool(false); - } else { - throw VmError{Excno::cell_und, "cannot load a MsgAddress"}; - } - } else { - stack.push_cellslice(std::move(csr_copy)); + auto csr = stack.pop_cellslice(); + td::Ref addr{true}; + if (util::load_msg_addr_q(csr.write(), addr.write(), quiet)) { + stack.push_cellslice(std::move(addr)); stack.push_cellslice(std::move(csr)); if (quiet) { stack.push_bool(true); } + } else { + stack.push_cellslice(std::move(csr)); + stack.push_bool(false); } return 0; } @@ -530,15 +1413,15 @@ bool parse_maybe_anycast(CellSlice& cs, StackEntry& res) { bool parse_message_addr(CellSlice& cs, std::vector& res) { res.clear(); switch ((unsigned)cs.fetch_ulong(2)) { - case 0: // addr_none$00 = MsgAddressExt; - res.emplace_back(td::RefInt256{true, 0}); // -> (0) + case 0: // addr_none$00 = MsgAddressExt; + res.emplace_back(td::zero_refint()); // -> (0) return true; case 1: { // addr_extern$01 unsigned len; Ref addr; if (cs.fetch_uint_to(9, len) // len:(## 9) && cs.fetch_subslice_to(len, addr)) { // external_address:(bits len) = MsgAddressExt; - res.emplace_back(td::RefInt256{true, 1}); + res.emplace_back(td::make_refint(1)); res.emplace_back(std::move(addr)); return true; } @@ -551,9 +1434,9 @@ bool parse_message_addr(CellSlice& cs, std::vector& res) { if (parse_maybe_anycast(cs, v) // anycast:(Maybe Anycast) && cs.fetch_int_to(8, workchain) // workchain_id:int8 && cs.fetch_subslice_to(256, addr)) { // address:bits256 = MsgAddressInt; - res.emplace_back(td::RefInt256{true, 2}); + res.emplace_back(td::make_refint(2)); res.emplace_back(std::move(v)); - res.emplace_back(td::RefInt256{true, workchain}); + res.emplace_back(td::make_refint(workchain)); res.emplace_back(std::move(addr)); return true; } @@ -567,9 +1450,9 @@ bool parse_message_addr(CellSlice& cs, std::vector& res) { && cs.fetch_uint_to(9, len) // addr_len:(## 9) && cs.fetch_int_to(32, workchain) // workchain_id:int32 && cs.fetch_subslice_to(len, addr)) { // address:(bits addr_len) = MsgAddressInt; - res.emplace_back(td::RefInt256{true, 3}); + res.emplace_back(td::make_refint(3)); res.emplace_back(std::move(v)); - res.emplace_back(td::RefInt256{true, workchain}); + res.emplace_back(td::make_refint(workchain)); res.emplace_back(std::move(addr)); return true; } @@ -728,32 +1611,266 @@ int exec_send_raw_message(VmState* st) { return install_output_action(st, cb.finalize()); } +int parse_addr_workchain(CellSlice cs) { + // anycast_info$_ depth:(#<= 30) { depth >= 1 } rewrite_pfx:(bits depth) = Anycast; + // addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt; + // addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) workchain_id:int32 address:(bits addr_len) = MsgAddressInt; + if (cs.fetch_ulong(1) != 1) { + throw VmError{Excno::range_chk, "not an internal MsgAddress"}; + } + bool is_var = cs.fetch_ulong(1); + if (cs.fetch_ulong(1) == 1) { // Anycast + unsigned depth; + cs.fetch_uint_leq(30, depth); + cs.skip_first(depth); + } + + if (is_var) { + cs.skip_first(9); + return (int)cs.fetch_long(32); + } else { + return (int)cs.fetch_long(8); + } +} + +int exec_send_message(VmState* st) { + VM_LOG(st) << "execute SENDMSG"; + Stack& stack = st->get_stack(); + stack.check_underflow(2); + int mode = stack.pop_smallint_range(2047); + bool send = !(mode & 1024); + mode &= ~1024; + if (mode >= 256) { + throw VmError{Excno::range_chk}; + } + Ref msg_cell = stack.pop_cell(); + + block::gen::MessageRelaxed::Record msg; + if (!tlb::type_unpack_cell(msg_cell, block::gen::t_MessageRelaxed_Any, msg)) { + throw VmError{Excno::unknown, "invalid message"}; + } + + Ref my_addr = get_param(st, 8).as_slice(); + if (my_addr.is_null()) { + throw VmError{Excno::type_chk, "invalid param MYADDR"}; + } + bool ihr_disabled; + Ref dest; + td::RefInt256 value; + td::RefInt256 user_fwd_fee, user_ihr_fee; + bool have_extra_currencies = false; + bool ext_msg = msg.info->prefetch_ulong(1); + if (ext_msg) { // External message + block::gen::CommonMsgInfoRelaxed::Record_ext_out_msg_info info; + if (!tlb::csr_unpack(msg.info, info)) { + throw VmError{Excno::unknown, "invalid message"}; + } + ihr_disabled = true; + dest = std::move(info.dest); + value = user_fwd_fee = user_ihr_fee = td::zero_refint(); + } else { // Internal message + block::gen::CommonMsgInfoRelaxed::Record_int_msg_info info; + if (!tlb::csr_unpack(msg.info, info)) { + throw VmError{Excno::unknown, "invalid message"}; + } + ihr_disabled = info.ihr_disabled; + dest = std::move(info.dest); + Ref extra; + if (!block::tlb::t_CurrencyCollection.unpack_special(info.value.write(), value, extra)) { + throw VmError{Excno::unknown, "invalid message"}; + } + have_extra_currencies = !extra.is_null(); + user_fwd_fee = block::tlb::t_Grams.as_integer(info.fwd_fee); + user_ihr_fee = block::tlb::t_Grams.as_integer(info.ihr_fee); + } + + bool is_masterchain = parse_addr_workchain(*my_addr) == -1 || (!ext_msg && parse_addr_workchain(*dest) == -1); + td::Ref prices_cs; + if (st->get_global_version() >= 6) { + prices_cs = tuple_index(get_unpacked_config_tuple(st), is_masterchain ? 4 : 5).as_slice(); + } else { + Ref config_dict = get_param(st, 9).as_cell(); + Dictionary config{config_dict, 32}; + Ref prices_cell = config.lookup_ref(td::BitArray<32>{is_masterchain ? 24 : 25}); + if (prices_cell.not_null()) { + prices_cs = load_cell_slice_ref(prices_cell); + } + } + if (prices_cs.is_null()) { + throw VmError{Excno::unknown, "invalid prices config"}; + } + auto r_prices = block::Config::do_get_msg_prices(*prices_cs, is_masterchain ? 24 : 25); + if (r_prices.is_error()) { + throw VmError{Excno::cell_und, PSTRING() << "cannot parse config: " << r_prices.error().message()}; + } + block::MsgPrices prices = r_prices.move_as_ok(); + + // msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms + // bits in the root cell of a message are not included in msg.bits (lump_price pays for them) + td::uint64 max_cells; + if (st->get_global_version() >= 6) { + auto r_size_limits_config = + block::Config::do_get_size_limits_config(tuple_index(get_unpacked_config_tuple(st), 6).as_slice()); + if (r_size_limits_config.is_error()) { + throw VmError{Excno::cell_und, PSTRING() << "cannot parse config: " << r_size_limits_config.error().message()}; + } + max_cells = r_size_limits_config.ok().max_msg_cells; + } else { + max_cells = 1 << 13; + } + vm::VmStorageStat stat(max_cells); + CellSlice cs = load_cell_slice(msg_cell); + cs.skip_first(cs.size()); + stat.add_storage(cs); + + if (!ext_msg) { + if (mode & 128) { // value is balance of the contract + Ref balance = get_param(st, 7).as_tuple(); + if (balance.is_null()) { + throw VmError{Excno::type_chk, "invalid param BALANCE"}; + } + value = tuple_index(balance, 0).as_int(); + if (value.is_null()) { + throw VmError{Excno::type_chk, "invalid param BALANCE"}; + } + have_extra_currencies |= !tuple_index(balance, 1).as_cell().is_null(); + } else if (mode & 64) { // value += value of incoming message + Ref balance = get_param(st, 11).as_tuple(); + if (balance.is_null()) { + throw VmError{Excno::type_chk, "invalid param INCOMINGVALUE"}; + } + td::RefInt256 balance_grams = tuple_index(balance, 0).as_int(); + if (balance_grams.is_null()) { + throw VmError{Excno::type_chk, "invalid param INCOMINGVALUE"}; + } + value += balance_grams; + have_extra_currencies |= !tuple_index(balance, 1).as_cell().is_null(); + } + } + + bool have_init = msg.init->bit_at(0); + bool init_ref = have_init && msg.init->bit_at(1); + bool body_ref = msg.body->bit_at(0); + + td::RefInt256 fwd_fee, ihr_fee; + td::uint64 cells = stat.cells; + td::uint64 bits = stat.bits; + auto compute_fees = [&]() { + td::uint64 fwd_fee_short = prices.lump_price + td::uint128(prices.bit_price) + .mult(bits) + .add(td::uint128(prices.cell_price).mult(cells)) + .add(td::uint128(0xffffu)) + .shr(16) + .lo(); + td::uint64 ihr_fee_short; + if (ihr_disabled) { + ihr_fee_short = 0; + } else { + ihr_fee_short = td::uint128(fwd_fee_short).mult(prices.ihr_factor).shr(16).lo(); + } + fwd_fee = td::RefInt256{true, fwd_fee_short}; + ihr_fee = td::RefInt256{true, ihr_fee_short}; + fwd_fee = std::max(fwd_fee, user_fwd_fee); + if (!ihr_disabled) { + ihr_fee = std::max(ihr_fee, user_ihr_fee); + } + }; + compute_fees(); + + auto stored_grams_len = [](td::RefInt256 const& x) -> unsigned { + unsigned bits = x->bit_size(false); + return 4 + ((bits + 7) & ~7); + }; + + auto msg_root_bits = [&]() -> unsigned { + unsigned bits; + // CommonMsgInfo + if (ext_msg) { + bits = 2 + my_addr->size() + dest->size() + 32 + 64; + } else { + bits = 4 + my_addr->size() + dest->size() + stored_grams_len(value) + 1 + 32 + 64; + td::RefInt256 fwd_fee_first = (fwd_fee * prices.first_frac) >> 16; + bits += stored_grams_len(fwd_fee - fwd_fee_first); + bits += stored_grams_len(ihr_fee); + } + // init + bits++; + if (have_init) { + bits += 1 + (init_ref ? 0 : msg.init->size() - 2); + } + // body + bits++; + bits += (body_ref ? 0 : msg.body->size() - 1); + return bits; + }; + auto msg_root_refs = [&]() -> unsigned { + unsigned refs; + // CommonMsgInfo + if (ext_msg) { + refs = 0; + } else { + refs = have_extra_currencies; + } + // init + if (have_init) { + refs += (init_ref ? 1 : msg.init->size_refs()); + } + // body + refs += (body_ref ? 1 : msg.body->size_refs()); + return refs; + }; + + if (have_init && !init_ref && (msg_root_bits() > Cell::max_bits || msg_root_refs() > Cell::max_refs)) { + init_ref = true; + cells += 1; + bits += msg.init->size() - 2; + compute_fees(); + } + if (!body_ref && (msg_root_bits() > Cell::max_bits || msg_root_refs() > Cell::max_refs)) { + body_ref = true; + cells += 1; + bits += msg.body->size() - 1; + compute_fees(); + } + stack.push_int(fwd_fee + ihr_fee); + + if (send) { + CellBuilder cb; + if (!(cb.store_ref_bool(get_actions(st)) // out_list$_ {n:#} prev:^(OutList n) + && cb.store_long_bool(0x0ec3c86d, 32) // action_send_msg#0ec3c86d + && cb.store_long_bool(mode, 8) // mode:(## 8) + && cb.store_ref_bool(std::move(msg_cell)))) { + throw VmError{Excno::cell_ov, "cannot serialize raw output message into an output action cell"}; + } + return install_output_action(st, cb.finalize()); + } + return 0; +} + bool store_grams(CellBuilder& cb, td::RefInt256 value) { int k = value->bit_size(false); return k <= 15 * 8 && cb.store_long_bool((k + 7) >> 3, 4) && cb.store_int256_bool(*value, (k + 7) & -8, false); } int exec_reserve_raw(VmState* st, int mode) { - VM_LOG(st) << "execute RESERVERAW" << (mode & 1 ? "X" : ""); + VM_LOG(st) << "execute RAWRESERVE" << (mode & 1 ? "X" : ""); Stack& stack = st->get_stack(); - stack.check_underflow(2); - int f = stack.pop_smallint_range(3); - td::RefInt256 x; - Ref csr; + stack.check_underflow(2 + (mode & 1)); + int f = stack.pop_smallint_range(st->get_global_version() >= 4 ? 31 : 15); + Ref y; if (mode & 1) { - csr = stack.pop_cellslice(); - } else { - x = stack.pop_int_finite(); - if (td::sgn(x) < 0) { - throw VmError{Excno::range_chk, "amount of nanograms must be non-negative"}; - } + y = stack.pop_maybe_cell(); + } + auto x = stack.pop_int_finite(); + if (td::sgn(x) < 0) { + throw VmError{Excno::range_chk, "amount of nanograms must be non-negative"}; } CellBuilder cb; if (!(cb.store_ref_bool(get_actions(st)) // out_list$_ {n:#} prev:^(OutList n) && cb.store_long_bool(0x36e6b809, 32) // action_reserve_currency#36e6b809 && cb.store_long_bool(f, 8) // mode:(## 8) - && (mode & 1 ? cb.append_cellslice_bool(std::move(csr)) - : (store_grams(cb, std::move(x)) && cb.store_bool_bool(false))))) { + && store_grams(cb, std::move(x)) // + && cb.store_maybe_ref(std::move(y)))) { throw VmError{Excno::cell_ov, "cannot serialize raw reserved currency amount into an output action cell"}; } return install_output_action(st, cb.finalize()); @@ -775,12 +1892,20 @@ int exec_set_lib_code(VmState* st) { VM_LOG(st) << "execute SETLIBCODE"; Stack& stack = st->get_stack(); stack.check_underflow(2); - int mode = stack.pop_smallint_range(2); + int mode; + if (st->get_global_version() >= 4) { + mode = stack.pop_smallint_range(31); + if ((mode & ~16) > 2) { + throw VmError{Excno::range_chk}; + } + } else { + mode = stack.pop_smallint_range(2); + } auto code = stack.pop_cell(); CellBuilder cb; if (!(cb.store_ref_bool(get_actions(st)) // out_list$_ {n:#} prev:^(OutList n) && cb.store_long_bool(0x26fa1dd4, 32) // action_change_library#26fa1dd4 - && cb.store_long_bool(mode * 2 + 1, 8) // mode:(## 7) { mode <= 2 } + && cb.store_long_bool(mode * 2 + 1, 8) // mode:(## 7) && cb.store_ref_bool(std::move(code)))) { // libref:LibRef = OutAction; throw VmError{Excno::cell_ov, "cannot serialize new library code into an output action cell"}; } @@ -791,7 +1916,15 @@ int exec_change_lib(VmState* st) { VM_LOG(st) << "execute CHANGELIB"; Stack& stack = st->get_stack(); stack.check_underflow(2); - int mode = stack.pop_smallint_range(2); + int mode; + if (st->get_global_version() >= 4) { + mode = stack.pop_smallint_range(31); + if ((mode & ~16) > 2) { + throw VmError{Excno::range_chk}; + } + } else { + mode = stack.pop_smallint_range(2); + } auto hash = stack.pop_int_finite(); if (!hash->unsigned_fits_bits(256)) { throw VmError{Excno::range_chk, "library hash must be non-negative"}; @@ -809,11 +1942,12 @@ int exec_change_lib(VmState* st) { void register_ton_message_ops(OpcodeTable& cp0) { using namespace std::placeholders; cp0.insert(OpcodeInstr::mksimple(0xfb00, 16, "SENDRAWMSG", exec_send_raw_message)) - .insert(OpcodeInstr::mksimple(0xfb02, 16, "RESERVERAW", std::bind(exec_reserve_raw, _1, 0))) - .insert(OpcodeInstr::mksimple(0xfb03, 16, "RESERVERAWX", std::bind(exec_reserve_raw, _1, 1))) + .insert(OpcodeInstr::mksimple(0xfb02, 16, "RAWRESERVE", std::bind(exec_reserve_raw, _1, 0))) + .insert(OpcodeInstr::mksimple(0xfb03, 16, "RAWRESERVEX", std::bind(exec_reserve_raw, _1, 1))) .insert(OpcodeInstr::mksimple(0xfb04, 16, "SETCODE", exec_set_code)) .insert(OpcodeInstr::mksimple(0xfb06, 16, "SETLIBCODE", exec_set_lib_code)) - .insert(OpcodeInstr::mksimple(0xfb07, 16, "CHANGELIB", exec_change_lib)); + .insert(OpcodeInstr::mksimple(0xfb07, 16, "CHANGELIB", exec_change_lib)) + .insert(OpcodeInstr::mksimple(0xfb08, 16, "SENDMSG", exec_send_message)->require_version(4)); } void register_ton_ops(OpcodeTable& cp0) { @@ -822,8 +1956,163 @@ void register_ton_ops(OpcodeTable& cp0) { register_prng_ops(cp0); register_ton_config_ops(cp0); register_ton_crypto_ops(cp0); + register_ton_misc_ops(cp0); register_ton_currency_address_ops(cp0); register_ton_message_ops(cp0); } +namespace util { + +bool load_var_integer_q(CellSlice& cs, td::RefInt256& res, int len_bits, bool sgnd, bool quiet) { + CellSlice cs0 = cs; + int len; + if (!(cs.fetch_uint_to(len_bits, len) && cs.fetch_int256_to(len * 8, res, sgnd))) { + cs = std::move(cs0); + if (quiet) { + return false; + } + throw VmError{Excno::cell_und, "cannot deserialize a variable-length integer"}; + } + return true; +} +bool load_coins_q(CellSlice& cs, td::RefInt256& res, bool quiet) { + return load_var_integer_q(cs, res, 4, false, quiet); +} +bool load_msg_addr_q(CellSlice& cs, CellSlice& res, bool quiet) { + res = cs; + if (!skip_message_addr(cs)) { + cs = res; + if (quiet) { + return false; + } + throw VmError{Excno::cell_und, "cannot load a MsgAddress"}; + } + res.cut_tail(cs); + return true; +} +bool parse_std_addr_q(CellSlice cs, ton::WorkchainId& res_wc, ton::StdSmcAddress& res_addr, bool quiet) { + // Like exec_rewrite_message_addr, but for std address case + std::vector tuple; + if (!(parse_message_addr(cs, tuple) && cs.empty_ext())) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und, "cannot parse a MsgAddress"}; + } + int t = (int)std::move(tuple[0]).as_int()->to_long(); + if (t != 2 && t != 3) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und, "cannot parse a MsgAddressInt"}; + } + auto addr = std::move(tuple[3]).as_slice(); + auto prefix = std::move(tuple[1]).as_slice(); + if (addr->size() != 256) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und, "MsgAddressInt is not a standard 256-bit address"}; + } + res_wc = (int)tuple[2].as_int()->to_long(); + CHECK(addr->prefetch_bits_to(res_addr) && + (prefix.is_null() || prefix->prefetch_bits_to(res_addr.bits(), prefix->size()))); + return true; +} + +td::RefInt256 load_var_integer(CellSlice& cs, int len_bits, bool sgnd) { + td::RefInt256 x; + load_var_integer_q(cs, x, len_bits, sgnd, false); + return x; +} +td::RefInt256 load_coins(CellSlice& cs) { + return load_var_integer(cs, 4, false); +} +CellSlice load_msg_addr(CellSlice& cs) { + CellSlice addr; + load_msg_addr_q(cs, addr, false); + return addr; +} +std::pair parse_std_addr(CellSlice cs) { + std::pair res; + parse_std_addr_q(std::move(cs), res.first, res.second, false); + return res; +} + +bool store_var_integer(CellBuilder& cb, const td::RefInt256& x, int len_bits, bool sgnd, bool quiet) { + unsigned len = (((unsigned)x->bit_size(sgnd) + 7) >> 3); + if (len >= (1u << len_bits)) { + throw VmError{Excno::range_chk}; // throw even if quiet + } + if (!cb.can_extend_by(len_bits + len * 8)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_ov, "cannot serialize a variable-length integer"}; + } + CHECK(cb.store_long_bool(len, len_bits) && cb.store_int256_bool(*x, len * 8, sgnd)); + return true; +} +bool store_coins(CellBuilder& cb, const td::RefInt256& x, bool quiet) { + return store_var_integer(cb, x, 4, false, quiet); +} + +block::GasLimitsPrices get_gas_prices(const Ref& unpacked_config, bool is_masterchain) { + Ref cs = tuple_index(unpacked_config, is_masterchain ? 2 : 3).as_slice(); + if (cs.is_null()) { + throw VmError{Excno::type_chk, "intermediate value is not a slice"}; + } + auto r_prices = block::Config::do_get_gas_limits_prices(*cs, is_masterchain ? 20 : 21); + if (r_prices.is_error()) { + throw VmError{Excno::cell_und, PSTRING() << "cannot parse config: " << r_prices.error().message()}; + } + return r_prices.move_as_ok(); +} + +block::MsgPrices get_msg_prices(const Ref& unpacked_config, bool is_masterchain) { + Ref cs = tuple_index(unpacked_config, is_masterchain ? 4 : 5).as_slice(); + if (cs.is_null()) { + throw VmError{Excno::type_chk, "intermediate value is not a slice"}; + } + auto r_prices = block::Config::do_get_msg_prices(*cs, is_masterchain ? 24 : 25); + if (r_prices.is_error()) { + throw VmError{Excno::cell_und, PSTRING() << "cannot parse config: " << r_prices.error().message()}; + } + return r_prices.move_as_ok(); +} + +td::optional get_storage_prices(const Ref& unpacked_config) { + Ref cs = tuple_index(unpacked_config, 0).as_slice(); + if (cs.is_null()) { + // null means tat no StoragePrices is active, so the price is 0 + return {}; + } + auto r_prices = block::Config::do_get_one_storage_prices(*cs); + if (r_prices.is_error()) { + throw VmError{Excno::cell_und, PSTRING() << "cannot parse config: " << r_prices.error().message()}; + } + return r_prices.move_as_ok(); +} + +td::RefInt256 calculate_storage_fee(const td::optional& maybe_prices, bool is_masterchain, + td::uint64 delta, td::uint64 bits, td::uint64 cells) { + if (!maybe_prices) { + // no StoragePrices is active, so the price is 0 + return td::zero_refint(); + } + const block::StoragePrices& prices = maybe_prices.value(); + td::RefInt256 total; + if (is_masterchain) { + total = td::make_refint(cells) * prices.mc_cell_price; + total += td::make_refint(bits) * prices.mc_bit_price; + } else { + total = td::make_refint(cells) * prices.cell_price; + total += td::make_refint(bits) * prices.bit_price; + } + total *= delta; + return td::rshift(total, 16, 1); +} + +} // namespace util + } // namespace vm diff --git a/crypto/vm/tonops.h b/crypto/vm/tonops.h index c2809e91..bbac078f 100644 --- a/crypto/vm/tonops.h +++ b/crypto/vm/tonops.h @@ -14,9 +14,12 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once +#include "vm/vm.h" +#include "ton/ton-types.h" +#include "mc-config.h" namespace vm { @@ -24,4 +27,30 @@ class OpcodeTable; void register_ton_ops(OpcodeTable& cp0); +namespace util { + +// "_q" functions throw on error if not quiet, return false if quiet (leaving cs unchanged) +bool load_var_integer_q(CellSlice& cs, td::RefInt256& res, int len_bits, bool sgnd, bool quiet); +bool load_coins_q(CellSlice& cs, td::RefInt256& res, bool quiet); +bool load_msg_addr_q(CellSlice& cs, CellSlice& res, bool quiet); +bool parse_std_addr_q(CellSlice cs, ton::WorkchainId& res_wc, ton::StdSmcAddress& res_addr, bool quiet); + +// Non-"_q" functions throw on error +td::RefInt256 load_var_integer(CellSlice& cs, int len_bits, bool sgnd); +td::RefInt256 load_coins(CellSlice& cs); +CellSlice load_msg_addr(CellSlice& cs); +std::pair parse_std_addr(CellSlice cs); + +// store_... functions throw on error if not quiet, return false if quiet (leaving cb unchanged) +bool store_var_integer(CellBuilder& cb, const td::RefInt256& x, int len_bits, bool sgnd, bool quiet = false); +bool store_coins(CellBuilder& cb, const td::RefInt256& x, bool quiet = false); + +block::GasLimitsPrices get_gas_prices(const td::Ref& unpacked_config, bool is_masterchain); +block::MsgPrices get_msg_prices(const td::Ref& unpacked_config, bool is_masterchain); +td::optional get_storage_prices(const td::Ref& unpacked_config); +td::RefInt256 calculate_storage_fee(const td::optional& maybe_prices, bool is_masterchain, + td::uint64 delta, td::uint64 bits, td::uint64 cells); + +} // namespace util + } // namespace vm diff --git a/crypto/vm/tupleops.cpp b/crypto/vm/tupleops.cpp index de4de834..f4be2c76 100644 --- a/crypto/vm/tupleops.cpp +++ b/crypto/vm/tupleops.cpp @@ -14,14 +14,14 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/log.h" #include "vm/stackops.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" namespace vm { @@ -53,6 +53,23 @@ int exec_null_swap_if(VmState* st, bool cond, int depth) { return 0; } +int exec_null_swap_if_many(VmState* st, bool cond, int depth, int count) { + Stack& stack = st->get_stack(); + VM_LOG(st) << "execute NULL" << (depth ? "ROTR" : "SWAP") << (cond ? "IF" : "IFNOT") << count; + stack.check_underflow(depth + 1); + auto x = stack.pop_int_finite(); + if (!x->sgn() != cond) { + for (int i = 0; i < count; i++) { + stack.push({}); + } + for (int i = 0; i < depth; i++) { + swap(stack[i], stack[i + count]); + } + } + stack.push_int(std::move(x)); + return 0; +} + int exec_mktuple_common(VmState* st, unsigned n) { Stack& stack = st->get_stack(); stack.check_underflow(n); @@ -82,7 +99,7 @@ int exec_mktuple_var(VmState* st) { int exec_tuple_index_common(Stack& stack, unsigned n) { auto tuple = stack.pop_tuple_range(255); - stack.push(tuple_index(*tuple, n)); + stack.push(tuple_index(tuple, n)); return 0; } @@ -305,11 +322,11 @@ int exec_tuple_index2(VmState* st, unsigned args) { VM_LOG(st) << "execute INDEX2 " << i << "," << j; Stack& stack = st->get_stack(); auto tuple = stack.pop_tuple_range(255); - auto t1 = tuple_index(*tuple, i).as_tuple_range(255); + auto t1 = tuple_index(tuple, i).as_tuple_range(255); if (t1.is_null()) { throw VmError{Excno::type_chk, "intermediate value is not a tuple"}; } - stack.push(tuple_index(*t1, j)); + stack.push(tuple_index(t1, j)); return 0; } @@ -325,15 +342,15 @@ int exec_tuple_index3(VmState* st, unsigned args) { VM_LOG(st) << "execute INDEX3 " << i << "," << j << "," << k; Stack& stack = st->get_stack(); auto tuple = stack.pop_tuple_range(255); - auto t1 = tuple_index(*tuple, i).as_tuple_range(255); + auto t1 = tuple_index(tuple, i).as_tuple_range(255); if (t1.is_null()) { throw VmError{Excno::type_chk, "intermediate value is not a tuple"}; } - auto t2 = tuple_index(*t1, j).as_tuple_range(255); + auto t2 = tuple_index(t1, j).as_tuple_range(255); if (t2.is_null()) { throw VmError{Excno::type_chk, "intermediate value is not a tuple"}; } - stack.push(tuple_index(*t2, k)); + stack.push(tuple_index(t2, k)); return 0; } @@ -374,6 +391,10 @@ void register_tuple_ops(OpcodeTable& cp0) { .insert(OpcodeInstr::mksimple(0x6fa1, 16, "NULLSWAPIFNOT", std::bind(exec_null_swap_if, _1, false, 0))) .insert(OpcodeInstr::mksimple(0x6fa2, 16, "NULLROTRIF", std::bind(exec_null_swap_if, _1, true, 1))) .insert(OpcodeInstr::mksimple(0x6fa3, 16, "NULLROTRIFNOT", std::bind(exec_null_swap_if, _1, false, 1))) + .insert(OpcodeInstr::mksimple(0x6fa4, 16, "NULLSWAPIF2", std::bind(exec_null_swap_if_many, _1, true, 0, 2))) + .insert(OpcodeInstr::mksimple(0x6fa5, 16, "NULLSWAPIFNOT2", std::bind(exec_null_swap_if_many, _1, false, 0, 2))) + .insert(OpcodeInstr::mksimple(0x6fa6, 16, "NULLROTRIF2", std::bind(exec_null_swap_if_many, _1, true, 1, 2))) + .insert(OpcodeInstr::mksimple(0x6fa7, 16, "NULLROTRIFNOT2", std::bind(exec_null_swap_if_many, _1, false, 1, 2))) .insert(OpcodeInstr::mkfixed(0x6fb, 12, 4, dump_tuple_index2, exec_tuple_index2)) .insert(OpcodeInstr::mkfixed(0x6fc >> 2, 10, 6, dump_tuple_index3, exec_tuple_index3)); } diff --git a/crypto/vm/tupleops.h b/crypto/vm/tupleops.h index cef8a0f4..f7d48959 100644 --- a/crypto/vm/tupleops.h +++ b/crypto/vm/tupleops.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/crypto/vm/utils.cpp b/crypto/vm/utils.cpp index 25e01f21..783bf132 100644 --- a/crypto/vm/utils.cpp +++ b/crypto/vm/utils.cpp @@ -1,3 +1,21 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2019-2020 Telegram Systems LLP +*/ #include "utils.h" namespace vm { @@ -88,7 +106,7 @@ td::Result convert_stack_entry(td::Slice str) { return vm::StackEntry{ Ref{true, vm::CellBuilder().store_bits(td::ConstBitPtr{buff}, bits).finalize()}}; } - auto num = td::RefInt256{true}; + auto num = td::make_refint(); auto& x = num.unique_write(); if (l >= 3 && str[0] == '0' && str[1] == 'x') { if (x.parse_hex(str.data() + 2, l - 2) != l - 2) { diff --git a/crypto/vm/utils.h b/crypto/vm/utils.h index e1afe60c..34d761b1 100644 --- a/crypto/vm/utils.h +++ b/crypto/vm/utils.h @@ -1,3 +1,21 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2019-2020 Telegram Systems LLP +*/ #pragma once #include "stack.hpp" diff --git a/crypto/vm/vm.cpp b/crypto/vm/vm.cpp new file mode 100644 index 00000000..3f595a00 --- /dev/null +++ b/crypto/vm/vm.cpp @@ -0,0 +1,786 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ +#include "vm/dispatch.h" +#include "vm/continuation.h" +#include "vm/dict.h" +#include "vm/log.h" +#include "vm/vm.h" +#include "cp0.h" +#include + +namespace vm { + +VmState::VmState() : cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) { + ensure_throw(init_cp(0)); + init_cregs(); +} + +VmState::VmState(Ref _code) + : code(std::move(_code)), cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) { + ensure_throw(init_cp(0)); + init_cregs(); +} + +VmState::VmState(Ref _code, Ref _stack, int flags, Ref _data, VmLog log, + std::vector> _libraries, Ref init_c7) + : code(std::move(_code)) + , stack(std::move(_stack)) + , cp(-1) + , dispatch(&dummy_dispatch_table) + , quit0(true, 0) + , quit1(true, 1) + , log(log) + , libraries(std::move(_libraries)) + , stack_trace((flags >> 2) & 1) { + ensure_throw(init_cp(0)); + set_c4(std::move(_data)); + if (init_c7.not_null()) { + set_c7(std::move(init_c7)); + } + init_cregs(flags & 1, flags & 2); +} + +VmState::VmState(Ref _code, Ref _stack, const GasLimits& gas, int flags, Ref _data, VmLog log, + std::vector> _libraries, Ref init_c7) + : code(std::move(_code)) + , stack(std::move(_stack)) + , cp(-1) + , dispatch(&dummy_dispatch_table) + , quit0(true, 0) + , quit1(true, 1) + , log(log) + , gas(gas) + , libraries(std::move(_libraries)) + , stack_trace((flags >> 2) & 1) { + ensure_throw(init_cp(0)); + set_c4(std::move(_data)); + if (init_c7.not_null()) { + set_c7(std::move(init_c7)); + } + init_cregs(flags & 1, flags & 2); +} + +void VmState::init_cregs(bool same_c3, bool push_0) { + cr.set_c0(quit0); + cr.set_c1(quit1); + cr.set_c2(Ref{true}); + if (same_c3) { + cr.set_c3(Ref{true, code, cp}); + if (push_0) { + VM_LOG(this) << "implicit PUSH 0 at start\n"; + get_stack().push_smallint(0); + } + } else { + cr.set_c3(Ref{true, 11}); + } + if (cr.d[0].is_null() || cr.d[1].is_null()) { + auto empty_cell = CellBuilder{}.finalize(); + for (int i = 0; i < ControlRegs::dreg_num; i++) { + if (cr.d[i].is_null()) { + cr.d[i] = empty_cell; + } + } + } + if (cr.c7.is_null()) { + cr.set_c7(Ref{true}); + } +} + +Ref VmState::convert_code_cell(Ref code_cell) { + if (code_cell.is_null()) { + return {}; + } + Ref csr{true, NoVmOrd(), code_cell}; + if (csr->is_valid()) { + return csr; + } + return load_cell_slice_ref(CellBuilder{}.store_ref(std::move(code_cell)).finalize()); +} + +bool VmState::init_cp(int new_cp) { + const DispatchTable* dt = DispatchTable::get_table(new_cp); + if (dt) { + cp = new_cp; + dispatch = dt; + return true; + } else { + return false; + } +} + +bool VmState::set_cp(int new_cp) { + return new_cp == cp || init_cp(new_cp); +} + +void VmState::force_cp(int new_cp) { + if (!set_cp(new_cp)) { + throw VmError{Excno::inv_opcode, "unsupported codepage"}; + } +} + +// simple call to a continuation cont +int VmState::call(Ref cont) { + const ControlData* cont_data = cont->get_cdata(); + if (cont_data) { + if (cont_data->save.c[0].not_null()) { + // call reduces to a jump + return jump(std::move(cont)); + } + if (cont_data->stack.not_null() || cont_data->nargs >= 0) { + // if cont has non-empty stack or expects fixed number of arguments, call is not simple + return call(std::move(cont), -1, -1); + } + // create return continuation, to be stored into new c0 + Ref ret = Ref{true, std::move(code), cp}; + ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); + cr.set_c0( + std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set + return jump_to(std::move(cont)); + } + // create return continuation, to be stored into new c0 + Ref ret = Ref{true, std::move(code), cp}; + ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); + // general implementation of a simple call + cr.set_c0(std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set + return jump_to(std::move(cont)); +} + +// call with parameters to continuation cont +int VmState::call(Ref cont, int pass_args, int ret_args) { + const ControlData* cont_data = cont->get_cdata(); + if (cont_data) { + if (cont_data->save.c[0].not_null()) { + // call reduces to a jump + return jump(std::move(cont), pass_args); + } + int depth = stack->depth(); + if (pass_args > depth || cont_data->nargs > depth) { + throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"}; + } + if (cont_data->nargs > pass_args && pass_args >= 0) { + throw VmError{Excno::stk_und, + "stack underflow while calling a closure continuation: not enough arguments passed"}; + } + auto old_c0 = std::move(cr.c[0]); + // optimization(?): decrease refcnts of unused continuations in c[i] as early as possible + preclear_cr(cont_data->save); + // no exceptions should be thrown after this point + int copy = cont_data->nargs, skip = 0; + if (pass_args >= 0) { + if (copy >= 0) { + skip = pass_args - copy; + } else { + copy = pass_args; + } + } + // copy=-1 : pass whole stack, else pass top `copy` elements, drop next `skip` elements. + Ref new_stk; + if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) { + // `cont` already has a stack, create resulting stack from it + if (copy < 0) { + copy = stack->depth(); + } + if (cont->is_unique()) { + // optimization: avoid copying stack if we hold the only copy of `cont` + new_stk = std::move(cont.unique_write().get_cdata()->stack); + } else { + new_stk = cont_data->stack; + } + new_stk.write().move_from_stack(get_stack(), copy); + if (skip > 0) { + get_stack().pop_many(skip); + } + consume_stack_gas(new_stk); + } else if (copy >= 0) { + new_stk = get_stack().split_top(copy, skip); + consume_stack_gas(new_stk); + } else { + new_stk = std::move(stack); + stack.clear(); + } + // create return continuation using the remainder of current stack + Ref ret = Ref{true, std::move(code), cp, std::move(stack), ret_args}; + ret.unique_write().get_cdata()->save.set_c0(std::move(old_c0)); + Ref ord_cont = static_cast>(cont); + set_stack(std::move(new_stk)); + cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0 + return jump_to(std::move(cont)); + } else { + // have no continuation data, situation is somewhat simpler + int depth = stack->depth(); + if (pass_args > depth) { + throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"}; + } + // create new stack from the top `pass_args` elements of the current stack + Ref new_stk; + if (pass_args >= 0) { + new_stk = get_stack().split_top(pass_args); + consume_stack_gas(new_stk); + } else { + new_stk = std::move(stack); + } + // create return continuation using the remainder of the current stack + Ref ret = Ref{true, std::move(code), cp, std::move(stack), ret_args}; + ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); + set_stack(std::move(new_stk)); + cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0 + return jump_to(std::move(cont)); + } +} + +// simple jump to continuation cont +int VmState::jump(Ref cont) { + const ControlData* cont_data = cont->get_cdata(); + if (cont_data && (cont_data->stack.not_null() || cont_data->nargs >= 0)) { + // if cont has non-empty stack or expects fixed number of arguments, jump is not simple + return jump(std::move(cont), -1); + } else { + return jump_to(std::move(cont)); + } +} + +// general jump to continuation cont +int VmState::jump(Ref cont, int pass_args) { + const ControlData* cont_data = cont->get_cdata(); + if (cont_data) { + // first do the checks + int depth = stack->depth(); + if (pass_args > depth || cont_data->nargs > depth) { + throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"}; + } + if (cont_data->nargs > pass_args && pass_args >= 0) { + throw VmError{Excno::stk_und, + "stack underflow while jumping to closure continuation: not enough arguments passed"}; + } + // optimization(?): decrease refcnts of unused continuations in c[i] as early as possible + preclear_cr(cont_data->save); + // no exceptions should be thrown after this point + int copy = cont_data->nargs; + if (pass_args >= 0 && copy < 0) { + copy = pass_args; + } + // copy=-1 : pass whole stack, else pass top `copy` elements, drop the remainder. + if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) { + // `cont` already has a stack, create resulting stack from it + if (copy < 0) { + copy = get_stack().depth(); + } + Ref new_stk; + if (cont->is_unique()) { + // optimization: avoid copying the stack if we hold the only copy of `cont` + new_stk = std::move(cont.unique_write().get_cdata()->stack); + } else { + new_stk = cont_data->stack; + } + new_stk.write().move_from_stack(get_stack(), copy); + consume_stack_gas(new_stk); + set_stack(std::move(new_stk)); + } else { + if (copy >= 0 && copy < stack->depth()) { + get_stack().drop_bottom(stack->depth() - copy); + consume_stack_gas(copy); + } + } + return jump_to(std::move(cont)); + } else { + // have no continuation data, situation is somewhat simpler + if (pass_args >= 0) { + int depth = get_stack().depth(); + if (pass_args > depth) { + throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"}; + } else if (pass_args < depth) { + get_stack().drop_bottom(depth - pass_args); + consume_stack_gas(pass_args); + } + } + return jump_to(std::move(cont)); + } +} + +int VmState::ret() { + Ref cont = quit0; + cont.swap(cr.c[0]); + return jump(std::move(cont)); +} + +int VmState::ret(int ret_args) { + Ref cont = quit0; + cont.swap(cr.c[0]); + return jump(std::move(cont), ret_args); +} + +int VmState::ret_alt() { + Ref cont = quit1; + cont.swap(cr.c[1]); + return jump(std::move(cont)); +} + +int VmState::ret_alt(int ret_args) { + Ref cont = quit1; + cont.swap(cr.c[1]); + return jump(std::move(cont), ret_args); +} + +Ref VmState::c1_envelope(Ref cont, bool save) { + if (save) { + force_cregs(cont)->define_c1(cr.c[1]); + force_cregs(cont)->define_c0(cr.c[0]); + } + set_c1(cont); + return cont; +} + +void VmState::c1_save_set(bool save) { + if (save) { + force_cregs(cr.c[0])->define_c1(cr.c[1]); + } + set_c1(cr.c[0]); +} + +Ref VmState::extract_cc(int save_cr, int stack_copy, int cc_args) { + Ref new_stk; + if (stack_copy < 0 || stack_copy == stack->depth()) { + new_stk = std::move(stack); + stack.clear(); + } else if (stack_copy > 0) { + stack->check_underflow(stack_copy); + new_stk = get_stack().split_top(stack_copy); + consume_stack_gas(new_stk); + } else { + new_stk = Ref{true}; + } + Ref cc = Ref{true, std::move(code), cp, std::move(stack), cc_args}; + stack = std::move(new_stk); + if (save_cr & 7) { + ControlData* cdata = cc.unique_write().get_cdata(); + if (save_cr & 1) { + cdata->save.set_c0(std::move(cr.c[0])); + cr.set_c0(quit0); + } + if (save_cr & 2) { + cdata->save.set_c1(std::move(cr.c[1])); + cr.set_c1(quit1); + } + if (save_cr & 4) { + cdata->save.set_c2(std::move(cr.c[2])); + // cr.set_c2(Ref{true}); + } + } + return cc; +} + +int VmState::throw_exception(int excno) { + Stack& stack_ref = get_stack(); + stack_ref.clear(); + stack_ref.push_smallint(0); + stack_ref.push_smallint(excno); + code.clear(); + consume_gas_chk(exception_gas_price); + return jump(get_c2()); +} + +int VmState::throw_exception(int excno, StackEntry&& arg) { + Stack& stack_ref = get_stack(); + stack_ref.clear(); + stack_ref.push(std::move(arg)); + stack_ref.push_smallint(excno); + code.clear(); + consume_gas_chk(exception_gas_price); + return jump(get_c2()); +} + +void GasLimits::gas_exception() const { + throw VmNoGas{}; +} + +void GasLimits::set_limits(long long _max, long long _limit, long long _credit) { + gas_max = _max; + gas_limit = _limit; + gas_credit = _credit; + change_base(_limit + _credit); +} + +void GasLimits::change_limit(long long _limit) { + _limit = std::min(std::max(_limit, 0LL), gas_max); + gas_credit = 0; + gas_limit = _limit; + change_base(_limit); +} + +bool VmState::set_gas_limits(long long _max, long long _limit, long long _credit) { + gas.set_limits(_max, _limit, _credit); + return true; +} + +void VmState::change_gas_limit(long long new_limit) { + VM_LOG(this) << "changing gas limit to " << std::min(new_limit, gas.gas_max); + gas.change_limit(new_limit); +} + +int VmState::step() { + CHECK(code.not_null() && stack.not_null()); + if (log.log_mask & vm::VmLog::DumpStack) { + std::stringstream ss; + int mode = 3; + if (log.log_mask & vm::VmLog::DumpStackVerbose) { + mode += 4; + } + stack->dump(ss, mode); + VM_LOG(this) << "stack:" << ss.str(); + } + if (stack_trace) { + stack->dump(std::cerr, 3); + } + ++steps; + if (code->size()) { + VM_LOG_MASK(this, vm::VmLog::ExecLocation) << "code cell hash: " << code->get_base_cell()->get_hash().to_hex() << " offset: " << code->cur_pos(); + return dispatch->dispatch(this, code.write()); + } else if (code->size_refs()) { + VM_LOG(this) << "execute implicit JMPREF"; + auto ref_cell = code->prefetch_ref(); + VM_LOG_MASK(this, vm::VmLog::ExecLocation) << "code cell hash: " << ref_cell->get_hash().to_hex() << " offset: 0"; + consume_gas_chk(implicit_jmpref_gas_price); + Ref cont = Ref{true, load_cell_slice_ref(std::move(ref_cell)), get_cp()}; + return jump(std::move(cont)); + } else { + VM_LOG(this) << "execute implicit RET"; + consume_gas_chk(implicit_ret_gas_price); + return ret(); + } +} + +int VmState::run_inner() { + int res; + Guard guard(this); + do { + try { + try { + res = step(); + VM_LOG_MASK(this, vm::VmLog::GasRemaining) << "gas remaining: " << gas.gas_remaining; + gas.check(); + } catch (vm::CellBuilder::CellWriteError) { + throw VmError{Excno::cell_ov}; + } catch (vm::CellBuilder::CellCreateError) { + throw VmError{Excno::cell_ov}; + } catch (vm::CellSlice::CellReadError) { + throw VmError{Excno::cell_und}; + } + } catch (const VmError& vme) { + VM_LOG(this) << "handling exception code " << vme.get_errno() << ": " << vme.get_msg(); + try { + ++steps; + res = throw_exception(vme.get_errno()); + } catch (const VmError& vme2) { + VM_LOG(this) << "exception " << vme2.get_errno() << " while handling exception: " << vme.get_msg(); + return ~vme2.get_errno(); + } + } + } while (!res); + if ((res | 1) == -1 && !try_commit()) { + VM_LOG(this) << "automatic commit failed (new data or action cells too deep)"; + get_stack().clear(); + get_stack().push_smallint(0); + return ~(int)Excno::cell_ov; + } + return res; +} + +int VmState::run() { + if (code.is_null() || stack.is_null()) { + // throw VmError{Excno::fatal, "cannot run an uninitialized VM"}; + return (int)Excno::fatal; // no ~ for unhandled exceptions + } + int res = 0; + bool restore_parent = false; + while (true) { + try { + if (restore_parent) { + restore_parent_vm(~res); + } + res = run_inner(); + } catch (VmNoGas &vmoog) { + ++steps; + VM_LOG(this) << "unhandled out-of-gas exception: gas consumed=" << gas.gas_consumed() + << ", limit=" << gas.gas_limit; + get_stack().clear(); + get_stack().push_smallint(gas.gas_consumed()); + res = vmoog.get_errno(); // no ~ for unhandled exceptions (to make their faking impossible) + } + if (!parent) { + return res; + } + restore_parent = true; + } +} + +bool VmState::try_commit() { + if (cr.d[0].not_null() && cr.d[1].not_null() && cr.d[0]->get_depth() <= max_data_depth && + cr.d[1]->get_depth() <= max_data_depth && cr.d[0]->get_level() == 0 && cr.d[1]->get_level() == 0) { + cstate.c4 = cr.d[0]; + cstate.c5 = cr.d[1]; + cstate.committed = true; + return true; + } else { + return false; + } +} + +void VmState::force_commit() { + if (!try_commit()) { + throw VmError{Excno::cell_ov, "cannot commit too deep cells as new data/actions"}; + } +} + +ControlData* force_cdata(Ref& cont) { + if (!cont->get_cdata()) { + cont = Ref{true, cont}; + return cont.unique_write().get_cdata(); + } else { + return cont.write().get_cdata(); + } +} + +ControlRegs* force_cregs(Ref& cont) { + return &force_cdata(cont)->save; +} + +int run_vm_code(Ref code, Ref& stack, int flags, Ref* data_ptr, VmLog log, long long* steps, + GasLimits* gas_limits, std::vector> libraries, Ref init_c7, Ref* actions_ptr, + int global_version) { + VmState vm{code, + std::move(stack), + gas_limits ? *gas_limits : GasLimits{}, + flags, + data_ptr ? *data_ptr : Ref{}, + log, + std::move(libraries), + std::move(init_c7)}; + vm.set_global_version(global_version); + int res = vm.run(); + stack = vm.get_stack_ref(); + if (vm.committed() && data_ptr) { + *data_ptr = vm.get_committed_state().c4; + } + if (vm.committed() && actions_ptr) { + *actions_ptr = vm.get_committed_state().c5; + } + if (steps) { + *steps = vm.get_steps_count(); + } + if (gas_limits) { + *gas_limits = vm.get_gas_limits(); + LOG(INFO) << "steps: " << vm.get_steps_count() << " gas: used=" << gas_limits->gas_consumed() + << ", max=" << gas_limits->gas_max << ", limit=" << gas_limits->gas_limit + << ", credit=" << gas_limits->gas_credit; + } + if ((vm.get_log().log_mask & vm::VmLog::DumpStack) != 0) { + VM_LOG(&vm) << "BEGIN_STACK_DUMP"; + for (int i = stack->depth(); i > 0; i--) { + VM_LOG(&vm) << (*stack)[i - 1].to_string(); + } + VM_LOG(&vm) << "END_STACK_DUMP"; + } + + return ~res; +} + +int run_vm_code(Ref code, Stack& stack, int flags, Ref* data_ptr, VmLog log, long long* steps, + GasLimits* gas_limits, std::vector> libraries, Ref init_c7, Ref* actions_ptr, + int global_version) { + Ref stk{true}; + stk.unique_write().set_contents(std::move(stack)); + stack.clear(); + int res = run_vm_code(code, stk, flags, data_ptr, log, steps, gas_limits, std::move(libraries), std::move(init_c7), + actions_ptr, global_version); + CHECK(stack.is_unique()); + if (stk.is_null()) { + stack.clear(); + } else if (&(*stk) != &stack) { + VmState* st = nullptr; + if (stk->is_unique()) { + VM_LOG(st) << "move resulting stack (" << stk->depth() << " entries)"; + stack.set_contents(std::move(stk.unique_write())); + } else { + VM_LOG(st) << "copying resulting stack (" << stk->depth() << " entries)"; + stack.set_contents(*stk); + } + } + return res; +} + +// may throw a dictionary exception; returns nullptr if library is not found in context +Ref VmState::load_library(td::ConstBitPtr hash) { + std::unique_ptr tmp_ctx; + // install temporary dummy vm state interface to prevent charging for cell load operations during library lookup + VmStateInterface::Guard guard{global_version >= 4 ? tmp_ctx.get() : VmStateInterface::get()}; + for (const auto& lib_collection : libraries) { + auto lib = lookup_library_in(hash, lib_collection); + if (lib.not_null()) { + return lib; + } + } + missing_library = td::Bits256{hash}; + return {}; +} + +bool VmState::register_library_collection(Ref lib) { + if (lib.is_null()) { + return true; + } + libraries.push_back(std::move(lib)); + return true; +} + +void VmState::register_cell_load(const CellHash& cell_hash) { + if (cell_load_gas_price == cell_reload_gas_price) { + consume_gas(cell_load_gas_price); + } else { + auto ok = loaded_cells.insert(cell_hash); // check whether this is the first time this cell is loaded + consume_gas(ok.second ? cell_load_gas_price : cell_reload_gas_price); + } +} + +void VmState::register_cell_create() { + consume_gas(cell_create_gas_price); +} + +td::BitArray<256> VmState::get_state_hash() const { + // TODO: implement properly, by serializing the stack etc, and computing the Merkle hash + td::BitArray<256> res; + res.clear(); + return res; +} + +td::BitArray<256> VmState::get_final_state_hash(int exit_code) const { + // TODO: implement properly, by serializing the stack etc, and computing the Merkle hash + td::BitArray<256> res; + res.clear(); + return res; +} + +Ref lookup_library_in(td::ConstBitPtr key, vm::Dictionary& dict) { + try { + auto val = dict.lookup(key, 256); + if (val.is_null() || !val->have_refs()) { + return {}; + } + auto root = val->prefetch_ref(); + if (root.not_null() && !root->get_hash().bits().compare(key, 256)) { + return root; + } + return {}; + } catch (vm::VmError) { + return {}; + } +} + +Ref lookup_library_in(td::ConstBitPtr key, Ref lib_root) { + if (lib_root.is_null()) { + return lib_root; + } + vm::Dictionary dict{std::move(lib_root), 256}; + return lookup_library_in(key, dict); +} + +void VmState::run_child_vm(VmState&& new_state, bool return_data, bool return_actions, bool return_gas, + bool isolate_gas, int ret_vals) { + new_state.log = std::move(log); + new_state.libraries = std::move(libraries); + new_state.stack_trace = stack_trace; + new_state.max_data_depth = max_data_depth; + if (!isolate_gas) { + new_state.loaded_cells = std::move(loaded_cells); + } else { + consume_gas(std::min(chksgn_counter, chksgn_free_count) * chksgn_gas_price); + chksgn_counter = 0; + } + new_state.chksgn_counter = chksgn_counter; + + auto new_parent = std::make_unique(); + new_parent->return_data = return_data; + new_parent->return_actions = return_actions; + new_parent->return_gas = return_gas; + new_parent->isolate_gas = isolate_gas; + new_parent->ret_vals = ret_vals; + new_parent->state = std::move(*this); + new_state.parent = std::move(new_parent); + *this = std::move(new_state); +} + +void VmState::restore_parent_vm(int res) { + auto parent = std::move(this->parent); + CHECK(parent); + VmState child_state = std::move(*this); + *this = std::move(parent->state); + log = std::move(child_state.log); + libraries = std::move(child_state.libraries); + steps += child_state.steps; + if (!parent->isolate_gas) { + loaded_cells = std::move(child_state.loaded_cells); + } + chksgn_counter = child_state.chksgn_counter; + VM_LOG(this) << "Child VM finished. res: " << res << ", steps: " << child_state.steps + << ", gas: " << child_state.gas_consumed(); + + consume_gas(std::min(child_state.gas_consumed(), child_state.gas.gas_limit + 1)); + Stack& cur_stack = get_stack(); + int ret_cnt; + if (res == 0 || res == 1) { + if (parent->ret_vals >= 0) { + if (child_state.stack->depth() >= parent->ret_vals) { + ret_cnt = parent->ret_vals; + } else { + ret_cnt = 0; + res = ~(int)Excno::stk_und; + cur_stack.push(td::zero_refint()); + } + } else { + ret_cnt = child_state.stack->depth(); + } + } else { + ret_cnt = std::min(child_state.stack->depth(), 1); + } + consume_stack_gas(ret_cnt); + for (int i = ret_cnt - 1; i >= 0; --i) { + cur_stack.push(std::move(child_state.stack->at(i))); + } + cur_stack.push_smallint(res); + if (parent->return_data) { + cur_stack.push_cell(child_state.get_committed_state().c4); + } + if (parent->return_actions) { + cur_stack.push_cell(child_state.get_committed_state().c5); + } + if (parent->return_gas) { + cur_stack.push_smallint(child_state.gas.gas_consumed()); + } +} + +td::Status init_vm(bool enable_debug) { + if (!init_op_cp0(enable_debug)) { + return td::Status::Error("Failed to init TVM: failed to init cp0"); + } + auto code = sodium_init(); + if (code < 0) { + return td::Status::Error(PSTRING() << "Failed to init TVM: sodium_init, code=" << code); + } + return td::Status::OK(); +} + +} // namespace vm diff --git a/crypto/vm/vm.h b/crypto/vm/vm.h new file mode 100644 index 00000000..e5cca026 --- /dev/null +++ b/crypto/vm/vm.h @@ -0,0 +1,435 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ +#pragma once + +#include "common/refcnt.hpp" +#include "vm/cellslice.h" +#include "vm/stack.hpp" +#include "vm/vmstate.h" +#include "vm/log.h" +#include "vm/continuation.h" +#include "td/utils/HashSet.h" +#include "td/utils/optional.h" + +namespace vm { + +using td::Ref; +struct GasLimits { + static constexpr long long infty = (1ULL << 63) - 1; + long long gas_max, gas_limit, gas_credit, gas_remaining, gas_base; + GasLimits() : gas_max(infty), gas_limit(infty), gas_credit(0), gas_remaining(infty), gas_base(infty) { + } + GasLimits(long long _limit, long long _max = infty, long long _credit = 0) + : gas_max(_max) + , gas_limit(_limit) + , gas_credit(_credit) + , gas_remaining(_limit + _credit) + , gas_base(gas_remaining) { + } + long long gas_consumed() const { + return gas_base - gas_remaining; + } + void set_limits(long long _max, long long _limit, long long _credit = 0); + void change_base(long long _base) { + gas_remaining += _base - gas_base; + gas_base = _base; + } + void change_limit(long long _limit); + void consume(long long amount) { + // LOG(DEBUG) << "consume " << amount << " gas (" << gas_remaining << " remaining)"; + gas_remaining -= amount; + } + bool try_consume(long long amount) { + // LOG(DEBUG) << "try consume " << amount << " gas (" << gas_remaining << " remaining)"; + return (gas_remaining -= amount) >= 0; + } + void gas_exception() const; + void gas_exception(bool cond) const { + if (!cond) { + gas_exception(); + } + } + void consume_chk(long long amount) { + gas_exception(try_consume(amount)); + } + void check() const { + gas_exception(gas_remaining >= 0); + } + bool final_ok() const { + return gas_remaining >= gas_credit; + } +}; + +struct CommittedState { + Ref c4, c5; + bool committed{false}; +}; + +struct ParentVmState; + +class VmState final : public VmStateInterface { + Ref code; + Ref stack; + ControlRegs cr; + CommittedState cstate; + int cp; + long long steps{0}; + const DispatchTable* dispatch; + Ref quit0, quit1; + VmLog log; + GasLimits gas; + std::vector> libraries; + td::HashSet loaded_cells; + int stack_trace{0}, debug_off{0}; + bool chksig_always_succeed{false}; + bool stop_on_accept_message{false}; + td::optional missing_library; + td::uint16 max_data_depth = 512; // Default value + int global_version{0}; + size_t chksgn_counter = 0; + std::unique_ptr parent = nullptr; + + public: + enum { + cell_load_gas_price = 100, + cell_reload_gas_price = 25, + cell_create_gas_price = 500, + exception_gas_price = 50, + tuple_entry_gas_price = 1, + implicit_jmpref_gas_price = 10, + implicit_ret_gas_price = 5, + free_stack_depth = 32, + stack_entry_gas_price = 1, + runvm_gas_price = 40, + hash_ext_entry_gas_price = 1, + + rist255_mul_gas_price = 2000, + rist255_mulbase_gas_price = 750, + rist255_add_gas_price = 600, + rist255_fromhash_gas_price = 600, + rist255_validate_gas_price = 200, + + ecrecover_gas_price = 1500, + chksgn_free_count = 10, + chksgn_gas_price = 4000, + p256_chksgn_gas_price = 3500, + + bls_verify_gas_price = 61000, + bls_aggregate_base_gas_price = -2650, + bls_aggregate_element_gas_price = 4350, + bls_fast_aggregate_verify_base_gas_price = 58000, + bls_fast_aggregate_verify_element_gas_price = 3000, + bls_aggregate_verify_base_gas_price = 38500, + bls_aggregate_verify_element_gas_price = 22500, + + bls_g1_add_sub_gas_price = 3900, + bls_g1_neg_gas_price = 750, + bls_g1_mul_gas_price = 5200, + bls_map_to_g1_gas_price = 2350, + bls_g1_in_group_gas_price = 2950, + + bls_g2_add_sub_gas_price = 6100, + bls_g2_neg_gas_price = 1550, + bls_g2_mul_gas_price = 10550, + bls_map_to_g2_gas_price = 7950, + bls_g2_in_group_gas_price = 4250, + + // multiexp gas = base + n * coef1 + n/floor(max(log2(n), 4)) * coef2 + bls_g1_multiexp_base_gas_price = 11375, + bls_g1_multiexp_coef1_gas_price = 630, + bls_g1_multiexp_coef2_gas_price = 8820, + bls_g2_multiexp_base_gas_price = 30388, + bls_g2_multiexp_coef1_gas_price = 1280, + bls_g2_multiexp_coef2_gas_price = 22840, + + bls_pairing_base_gas_price = 20000, + bls_pairing_element_gas_price = 11800 + }; + VmState(); + VmState(Ref _code); + VmState(Ref _code, Ref _stack, int flags = 0, Ref _data = {}, VmLog log = {}, + std::vector> _libraries = {}, Ref init_c7 = {}); + VmState(Ref _code, Ref _stack, const GasLimits& _gas, int flags = 0, Ref _data = {}, + VmLog log = {}, std::vector> _libraries = {}, Ref init_c7 = {}); + template + VmState(Ref code_cell, Args&&... args) + : VmState(convert_code_cell(std::move(code_cell)), std::forward(args)...) { + } + VmState(const VmState&) = delete; + VmState(VmState&&) = default; + VmState& operator=(const VmState&) = delete; + VmState& operator=(VmState&&) = default; + bool set_gas_limits(long long _max, long long _limit, long long _credit = 0); + bool final_gas_ok() const { + return gas.final_ok(); + } + long long gas_consumed() const { + return gas.gas_consumed(); + } + bool committed() const { + return cstate.committed; + } + const CommittedState& get_committed_state() const { + return cstate; + } + void consume_gas_chk(long long amount) { + gas.consume_chk(amount); + } + void consume_gas(long long amount) { + if (global_version >= 4) { + gas.consume_chk(amount); + } else { + gas.consume(amount); + } + } + void consume_tuple_gas(unsigned tuple_len) { + consume_gas(tuple_len * tuple_entry_gas_price); + } + void consume_tuple_gas(const Ref& tup) { + if (tup.not_null()) { + consume_tuple_gas((unsigned)tup->size()); + } + } + void consume_stack_gas(unsigned stack_depth) { + consume_gas((std::max(stack_depth, (unsigned)free_stack_depth) - free_stack_depth) * stack_entry_gas_price); + } + void consume_stack_gas(const Ref& stk) { + if (stk.not_null()) { + consume_stack_gas((unsigned)stk->depth()); + } + } + GasLimits get_gas_limits() const { + return gas; + } + void change_gas_limit(long long new_limit); + template + void check_underflow(Args... args) { + stack->check_underflow(args...); + } + bool register_library_collection(Ref lib); + Ref load_library( + td::ConstBitPtr hash) override; // may throw a dictionary exception; returns nullptr if library is not found + void register_cell_load(const CellHash& cell_hash) override; + void register_cell_create() override; + bool init_cp(int new_cp); + bool set_cp(int new_cp); + void force_cp(int new_cp); + int get_cp() const { + return cp; + } + int incr_stack_trace(int v) { + return stack_trace += v; + } + long long get_steps_count() const { + return steps; + } + td::BitArray<256> get_state_hash() const; + td::BitArray<256> get_final_state_hash(int exit_code) const; + int step(); + int run(); + Stack& get_stack() { + return stack.write(); + } + const Stack& get_stack_const() const { + return *stack; + } + Ref get_stack_ref() const { + return stack; + } + Ref get_c0() const { + return cr.c[0]; + } + Ref get_c1() const { + return cr.c[1]; + } + Ref get_c2() const { + return cr.c[2]; + } + Ref get_c3() const { + return cr.c[3]; + } + Ref get_c4() const { + return cr.d[0]; + } + Ref get_c7() const { + return cr.c7; + } + Ref get_c(unsigned idx) const { + return cr.get_c(idx); + } + Ref get_d(unsigned idx) const { + return cr.get_d(idx); + } + StackEntry get(unsigned idx) const { + return cr.get(idx); + } + const VmLog& get_log() const { + return log; + } + void define_c0(Ref cont) { + cr.define_c0(std::move(cont)); + } + void set_c0(Ref cont) { + cr.set_c0(std::move(cont)); + } + void set_c1(Ref cont) { + cr.set_c1(std::move(cont)); + } + void set_c2(Ref cont) { + cr.set_c2(std::move(cont)); + } + bool set_c(unsigned idx, Ref val) { + return cr.set_c(idx, std::move(val)); + } + bool set_d(unsigned idx, Ref val) { + return cr.set_d(idx, std::move(val)); + } + void set_c4(Ref val) { + cr.set_c4(std::move(val)); + } + bool set_c7(Ref val) { + return cr.set_c7(std::move(val)); + } + bool set(unsigned idx, StackEntry val) { + return cr.set(idx, std::move(val)); + } + void set_stack(Ref new_stk) { + stack = std::move(new_stk); + } + Ref swap_stack(Ref new_stk) { + stack.swap(new_stk); + return new_stk; + } + void ensure_throw(bool cond) const { + if (!cond) { + fatal(); + } + } + void set_code(Ref _code, int _cp) { + code = std::move(_code); + force_cp(_cp); + } + Ref get_code() const { + return code; + } + void push_code() { + get_stack().push_cellslice(get_code()); + } + void adjust_cr(const ControlRegs& save) { + cr ^= save; + } + void adjust_cr(ControlRegs&& save) { + cr ^= save; + } + void preclear_cr(const ControlRegs& save) { + cr &= save; + } + int get_global_version() const override { + return global_version; + } + void set_global_version(int version) { + global_version = version; + } + int call(Ref cont); + int call(Ref cont, int pass_args, int ret_args = -1); + int jump(Ref cont); + int jump(Ref cont, int pass_args); + int ret(); + int ret(int ret_args); + int ret_alt(); + int ret_alt(int ret_args); + int repeat(Ref body, Ref after, long long count); + int again(Ref body); + int until(Ref body, Ref after); + int loop_while(Ref cond, Ref body, Ref after); + int throw_exception(int excno, StackEntry&& arg); + int throw_exception(int excno); + Ref extract_cc(int save_cr = 1, int stack_copy = -1, int cc_args = -1); + Ref c1_envelope(Ref cont, bool save = true); + Ref c1_envelope_if(bool cond, Ref cont, bool save = true) { + return cond ? c1_envelope(std::move(cont), save) : std::move(cont); + } + void c1_save_set(bool save = true); + void fatal(void) const { + throw VmFatal{}; + } + int jump_to(Ref cont) { + return cont->is_unique() ? cont.unique_write().jump_w(this) : cont->jump(this); + } + static Ref convert_code_cell(Ref code_cell); + bool try_commit(); + void force_commit(); + + void set_chksig_always_succeed(bool flag) { + chksig_always_succeed = flag; + } + bool get_chksig_always_succeed() const { + return chksig_always_succeed; + } + void set_stop_on_accept_message(bool flag) { + stop_on_accept_message = flag; + } + bool get_stop_on_accept_message() const { + return stop_on_accept_message; + } + Ref ref_to_cont(Ref cell) const { + return td::make_ref(load_cell_slice_ref(std::move(cell)), get_cp()); + } + td::optional get_missing_library() const { + return missing_library; + } + void set_max_data_depth(td::uint16 depth) { + max_data_depth = depth; + } + void run_child_vm(VmState&& new_state, bool return_data, bool return_actions, bool return_gas, bool isolate_gas, + int ret_vals); + void restore_parent_vm(int res); + + void register_chksgn_call() { + if (global_version >= 4) { + ++chksgn_counter; + if (chksgn_counter > chksgn_free_count) { + consume_gas(chksgn_gas_price); + } + } + } + + private: + void init_cregs(bool same_c3 = false, bool push_0 = true); + int run_inner(); +}; + +struct ParentVmState { + VmState state; + bool return_data, return_actions, return_gas, isolate_gas; + int ret_vals; +}; + +int run_vm_code(Ref _code, Ref& _stack, int flags = 0, Ref* data_ptr = nullptr, VmLog log = {}, + long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector> libraries = {}, + Ref init_c7 = {}, Ref* actions_ptr = nullptr, int global_version = 0); +int run_vm_code(Ref _code, Stack& _stack, int flags = 0, Ref* data_ptr = nullptr, VmLog log = {}, + long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector> libraries = {}, + Ref init_c7 = {}, Ref* actions_ptr = nullptr, int global_version = 0); + +Ref lookup_library_in(td::ConstBitPtr key, Ref lib_root); + +td::Status init_vm(bool enable_debug = false); + +} // namespace vm diff --git a/crypto/vm/vmstate.h b/crypto/vm/vmstate.h index 312b6626..0d6c3fdf 100644 --- a/crypto/vm/vmstate.h +++ b/crypto/vm/vmstate.h @@ -14,11 +14,12 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/refcnt.hpp" #include "vm/cells.h" +#include "common/global-version.h" #include "td/utils/Context.h" @@ -28,12 +29,19 @@ using td::Ref; class VmStateInterface : public td::Context { public: virtual ~VmStateInterface() = default; - virtual Ref load_library( + virtual Ref load_library( td::ConstBitPtr hash) { // may throw a dictionary exception; returns nullptr if library is not found return {}; } - virtual void register_cell_load(){}; + virtual void register_cell_load(const CellHash& cell_hash){}; virtual void register_cell_create(){}; + virtual void register_new_cell(Ref& cell){}; + virtual bool register_op(int op_units = 1) { + return true; + }; + virtual int get_global_version() const { + return ton::SUPPORTED_VERSION; + } }; } // namespace vm diff --git a/dht-server/CMakeLists.txt b/dht-server/CMakeLists.txt index af67a432..6daac033 100644 --- a/dht-server/CMakeLists.txt +++ b/dht-server/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) if (NOT OPENSSL_FOUND) find_package(OpenSSL REQUIRED) @@ -11,4 +11,6 @@ set (DHT_SERVER_SOURCE ) add_executable (dht-server ${DHT_SERVER_SOURCE}) -target_link_libraries(dht-server tdutils tdactor adnl tl_api dht memprof ${JEMALLOC_LIBRARIES}) +target_link_libraries(dht-server tdutils tdactor adnl tl_api dht memprof git ${JEMALLOC_LIBRARIES}) + +install(TARGETS dht-server RUNTIME DESTINATION bin) diff --git a/dht-server/dht-server.cpp b/dht-server/dht-server.cpp index 481b79fb..37a158eb 100644 --- a/dht-server/dht-server.cpp +++ b/dht-server/dht-server.cpp @@ -1,4 +1,4 @@ -/* +/* This file is part of TON Blockchain source code. TON Blockchain is free software; you can redistribute it and/or @@ -14,23 +14,23 @@ You should have received a copy of the GNU General Public License along with TON Blockchain. If not, see . - In addition, as a special exception, the copyright holders give permission - to link the code of portions of this program with the OpenSSL library. - You must obey the GNU General Public License in all respects for all - of the code used other than OpenSSL. If you modify file(s) with this - exception, you may extend this exception to your version of the file(s), - but you are not obligated to do so. If you do not wish to do so, delete this - exception statement from your version. If you delete this exception statement + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "dht-server.hpp" #include "td/utils/filesystem.h" #include "td/actor/MultiPromise.h" #include "td/utils/overloaded.h" -#include "td/utils/OptionsParser.h" +#include "td/utils/OptionParser.h" #include "td/utils/port/path.h" #include "td/utils/port/user.h" #include "td/utils/port/signals.h" @@ -48,6 +48,7 @@ #include #include #include +#include "git.h" Config::Config() { out_port = 3278; @@ -70,8 +71,12 @@ Config::Config(ton::ton_api::engine_validator_config &config) { [&](const ton::ton_api::engine_addr &obj) { in_ip.init_ipv4_port(td::IPAddress::ipv4_to_str(obj.ip_), static_cast(obj.port_)).ensure(); out_ip = in_ip; - categories = obj.categories_; - priority_categories = obj.priority_categories_; + for (auto &cat : obj.categories_) { + categories.push_back(td::narrow_cast(cat)); + } + for (auto &cat : obj.priority_categories_) { + priority_categories.push_back(td::narrow_cast(cat)); + } }, [&](const ton::ton_api::engine_addrProxy &obj) { in_ip.init_ipv4_port(td::IPAddress::ipv4_to_str(obj.in_ip_), static_cast(obj.in_port_)) @@ -82,15 +87,19 @@ Config::Config(ton::ton_api::engine_validator_config &config) { auto R = ton::adnl::AdnlProxy::create(*obj.proxy_type_.get()); R.ensure(); proxy = R.move_as_ok(); - categories = obj.categories_; - priority_categories = obj.priority_categories_; + for (auto &cat : obj.categories_) { + categories.push_back(td::narrow_cast(cat)); + } + for (auto &cat : obj.priority_categories_) { + priority_categories.push_back(td::narrow_cast(cat)); + } } })); config_add_network_addr(in_ip, out_ip, std::move(proxy), categories, priority_categories).ensure(); } for (auto &adnl : config.adnl_) { - config_add_adnl_addr(ton::PublicKeyHash{adnl->id_}, adnl->category_).ensure(); + config_add_adnl_addr(ton::PublicKeyHash{adnl->id_}, td::narrow_cast(adnl->category_)).ensure(); } for (auto &dht : config.dht_) { config_add_dht_node(ton::PublicKeyHash{dht->id_}).ensure(); @@ -161,7 +170,7 @@ ton::tl_object_ptr Config::tl() const { return ton::create_tl_object( out_port, std::move(addrs_vec), std::move(adnl_vec), std::move(dht_vec), std::move(val_vec), ton::PublicKeyHash::zero().tl(), std::move(full_node_slaves_vec), std::move(full_node_masters_vec), - std::move(liteserver_vec), std::move(control_vec), std::move(gc_vec)); + nullptr, std::move(liteserver_vec), std::move(control_vec), std::move(gc_vec)); } td::Result Config::config_add_network_addr(td::IPAddress in_ip, td::IPAddress out_ip, @@ -448,7 +457,7 @@ void DhtServer::load_empty_local_config(td::Promise promise) { ig.add_promise(std::move(ret_promise)); for (auto &addr : addrs_) { - config_.config_add_network_addr(addr, addr, nullptr, std::vector{0, 1, 2, 3}, std::vector{}) + config_.config_add_network_addr(addr, addr, nullptr, std::vector{0, 1, 2, 3}, std::vector{}) .ensure(); } @@ -501,7 +510,7 @@ void DhtServer::load_local_config(td::Promise promise) { ig.add_promise(std::move(ret_promise)); for (auto &addr : addrs_) { - config_.config_add_network_addr(addr, addr, nullptr, std::vector{0, 1, 2, 3}, std::vector{}) + config_.config_add_network_addr(addr, addr, nullptr, std::vector{0, 1, 2, 3}, std::vector{}) .ensure(); } @@ -563,6 +572,12 @@ void DhtServer::load_config(td::Promise promise) { config_file_ = db_root_ + "/config.json"; } auto conf_data_R = td::read_file(config_file_); + if (conf_data_R.is_error()) { + conf_data_R = td::read_file(temp_config_file()); + if (conf_data_R.is_ok()) { + td::rename(temp_config_file(), config_file_).ensure(); + } + } if (conf_data_R.is_error()) { auto P = td::PromiseCreator::lambda( [name = local_config_, new_name = config_file_, promise = std::move(promise)](td::Result R) { @@ -611,12 +626,15 @@ void DhtServer::load_config(td::Promise promise) { void DhtServer::write_config(td::Promise promise) { auto s = td::json_encode(td::ToJson(*config_.tl().get()), true); - auto S = td::write_file(config_file_, s); - if (S.is_ok()) { - promise.set_value(td::Unit()); - } else { + auto S = td::write_file(temp_config_file(), s); + if (S.is_error()) { + td::unlink(temp_config_file()).ignore(); promise.set_error(std::move(S)); + return; } + td::unlink(config_file_).ignore(); + TRY_STATUS_PROMISE(promise, td::rename(temp_config_file(), config_file_)); + promise.set_value(td::Unit()); } td::Promise DhtServer::get_key_promise(td::MultiPromise::InitGuard &ig) { @@ -656,12 +674,20 @@ void DhtServer::start_adnl() { } void DhtServer::add_addr(const Config::Addr &addr, const Config::AddrCats &cats) { + ton::adnl::AdnlCategoryMask cat_mask; + for (auto cat : cats.cats) { + cat_mask[cat] = true; + } + for (auto cat : cats.priority_cats) { + cat_mask[cat] = true; + } if (!cats.proxy) { td::actor::send_closure(adnl_network_manager_, &ton::adnl::AdnlNetworkManager::add_self_addr, addr.addr, - cats.cats.size() ? 0 : 1); + std::move(cat_mask), cats.cats.size() ? 0 : 1); } else { - td::actor::send_closure(adnl_network_manager_, &ton::adnl::AdnlNetworkManager::add_proxy_addr, addr.addr, - cats.proxy, cats.cats.size() ? 0 : 1); + td::actor::send_closure(adnl_network_manager_, &ton::adnl::AdnlNetworkManager::add_proxy_addr, cats.in_addr, + static_cast(addr.addr.get_port()), cats.proxy, std::move(cat_mask), + cats.cats.size() ? 0 : 1); } td::uint32 ts = static_cast(td::Clocks::system()); @@ -687,7 +713,7 @@ void DhtServer::add_addr(const Config::Addr &addr, const Config::AddrCats &cats) void DhtServer::add_adnl(ton::PublicKeyHash id, AdnlCategory cat) { CHECK(addr_lists_[cat].size() > 0); CHECK(keys_.count(id) > 0); - td::actor::send_closure(adnl_, &ton::adnl::Adnl::add_id, ton::adnl::AdnlNodeIdFull{keys_[id]}, addr_lists_[cat]); + td::actor::send_closure(adnl_, &ton::adnl::Adnl::add_id, ton::adnl::AdnlNodeIdFull{keys_[id]}, addr_lists_[cat], cat); } void DhtServer::started_adnl() { @@ -738,7 +764,7 @@ void DhtServer::start_control_interface() { for (auto &s : config_.controls) { td::actor::send_closure(adnl_, &ton::adnl::Adnl::add_id, ton::adnl::AdnlNodeIdFull{keys_[s.second.key]}, - ton::adnl::AdnlAddressList{}); + ton::adnl::AdnlAddressList{}, static_cast(255)); td::actor::send_closure(adnl_, &ton::adnl::Adnl::subscribe, ton::adnl::AdnlNodeIdShort{s.second.key}, std::string(""), std::make_unique(actor_id(this))); @@ -785,7 +811,7 @@ void DhtServer::add_adnl_node(ton::PublicKey key, AdnlCategory cat, td::Promise< } if (!adnl_.empty()) { - td::actor::send_closure(adnl_, &ton::adnl::Adnl::add_id, ton::adnl::AdnlNodeIdFull{key}, addr_lists_[cat]); + td::actor::send_closure(adnl_, &ton::adnl::Adnl::add_id, ton::adnl::AdnlNodeIdFull{key}, addr_lists_[cat], cat); } write_config(std::move(promise)); @@ -970,23 +996,25 @@ void DhtServer::run_control_query(ton::ton_api::engine_validator_addAdnlId &quer return; } - auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), cat = query.category_, - promise = std::move(promise)](td::Result R) mutable { - if (R.is_error()) { - promise.set_value(create_control_query_error(R.move_as_error_prefix("failed to get public key: "))); - return; - } - auto pub = R.move_as_ok(); - auto P = td::PromiseCreator::lambda([promise = std::move(promise)](td::Result R) mutable { - if (R.is_error()) { - promise.set_value(create_control_query_error(R.move_as_error_prefix("failed to add adnl node: "))); - } else { - promise.set_value( - ton::serialize_tl_object(ton::create_tl_object(), true)); - } - }); - td::actor::send_closure(SelfId, &DhtServer::add_adnl_node, pub, cat, std::move(P)); - }); + TRY_RESULT_PROMISE(promise, cat, td::narrow_cast_safe(query.category_)); + + auto P = td::PromiseCreator::lambda( + [SelfId = actor_id(this), cat, promise = std::move(promise)](td::Result R) mutable { + if (R.is_error()) { + promise.set_value(create_control_query_error(R.move_as_error_prefix("failed to get public key: "))); + return; + } + auto pub = R.move_as_ok(); + auto P = td::PromiseCreator::lambda([promise = std::move(promise)](td::Result R) mutable { + if (R.is_error()) { + promise.set_value(create_control_query_error(R.move_as_error_prefix("failed to add adnl node: "))); + } else { + promise.set_value( + ton::serialize_tl_object(ton::create_tl_object(), true)); + } + }); + td::actor::send_closure(SelfId, &DhtServer::add_adnl_node, pub, cat, std::move(P)); + }); td::actor::send_closure(keyring_, &ton::keyring::Keyring::get_public_key, ton::PublicKeyHash{query.key_hash_}, std::move(P)); @@ -1158,12 +1186,15 @@ int main(int argc, char *argv[]) { std::vector> acts; - td::OptionsParser p; + td::OptionParser p; p.set_description("dht server for TON DHT network"); p.add_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) { int v = VERBOSITY_NAME(FATAL) + (td::to_integer(arg)); SET_VERBOSITY_LEVEL(v); - return td::Status::OK(); + }); + p.add_option('V', "version", "shows dht-server build information", [&]() { + std::cout << "dht-server build information: [ Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate() << "]\n"; + std::exit(0); }); p.add_option('h', "help", "prints_help", [&]() { char b[10240]; @@ -1171,17 +1202,14 @@ int main(int argc, char *argv[]) { sb << p; std::cout << sb.as_cslice().c_str(); std::exit(2); - return td::Status::OK(); }); p.add_option('C', "global-config", "file to read global config", [&](td::Slice fname) { acts.push_back([&x, fname = fname.str()]() { td::actor::send_closure(x, &DhtServer::set_global_config, fname); }); - return td::Status::OK(); }); p.add_option('c', "local-config", "file to read local config", [&](td::Slice fname) { acts.push_back([&x, fname = fname.str()]() { td::actor::send_closure(x, &DhtServer::set_local_config, fname); }); - return td::Status::OK(); }); - p.add_option('I', "ip", "ip:port of instance", [&](td::Slice arg) { + p.add_checked_option('I', "ip", "ip:port of instance", [&](td::Slice arg) { td::IPAddress addr; TRY_STATUS(addr.init_host_port(arg.str())); acts.push_back([&x, addr]() { td::actor::send_closure(x, &DhtServer::add_ip, addr); }); @@ -1189,7 +1217,6 @@ int main(int argc, char *argv[]) { }); p.add_option('D', "db", "root for dbs", [&](td::Slice fname) { acts.push_back([&x, fname = fname.str()]() { td::actor::send_closure(x, &DhtServer::set_db_root, fname); }); - return td::Status::OK(); }); p.add_option('d', "daemonize", "set SIGHUP", [&]() { #if TD_DARWIN || TD_LINUX @@ -1197,28 +1224,27 @@ int main(int argc, char *argv[]) { setsid(); #endif td::set_signal_handler(td::SignalType::HangUp, force_rotate_logs).ensure(); - return td::Status::OK(); }); p.add_option('l', "logname", "log to file", [&](td::Slice fname) { logger_ = td::TsFileLog::create(fname.str()).move_as_ok(); td::log_interface = logger_.get(); - return td::Status::OK(); }); td::uint32 threads = 7; - p.add_option('t', "threads", PSTRING() << "number of threads (default=" << threads << ")", [&](td::Slice fname) { - td::int32 v; - try { - v = std::stoi(fname.str()); - } catch (...) { - return td::Status::Error(ton::ErrorCode::error, "bad value for --threads: not a number"); - } - if (v < 1 || v > 256) { - return td::Status::Error(ton::ErrorCode::error, "bad value for --threads: should be in range [1..256]"); - } - threads = v; - return td::Status::OK(); - }); - p.add_option('u', "user", "change user", [&](td::Slice user) { return td::change_user(user); }); + p.add_checked_option( + 't', "threads", PSTRING() << "number of threads (default=" << threads << ")", [&](td::Slice fname) { + td::int32 v; + try { + v = std::stoi(fname.str()); + } catch (...) { + return td::Status::Error(ton::ErrorCode::error, "bad value for --threads: not a number"); + } + if (v < 1 || v > 256) { + return td::Status::Error(ton::ErrorCode::error, "bad value for --threads: should be in range [1..256]"); + } + threads = v; + return td::Status::OK(); + }); + p.add_checked_option('u', "user", "change user", [&](td::Slice user) { return td::change_user(user.str()); }); p.run(argc, argv).ensure(); td::set_runtime_signal_handler(1, need_stats).ensure(); diff --git a/dht-server/dht-server.hpp b/dht-server/dht-server.hpp index 3d1c9ff6..5b81875b 100644 --- a/dht-server/dht-server.hpp +++ b/dht-server/dht-server.hpp @@ -23,7 +23,7 @@ exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl/adnl.h" #include "dht/dht.h" @@ -40,7 +40,7 @@ enum DhtServerPermissions : td::uint32 { vep_default = 1, vep_modify = 2, vep_unsafe = 4 }; -using AdnlCategory = td::int32; +using AdnlCategory = td::int8; struct Config { struct Addr { @@ -109,6 +109,9 @@ class DhtServer : public td::actor::Actor { std::string local_config_ = ""; std::string global_config_ = "ton-global.config"; std::string config_file_; + std::string temp_config_file() const { + return config_file_ + ".tmp"; + } std::string db_root_ = "/var/ton-work/db/"; @@ -177,7 +180,7 @@ class DhtServer : public td::actor::Actor { void alarm() override; void run(); - void add_adnl_node(ton::PublicKey pub, td::int32 cat, td::Promise promise); + void add_adnl_node(ton::PublicKey pub, AdnlCategory cat, td::Promise promise); void add_dht_node(ton::PublicKeyHash pub, td::Promise promise); void add_control_interface(ton::PublicKeyHash id, td::int32 port, td::Promise promise); void add_control_process(ton::PublicKeyHash id, td::int32 port, ton::PublicKeyHash pub, td::int32 permissions, @@ -214,4 +217,3 @@ class DhtServer : public td::actor::Actor { void process_control_query(ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, td::BufferSlice data, td::Promise promise); }; - diff --git a/dht/CMakeLists.txt b/dht/CMakeLists.txt index 36cfdd82..95ee7069 100644 --- a/dht/CMakeLists.txt +++ b/dht/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) if (NOT OPENSSL_FOUND) find_package(OpenSSL REQUIRED) @@ -30,3 +30,8 @@ target_include_directories(dht PUBLIC ) target_link_libraries(dht PRIVATE tdutils tdactor adnl tl_api) +add_executable(dht-ping-servers utils/dht-ping-servers.cpp) +target_link_libraries(dht-ping-servers PRIVATE tdutils tdactor adnl dht terminal) + +add_executable(dht-resolve utils/dht-resolve.cpp) +target_link_libraries(dht-resolve PRIVATE tdutils tdactor adnl dht terminal) diff --git a/dht/dht-bucket.cpp b/dht/dht-bucket.cpp index 4e4d9918..f60f9961 100644 --- a/dht/dht-bucket.cpp +++ b/dht/dht-bucket.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "td/utils/tl_storers.h" #include "td/utils/crypto.h" @@ -66,38 +66,66 @@ td::uint32 DhtBucket::active_cnt() { } td::Status DhtBucket::add_full_node(DhtKeyId id, DhtNode newnode, td::actor::ActorId adnl, - adnl::AdnlNodeIdShort self_id) { + adnl::AdnlNodeIdShort self_id, td::int32 our_network_id, bool set_active) { for (auto &node : active_nodes_) { if (node && node->get_key() == id) { - return node->update_value(std::move(newnode), adnl, self_id); + if (set_active) { + return node->receive_ping(std::move(newnode), adnl, self_id); + } else { + return node->update_value(std::move(newnode), adnl, self_id); + } } } - for (auto &node : backup_nodes_) { + for (size_t i = 0; i < backup_nodes_.size(); ++i) { + auto &node = backup_nodes_[i]; if (node && node->get_key() == id) { - return node->update_value(std::move(newnode), adnl, self_id); + if (set_active) { + TRY_STATUS(node->receive_ping(std::move(newnode), adnl, self_id)); + if (node->is_ready()) { + promote_node(i); + } + return td::Status::OK(); + } else { + return node->update_value(std::move(newnode), adnl, self_id); + } } } - TRY_RESULT_PREFIX(N, DhtRemoteNode::create(std::move(newnode), max_missed_pings_), "failed to add new node: "); - - for (auto &node : backup_nodes_) { - if (node == nullptr) { - node = std::move(N); - return td::Status::OK(); + TRY_RESULT_PREFIX(N, DhtRemoteNode::create(std::move(newnode), max_missed_pings_, our_network_id), + "failed to add new node: "); + if (set_active) { + for (auto &node : active_nodes_) { + if (node == nullptr) { + node = std::move(N); + node->receive_ping(); + return td::Status::OK(); + } } } - for (auto &node : backup_nodes_) { - CHECK(node); - if (node->ready_from() == 0 && node->failed_from() + 60 < td::Time::now_cached()) { - node = std::move(N); - return td::Status::OK(); - } + size_t idx = select_backup_node_to_drop(); + if (idx < backup_nodes_.size()) { + backup_nodes_[idx] = std::move(N); } - return td::Status::OK(); } +size_t DhtBucket::select_backup_node_to_drop() const { + size_t result = backup_nodes_.size(); + for (size_t idx = 0; idx < backup_nodes_.size(); ++idx) { + const auto &node = backup_nodes_[idx]; + if (node == nullptr) { + return idx; + } + if (node->ready_from() == 0 && node->failed_from() + 60 < td::Time::now_cached()) { + if (result == backup_nodes_.size() || node->failed_from() < backup_nodes_[result]->failed_from()) { + result = idx; + } + } + } + return result; +} + void DhtBucket::receive_ping(DhtKeyId id, DhtNode result, td::actor::ActorId adnl, adnl::AdnlNodeIdShort self_id) { for (auto &node : active_nodes_) { @@ -119,17 +147,9 @@ void DhtBucket::receive_ping(DhtKeyId id, DhtNode result, td::actor::ActorIdready_from() == 0 && node->failed_from() + 60 < td::Time::now_cached()) { - node = std::move(active_nodes_[idx]); - return; - } + size_t new_idx = select_backup_node_to_drop(); + if (new_idx < backup_nodes_.size()) { + backup_nodes_[new_idx] = std::move(active_nodes_[idx]); } active_nodes_[idx] = nullptr; } @@ -145,13 +165,13 @@ void DhtBucket::promote_node(size_t idx) { } } -void DhtBucket::check(td::actor::ActorId adnl, td::actor::ActorId dht, +void DhtBucket::check(bool client_only, td::actor::ActorId adnl, td::actor::ActorId dht, adnl::AdnlNodeIdShort src) { size_t have_space = 0; for (size_t i = 0; i < active_nodes_.size(); i++) { auto &node = active_nodes_[i]; - if (node && td::Time::now_cached() - node->last_ping_at() > ping_timeout_) { - node->send_ping(adnl, dht, src); + if (node && td::Time::now_cached() - node->last_ping_at() > node->ping_interval()) { + node->send_ping(client_only, adnl, dht, src); if (node->ready_from() == 0) { demote_node(i); } @@ -162,8 +182,8 @@ void DhtBucket::check(td::actor::ActorId adnl, td::actor::ActorIdlast_ping_at() > ping_timeout_) { - node->send_ping(adnl, dht, src); + if (node && td::Time::now_cached() - node->last_ping_at() > node->ping_interval()) { + node->send_ping(client_only, adnl, dht, src); } if (node && have_space > 0 && node->is_ready()) { promote_node(i); @@ -200,6 +220,9 @@ DhtNodesList DhtBucket::export_nodes() const { list.push_back(node->get_node()); } } + if (list.size() > k_) { + list.list().resize(k_); + } return list; } diff --git a/dht/dht-bucket.hpp b/dht/dht-bucket.hpp index 86407b3c..e12fe6a4 100644 --- a/dht/dht-bucket.hpp +++ b/dht/dht-bucket.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -31,7 +31,6 @@ class DhtMember; class DhtBucket { private: - double ping_timeout_ = 60; td::uint32 max_missed_pings_ = 3; std::vector> active_nodes_; @@ -39,10 +38,11 @@ class DhtBucket { //std::map> pending_nodes_; td::uint32 k_; - bool check_one(td::actor::ActorId adnl, td::actor::ActorId node, adnl::AdnlNodeIdShort src, - const DhtMember::PrintId &print_id); + //bool check_one(td::actor::ActorId adnl, td::actor::ActorId node, adnl::AdnlNodeIdShort src, + // const DhtMember::PrintId &print_id); void demote_node(size_t idx); void promote_node(size_t idx); + size_t select_backup_node_to_drop() const; public: DhtBucket(td::uint32 k) : k_(k) { @@ -51,8 +51,9 @@ class DhtBucket { } td::uint32 active_cnt(); td::Status add_full_node(DhtKeyId id, DhtNode node, td::actor::ActorId adnl, - adnl::AdnlNodeIdShort self_id); - void check(td::actor::ActorId adnl, td::actor::ActorId node, adnl::AdnlNodeIdShort src); + adnl::AdnlNodeIdShort self_id, td::int32 our_network_id, bool set_active = false); + void check(bool client_only, td::actor::ActorId adnl, td::actor::ActorId node, + adnl::AdnlNodeIdShort src); void receive_ping(DhtKeyId id, DhtNode result, td::actor::ActorId adnl, adnl::AdnlNodeIdShort self_id); void get_nearest_nodes(DhtKeyId id, td::uint32 bit, DhtNodesList &vec, td::uint32 k); void dump(td::StringBuilder &sb) const; diff --git a/dht/dht-in.hpp b/dht/dht-in.hpp index 43962208..c2d20455 100644 --- a/dht/dht-in.hpp +++ b/dht/dht-in.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -46,6 +46,7 @@ class DhtMemberImpl : public DhtMember { DhtKeyId key_; td::uint32 k_; td::uint32 a_; + td::int32 network_id_{-1}; td::uint32 max_cache_time_ = 60; td::uint32 max_cache_size_ = 100; @@ -66,6 +67,15 @@ class DhtMemberImpl : public DhtMember { DhtKeyId last_republish_key_ = DhtKeyId::zero(); DhtKeyId last_check_key_ = DhtKeyId::zero(); + adnl::AdnlNodeIdShort last_check_reverse_conn_ = adnl::AdnlNodeIdShort::zero(); + + struct ReverseConnection { + adnl::AdnlNodeIdShort dht_node_; + DhtKeyId key_id_; + td::Timestamp ttl_; + }; + std::map reverse_connections_; + std::set our_reverse_connections_; class Callback : public adnl::Adnl::Callback { public: @@ -94,6 +104,8 @@ class DhtMemberImpl : public DhtMember { td::actor::ActorId keyring_; td::actor::ActorId adnl_; + bool client_only_{false}; + td::uint64 ping_queries_{0}; td::uint64 find_node_queries_{0}; td::uint64 find_value_queries_{0}; @@ -120,17 +132,30 @@ class DhtMemberImpl : public DhtMember { void process_query(adnl::AdnlNodeIdShort src, ton_api::dht_store &query, td::Promise promise); void process_query(adnl::AdnlNodeIdShort src, ton_api::dht_getSignedAddressList &query, td::Promise promise); + void process_query(adnl::AdnlNodeIdShort src, ton_api::dht_registerReverseConnection &query, + td::Promise promise); + void process_query(adnl::AdnlNodeIdShort src, ton_api::dht_requestReversePing &query, + td::Promise promise); public: DhtMemberImpl(adnl::AdnlNodeIdShort id, std::string db_root, td::actor::ActorId keyring, - td::actor::ActorId adnl, td::uint32 k, td::uint32 a = 3) - : id_(id), key_{id_}, k_(k), a_(a), db_root_(db_root), keyring_(keyring), adnl_(adnl) { + td::actor::ActorId adnl, td::int32 network_id, td::uint32 k, td::uint32 a = 3, + bool client_only = false) + : id_(id) + , key_{id_} + , k_(k) + , a_(a) + , network_id_(network_id) + , db_root_(db_root) + , keyring_(keyring) + , adnl_(adnl) + , client_only_(client_only) { for (size_t i = 0; i < 256; i++) { buckets_.emplace_back(k_); } } - void add_full_node(DhtKeyId id, DhtNode node) override; + void add_full_node(DhtKeyId id, DhtNode node, bool set_active) override; adnl::AdnlNodeIdShort get_id() const override { return id_; @@ -141,6 +166,12 @@ class DhtMemberImpl : public DhtMember { void set_value(DhtValue key_value, td::Promise result) override; td::uint32 distance(DhtKeyId key_id, td::uint32 max_value); + void register_reverse_connection(adnl::AdnlNodeIdFull client, td::Promise promise) override; + void request_reverse_ping(adnl::AdnlNode target, adnl::AdnlNodeIdShort client, + td::Promise promise) override; + void request_reverse_ping_cont(adnl::AdnlNode target, td::BufferSlice signature, adnl::AdnlNodeIdShort client, + td::Promise promise); + td::Status store_in(DhtValue value) override; void send_store(DhtValue value, td::Promise promise); diff --git a/dht/dht-node.cpp b/dht/dht-node.cpp index f181ca86..409e3f68 100644 --- a/dht/dht-node.cpp +++ b/dht/dht-node.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "dht-node.hpp" #include "keys/encryptor.h" @@ -23,31 +23,46 @@ namespace ton { namespace dht { -td::Status DhtNode::update(tl_object_ptr obj) { +td::Status DhtNode::update(tl_object_ptr obj, td::int32 our_network_id) { if (version_ && obj->version_ <= version_) { return td::Status::Error(ErrorCode::notready, "too old version"); } - auto signature = std::move(obj->signature_); - auto B = serialize_tl_object(obj, true); - + td::BufferSlice signature; + td::int32 network_id = -1; + if (obj->signature_.size() == 64) { + signature = std::move(obj->signature_); + } else if (obj->signature_.size() == 64 + 4) { + signature = td::BufferSlice{obj->signature_.as_slice().remove_prefix(4)}; + network_id = *(td::uint32 *)obj->signature_.as_slice().remove_suffix(64).data(); + } else { + return td::Status::Error(ErrorCode::notready, "invalid length of signature"); + } + if (network_id != our_network_id && network_id != -1 && our_network_id != -1) { + // Remove (network_id != -1 && our_network_id != -1) after network update + return td::Status::Error(ErrorCode::notready, PSTRING() << "wrong network id (expected " << our_network_id + << ", found " << network_id << ")"); + } TRY_RESULT(pub, adnl::AdnlNodeIdFull::create(obj->id_)); TRY_RESULT(addr_list, adnl::AdnlAddressList::create(std::move(obj->addr_list_))); - if (!addr_list.public_only()) { return td::Status::Error(ErrorCode::notready, "dht node must have only public addresses"); } if (!addr_list.size()) { return td::Status::Error(ErrorCode::notready, "dht node must have >0 addresses"); } + DhtNode new_node{std::move(pub), std::move(addr_list), obj->version_, network_id, std::move(signature)}; + TRY_STATUS(new_node.check_signature()); - TRY_RESULT(E, pub.pubkey().create_encryptor()); - TRY_STATUS(E->check_signature(B.as_slice(), signature.as_slice())); - - id_ = pub; - addr_list_ = addr_list; - version_ = obj->version_; - signature_ = td::SharedSlice(signature.as_slice()); + *this = std::move(new_node); + return td::Status::OK(); +} +td::Status DhtNode::check_signature() const { + TRY_RESULT(enc, id_.pubkey().create_encryptor()); + auto node2 = clone(); + node2.signature_ = {}; + TRY_STATUS_PREFIX(enc->check_signature(serialize_tl_object(node2.tl(), true).as_slice(), signature_.as_slice()), + "bad node signature: "); return td::Status::OK(); } diff --git a/dht/dht-node.hpp b/dht/dht-node.hpp index c34b0d01..d5860f91 100644 --- a/dht/dht-node.hpp +++ b/dht/dht-node.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -22,6 +22,8 @@ #include "adnl/adnl-address-list.hpp" #include "dht-types.h" +#include "auto/tl/ton_api.hpp" +#include "td/utils/overloaded.h" namespace ton { @@ -32,26 +34,26 @@ class DhtNode { adnl::AdnlNodeIdFull id_; adnl::AdnlAddressList addr_list_; td::int32 version_{0}; + td::int32 network_id_{-1}; td::SharedSlice signature_; public: - DhtNode() { + DhtNode() = default; + DhtNode(adnl::AdnlNodeIdFull id, adnl::AdnlAddressList addr_list, td::int32 version, td::int32 network_id, td::BufferSlice signature) + : id_(id), addr_list_(addr_list), version_(version), network_id_(network_id), signature_(signature.as_slice()) { } - DhtNode(adnl::AdnlNodeIdFull id, adnl::AdnlAddressList addr_list, td::int32 version, td::BufferSlice signature) - : id_(id), addr_list_(addr_list), version_(version), signature_(signature.as_slice()) { + DhtNode(adnl::AdnlNodeIdFull id, adnl::AdnlAddressList addr_list, td::int32 version, td::int32 network_id, td::SharedSlice signature) + : id_(id), addr_list_(addr_list), version_(version), network_id_(network_id), signature_(std::move(signature)) { } - DhtNode(adnl::AdnlNodeIdFull id, adnl::AdnlAddressList addr_list, td::int32 version, td::SharedSlice signature) - : id_(id), addr_list_(addr_list), version_(version), signature_(std::move(signature)) { - } - static td::Result create(tl_object_ptr obj) { + static td::Result create(tl_object_ptr obj, td::int32 our_network_id) { if (obj->version_ == 0) { return td::Status::Error(ErrorCode::protoviolation, "zero version"); } DhtNode n; - TRY_STATUS(n.update(std::move(obj))); + TRY_STATUS(n.update(std::move(obj), our_network_id)); return std::move(n); } - td::Status update(tl_object_ptr obj); + td::Status update(tl_object_ptr obj, td::int32 our_network_id); DhtKeyId get_key() const { CHECK(!id_.empty()); return DhtKeyId{id_.compute_short_id()}; @@ -68,20 +70,30 @@ class DhtNode { } tl_object_ptr tl() const { - return create_tl_object(id_.tl(), addr_list_.tl(), version_, signature_.clone_as_buffer_slice()); + td::BufferSlice signature_ext; + if (network_id_ == -1) { + signature_ext = signature_.clone_as_buffer_slice(); + } else { + signature_ext = td::BufferSlice{4 + signature_.size()}; + td::MutableSlice s = signature_ext.as_slice(); + s.copy_from(td::Slice(reinterpret_cast(&network_id_), 4)); + s.remove_prefix(4); + s.copy_from(signature_.as_slice()); + } + return create_tl_object(id_.tl(), addr_list_.tl(), version_, std::move(signature_ext)); } DhtNode clone() const { - return DhtNode{id_, addr_list_, version_, signature_.clone()}; + return DhtNode{id_, addr_list_, version_, network_id_, signature_.clone()}; } + td::Status check_signature() const; }; class DhtNodesList { public: - DhtNodesList() { - } - DhtNodesList(tl_object_ptr R) { + DhtNodesList() = default; + DhtNodesList(tl_object_ptr R, td::int32 our_network_id) { for (auto &n : R->nodes_) { - auto N = DhtNode::create(std::move(n)); + auto N = DhtNode::create(std::move(n), our_network_id); if (N.is_ok()) { list_.emplace_back(N.move_as_ok()); } else { diff --git a/dht/dht-query.cpp b/dht/dht-query.cpp index 440e6cb1..b84ef8c3 100644 --- a/dht/dht-query.cpp +++ b/dht/dht-query.cpp @@ -14,20 +14,17 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "dht.hpp" #include "td/utils/tl_storers.h" #include "td/utils/crypto.h" -#include "td/utils/tl_parsers.h" #include "td/utils/Random.h" #include "td/utils/overloaded.h" #include "td/utils/format.h" -#include "keys/encryptor.h" - #include "auto/tl/ton_api.hpp" #include "dht-query.hpp" @@ -37,24 +34,33 @@ namespace ton { namespace dht { void DhtQuery::send_queries() { + while (pending_queries_.size() > k_ * 2) { + pending_queries_.erase(--pending_queries_.end()); + } VLOG(DHT_EXTRA_DEBUG) << this << ": sending new queries. active=" << active_queries_ << " max_active=" << a_; - while (pending_ids_.size() > 0 && active_queries_ < a_) { + while (pending_queries_.size() > 0 && active_queries_ < a_) { + auto id_xor = *pending_queries_.begin(); + if (result_list_.size() == k_ && *result_list_.rbegin() < id_xor) { + break; + } active_queries_++; - auto id_xor = *pending_ids_.begin(); auto id = id_xor ^ key_; VLOG(DHT_EXTRA_DEBUG) << this << ": sending " << get_name() << " query to " << id; - pending_ids_.erase(id_xor); + pending_queries_.erase(id_xor); - auto it = list_.find(id_xor); - CHECK(it != list_.end()); - td::actor::send_closure(adnl_, &adnl::Adnl::add_peer, get_src(), it->second.adnl_id(), it->second.addr_list()); + auto it = nodes_.find(id_xor); + CHECK(it != nodes_.end()); + td::actor::send_closure(adnl_, &adnl::Adnl::add_peer, get_src(), it->second.node.adnl_id(), + it->second.node.addr_list()); send_one_query(id.to_adnl()); } if (active_queries_ == 0) { - CHECK(pending_ids_.size() == 0); + pending_queries_.clear(); DhtNodesList list; - for (auto &node : list_) { - list.push_back(std::move(node.second)); + for (auto id_xor : result_list_) { + auto it = nodes_.find(id_xor); + CHECK(it != nodes_.end()); + list.push_back(it->second.node.clone()); } CHECK(list.size() <= k_); VLOG(DHT_EXTRA_DEBUG) << this << ": finalizing " << get_name() << " query. List size=" << list.size(); @@ -68,35 +74,42 @@ void DhtQuery::add_nodes(DhtNodesList list) { for (auto &node : list.list()) { auto id = node.get_key(); auto id_xor = key_ ^ id; - if (list_.find(id_xor) != list_.end()) { + if (nodes_.find(id_xor) != nodes_.end()) { continue; } - td::actor::send_closure(node_, &DhtMember::add_full_node, id, node.clone()); + VLOG(DHT_EXTRA_DEBUG) << this << ": " << get_name() << " query: adding " << id << " key"; + td::actor::send_closure(node_, &DhtMember::add_full_node, id, node.clone(), false); + nodes_[id_xor].node = std::move(node); + pending_queries_.insert(id_xor); + } +} - DhtKeyId last_id_xor; - if (list_.size() > 0) { - last_id_xor = list_.rbegin()->first; +void DhtQuery::finish_query(adnl::AdnlNodeIdShort id, bool success) { + active_queries_--; + CHECK(active_queries_ <= k_); + auto id_xor = key_ ^ DhtKeyId(id); + if (success) { + result_list_.insert(id_xor); + if (result_list_.size() > k_) { + result_list_.erase(--result_list_.end()); } - - if (list_.size() < k_ || id_xor < last_id_xor) { - list_[id_xor] = std::move(node); - pending_ids_.insert(id_xor); - if (list_.size() > k_) { - CHECK(id_xor != last_id_xor); - VLOG(DHT_EXTRA_DEBUG) << this << ": " << get_name() << " query: replacing " << (last_id_xor ^ key_) - << " key with " << id; - pending_ids_.erase(last_id_xor); - list_.erase(last_id_xor); - } else { - VLOG(DHT_EXTRA_DEBUG) << this << ": " << get_name() << " query: adding " << id << " key"; - } + } else { + NodeInfo &info = nodes_[id_xor]; + if (++info.failed_attempts < MAX_ATTEMPTS) { + pending_queries_.insert(id_xor); } } + send_queries(); } void DhtQueryFindNodes::send_one_query(adnl::AdnlNodeIdShort id) { auto P = create_serialize_tl_object(get_key().tl(), get_k()); - auto B = create_serialize_tl_object_suffix(P.as_slice(), self_.tl()); + td::BufferSlice B; + if (client_only_) { + B = std::move(P); + } else { + B = create_serialize_tl_object_suffix(P.as_slice(), self_.tl()); + } auto Pr = td::PromiseCreator::lambda([SelfId = actor_id(this), dst = id](td::Result R) { td::actor::send_closure(SelfId, &DhtQueryFindNodes::on_result, std::move(R), dst); @@ -109,7 +122,7 @@ void DhtQueryFindNodes::send_one_query(adnl::AdnlNodeIdShort id) { void DhtQueryFindNodes::on_result(td::Result R, adnl::AdnlNodeIdShort dst) { if (R.is_error()) { VLOG(DHT_INFO) << this << ": failed find nodes query " << get_src() << "->" << dst << ": " << R.move_as_error(); - finish_query(); + finish_query(dst, false); return; } @@ -118,9 +131,9 @@ void DhtQueryFindNodes::on_result(td::Result R, adnl::AdnlNodeI VLOG(DHT_WARNING) << this << ": incorrect result on dht.findNodes query from " << dst << ": " << Res.move_as_error(); } else { - add_nodes(DhtNodesList{Res.move_as_ok()}); + add_nodes(DhtNodesList{Res.move_as_ok(), our_network_id()}); } - finish_query(); + finish_query(dst); } void DhtQueryFindNodes::finish(DhtNodesList list) { @@ -129,7 +142,12 @@ void DhtQueryFindNodes::finish(DhtNodesList list) { void DhtQueryFindValue::send_one_query(adnl::AdnlNodeIdShort id) { auto P = create_serialize_tl_object(get_key().tl(), get_k()); - auto B = create_serialize_tl_object_suffix(P.as_slice(), self_.tl()); + td::BufferSlice B; + if (client_only_) { + B = std::move(P); + } else { + B = create_serialize_tl_object_suffix(P.as_slice(), self_.tl()); + } auto Pr = td::PromiseCreator::lambda([SelfId = actor_id(this), dst = id](td::Result R) { td::actor::send_closure(SelfId, &DhtQueryFindValue::on_result, std::move(R), dst); @@ -139,63 +157,109 @@ void DhtQueryFindValue::send_one_query(adnl::AdnlNodeIdShort id) { td::Timestamp::in(2.0 + td::Random::fast(0, 20) * 0.1), std::move(B)); } +void DhtQueryFindValue::send_one_query_nodes(adnl::AdnlNodeIdShort id) { + auto P = create_serialize_tl_object(get_key().tl(), get_k()); + td::BufferSlice B; + if (client_only_) { + B = std::move(P); + } else { + B = create_serialize_tl_object_suffix(P.as_slice(), self_.tl()); + } + + auto Pr = td::PromiseCreator::lambda([SelfId = actor_id(this), dst = id](td::Result R) { + td::actor::send_closure(SelfId, &DhtQueryFindValue::on_result_nodes, std::move(R), dst); + }); + + td::actor::send_closure(adnl_, &adnl::Adnl::send_query, get_src(), id, "dht findValue", std::move(Pr), + td::Timestamp::in(2.0 + td::Random::fast(0, 20) * 0.1), std::move(B)); +} + void DhtQueryFindValue::on_result(td::Result R, adnl::AdnlNodeIdShort dst) { if (R.is_error()) { VLOG(DHT_INFO) << this << ": failed find value query " << get_src() << "->" << dst << ": " << R.move_as_error(); - finish_query(); + finish_query(dst, false); return; } auto Res = fetch_tl_object(R.move_as_ok(), true); if (Res.is_error()) { VLOG(DHT_WARNING) << this << ": dropping incorrect answer on dht.findValue query from " << dst << ": " << Res.move_as_error(); - finish_query(); + finish_query(dst, false); return; } bool need_stop = false; + bool send_get_nodes = false; auto A = Res.move_as_ok(); ton_api::downcast_call( - *A.get(), td::overloaded( - [&](ton_api::dht_valueFound &v) { - auto valueR = DhtValue::create(std::move(v.value_), true); - if (valueR.is_error()) { - VLOG(DHT_WARNING) << this << ": received incorrect dht answer on find value query from " << dst - << ": " << valueR.move_as_error(); - return; - } - auto value = valueR.move_as_ok(); - if (value.key_id() != key_) { - VLOG(DHT_WARNING) << this << ": received value for bad key on find value query from " << dst; - return; - } - promise_.set_value(std::move(value)); - need_stop = true; - }, - [&](ton_api::dht_valueNotFound &v) { add_nodes(DhtNodesList{std::move(v.nodes_)}); })); + *A, td::overloaded( + [&](ton_api::dht_valueFound &v) { + auto valueR = DhtValue::create(std::move(v.value_), true); + if (valueR.is_error()) { + VLOG(DHT_WARNING) << this << ": received incorrect dht answer on find value query from " << dst + << ": " << valueR.move_as_error(); + return; + } + auto value = valueR.move_as_ok(); + if (value.key_id() != key_) { + VLOG(DHT_WARNING) << this << ": received value for bad key on find value query from " << dst; + return; + } + if (!value.check_is_acceptable()) { + send_get_nodes = true; + return; + } + promise_.set_value(std::move(value)); + need_stop = true; + }, + [&](ton_api::dht_valueNotFound &v) { + add_nodes(DhtNodesList{std::move(v.nodes_), our_network_id()}); + })); if (need_stop) { stop(); + } else if (send_get_nodes) { + send_one_query_nodes(dst); } else { - finish_query(); + finish_query(dst); } } +void DhtQueryFindValue::on_result_nodes(td::Result R, adnl::AdnlNodeIdShort dst) { + if (R.is_error()) { + VLOG(DHT_INFO) << this << ": failed find nodes query " << get_src() << "->" << dst << ": " << R.move_as_error(); + finish_query(dst, false); + return; + } + auto Res = fetch_tl_object(R.move_as_ok(), true); + if (Res.is_error()) { + VLOG(DHT_WARNING) << this << ": dropping incorrect answer on dht.findNodes query from " << dst << ": " + << Res.move_as_error(); + finish_query(dst, false); + return; + } + auto r = Res.move_as_ok(); + add_nodes(DhtNodesList{create_tl_object(std::move(r->nodes_)), our_network_id()}); + finish_query(dst); +} + void DhtQueryFindValue::finish(DhtNodesList list) { promise_.set_error(td::Status::Error(ErrorCode::notready, "dht key not found")); } DhtQueryStore::DhtQueryStore(DhtValue key_value, DhtMember::PrintId print_id, adnl::AdnlNodeIdShort src, - DhtNodesList list, td::uint32 k, td::uint32 a, DhtNode self, - td::actor::ActorId node, td::actor::ActorId adnl, + DhtNodesList list, td::uint32 k, td::uint32 a, td::int32 our_network_id, DhtNode self, + bool client_only, td::actor::ActorId node, td::actor::ActorId adnl, td::Promise promise) : print_id_(print_id) , k_(k) , a_(a) + , our_network_id_(our_network_id) , promise_(std::move(promise)) , value_(std::move(key_value)) , list_(std::move(list)) - , self_(std::move(self)) { + , self_(std::move(self)) + , client_only_(client_only) { node_ = node; adnl_ = adnl; src_ = src; @@ -208,7 +272,8 @@ void DhtQueryStore::start_up() { auto key = value_.key_id(); auto A = td::actor::create_actor("FindNodesQuery", key, print_id_, src_, std::move(list_), k_, a_, - self_.clone(), node_, adnl_, std::move(P)); + our_network_id_, self_.clone(), client_only_, node_, adnl_, + std::move(P)); A.release(); } @@ -268,6 +333,133 @@ void DhtQueryStore::store_ready(td::Result R) { } } +DhtQueryRegisterReverseConnection::DhtQueryRegisterReverseConnection( + DhtKeyId key_id, adnl::AdnlNodeIdFull client, td::uint32 ttl, td::BufferSlice signature, + DhtMember::PrintId print_id, adnl::AdnlNodeIdShort src, DhtNodesList list, td::uint32 k, td::uint32 a, + td::int32 our_network_id, DhtNode self, bool client_only, td::actor::ActorId node, + td::actor::ActorId adnl, td::Promise promise) + : print_id_(print_id) + , k_(k) + , a_(a) + , our_network_id_(our_network_id) + , promise_(std::move(promise)) + , key_id_(key_id) + , list_(std::move(list)) + , self_(std::move(self)) + , client_only_(client_only) { + node_ = node; + adnl_ = adnl; + src_ = src; + query_ = create_serialize_tl_object(client.tl(), ttl, std::move(signature)); +} + +void DhtQueryRegisterReverseConnection::start_up() { + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result res) { + td::actor::send_closure(SelfId, &DhtQueryRegisterReverseConnection::send_queries, std::move(res)); + }); + + auto A = td::actor::create_actor("FindNodesQuery", key_id_, print_id_, src_, std::move(list_), k_, + a_, our_network_id_, self_.clone(), client_only_, node_, adnl_, + std::move(P)); + A.release(); +} + +void DhtQueryRegisterReverseConnection::send_queries(td::Result R) { + if (R.is_error()) { + auto S = R.move_as_error(); + VLOG(DHT_NOTICE) << this << ": failed to get nearest nodes to " << key_id_ << ": " << S; + promise_.set_error(std::move(S)); + stop(); + return; + } + auto list = R.move_as_ok(); + + remaining_ = static_cast(list.size()); + if (remaining_ == 0) { + VLOG(DHT_NOTICE) << this << ": failed to get nearest nodes to " << key_id_ << ": no nodes"; + promise_.set_error(td::Status::Error("no dht nodes")); + stop(); + return; + } + + for (auto &node : list.list()) { + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { + td::actor::send_closure(SelfId, &DhtQueryRegisterReverseConnection::ready, std::move(R)); + }); + td::actor::send_closure(adnl_, &adnl::Adnl::send_query, src_, node.adnl_id().compute_short_id(), "dht regrevcon", + std::move(P), td::Timestamp::in(2.0 + td::Random::fast(0, 20) * 0.1), query_.clone()); + } +} + +void DhtQueryRegisterReverseConnection::ready(td::Result R) { + if (R.is_error()) { + fail_++; + VLOG(DHT_INFO) << this << ": failed register reverse connection query: " << R.move_as_error(); + } else { + auto R2 = fetch_tl_object(R.move_as_ok(), true); + if (R2.is_error()) { + fail_++; + VLOG(DHT_WARNING) << this << ": can not parse answer (expected dht.stored): " << R2.move_as_error(); + } else { + success_++; + } + } + CHECK(remaining_ > 0); + remaining_--; + if (remaining_ == 0) { + if (success_ > 0) { + promise_.set_value(td::Unit()); + } else { + promise_.set_result(td::Status::Error("failed to make actual query")); + } + stop(); + } +} + +void DhtQueryRequestReversePing::send_one_query(adnl::AdnlNodeIdShort id) { + td::BufferSlice B; + if (client_only_) { + B = query_.clone(); + } else { + B = create_serialize_tl_object_suffix(query_.as_slice(), self_.tl()); + } + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), dst = id](td::Result R) { + td::actor::send_closure(SelfId, &DhtQueryRequestReversePing::on_result, std::move(R), dst); + }); + td::actor::send_closure(adnl_, &adnl::Adnl::send_query, get_src(), id, "dht requestReversePing", std::move(P), + td::Timestamp::in(2.0 + td::Random::fast(0, 20) * 0.1), std::move(B)); +} + +void DhtQueryRequestReversePing::on_result(td::Result R, adnl::AdnlNodeIdShort dst) { + if (R.is_error()) { + VLOG(DHT_INFO) << this << ": failed reverse ping query " << get_src() << "->" << dst << ": " << R.move_as_error(); + finish_query(dst, false); + return; + } + auto Res = fetch_tl_object(R.move_as_ok(), true); + if (Res.is_error()) { + VLOG(DHT_WARNING) << this << ": dropping incorrect answer on dht.requestReversePing query from " << dst << ": " + << Res.move_as_error(); + finish_query(dst, false); + return; + } + + auto A = Res.move_as_ok(); + ton_api::downcast_call(*A, td::overloaded( + [&](ton_api::dht_reversePingOk &v) { + promise_.set_value(td::Unit()); + stop(); + }, + [&](ton_api::dht_clientNotFound &v) { + add_nodes(DhtNodesList{std::move(v.nodes_), our_network_id()}); + finish_query(dst); + })); +} + +void DhtQueryRequestReversePing::finish(DhtNodesList list) { + promise_.set_error(td::Status::Error(ErrorCode::notready, "dht key not found")); +} + } // namespace dht } // namespace ton diff --git a/dht/dht-query.hpp b/dht/dht-query.hpp index a5624373..e4740361 100644 --- a/dht/dht-query.hpp +++ b/dht/dht-query.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -41,23 +41,29 @@ class DhtQuery : public td::actor::Actor { protected: DhtKeyId key_; DhtNode self_; + bool client_only_; public: - DhtQuery(DhtKeyId key, DhtMember::PrintId print_id, adnl::AdnlNodeIdShort src, DhtNodesList list, td::uint32 k, - td::uint32 a, DhtNode self, td::actor::ActorId node, td::actor::ActorId adnl) - : key_(key), self_(std::move(self)), print_id_(print_id), src_(src), k_(k), a_(a), node_(node), adnl_(adnl) { - add_nodes(std::move(list)); + DhtQuery(DhtKeyId key, DhtMember::PrintId print_id, adnl::AdnlNodeIdShort src, td::uint32 k, + td::uint32 a, td::int32 our_network_id, DhtNode self, bool client_only, td::actor::ActorId node, + td::actor::ActorId adnl) + : key_(key) + , self_(std::move(self)) + , client_only_(client_only) + , print_id_(print_id) + , src_(src) + , k_(k) + , a_(a) + , our_network_id_(our_network_id) + , node_(node) + , adnl_(adnl) { } DhtMember::PrintId print_id() const { return print_id_; } void send_queries(); void add_nodes(DhtNodesList list); - void finish_query() { - active_queries_--; - CHECK(active_queries_ <= k_); - send_queries(); - } + void finish_query(adnl::AdnlNodeIdShort id, bool success = true); DhtKeyId get_key() const { return key_; } @@ -67,6 +73,9 @@ class DhtQuery : public td::actor::Actor { td::uint32 get_k() const { return k_; } + td::int32 our_network_id() const { + return our_network_id_; + } void start_up() override { send_queries(); } @@ -75,15 +84,22 @@ class DhtQuery : public td::actor::Actor { virtual std::string get_name() const = 0; private: + struct NodeInfo { + DhtNode node; + int failed_attempts = 0; + }; DhtMember::PrintId print_id_; adnl::AdnlNodeIdShort src_; - std::map list_; - std::set pending_ids_; + std::map nodes_; + std::set result_list_, pending_queries_; td::uint32 k_; td::uint32 a_; + td::int32 our_network_id_; td::actor::ActorId node_; td::uint32 active_queries_ = 0; + static const int MAX_ATTEMPTS = 1; + protected: td::actor::ActorId adnl_; }; @@ -94,9 +110,12 @@ class DhtQueryFindNodes : public DhtQuery { public: DhtQueryFindNodes(DhtKeyId key, DhtMember::PrintId print_id, adnl::AdnlNodeIdShort src, DhtNodesList list, - td::uint32 k, td::uint32 a, DhtNode self, td::actor::ActorId node, - td::actor::ActorId adnl, td::Promise promise) - : DhtQuery(key, print_id, src, std::move(list), k, a, std::move(self), node, adnl), promise_(std::move(promise)) { + td::uint32 k, td::uint32 a, td::int32 our_network_id, DhtNode self, bool client_only, + td::actor::ActorId node, td::actor::ActorId adnl, + td::Promise promise) + : DhtQuery(key, print_id, src, k, a, our_network_id, std::move(self), client_only, node, adnl) + , promise_(std::move(promise)) { + add_nodes(std::move(list)); } void send_one_query(adnl::AdnlNodeIdShort id) override; void on_result(td::Result R, adnl::AdnlNodeIdShort dst); @@ -112,12 +131,17 @@ class DhtQueryFindValue : public DhtQuery { public: DhtQueryFindValue(DhtKeyId key, DhtMember::PrintId print_id, adnl::AdnlNodeIdShort src, DhtNodesList list, - td::uint32 k, td::uint32 a, DhtNode self, td::actor::ActorId node, - td::actor::ActorId adnl, td::Promise promise) - : DhtQuery(key, print_id, src, std::move(list), k, a, std::move(self), node, adnl), promise_(std::move(promise)) { + td::uint32 k, td::uint32 a, td::int32 our_network_id, DhtNode self, bool client_only, + td::actor::ActorId node, td::actor::ActorId adnl, + td::Promise promise) + : DhtQuery(key, print_id, src, k, a, our_network_id, std::move(self), client_only, node, adnl) + , promise_(std::move(promise)) { + add_nodes(std::move(list)); } void send_one_query(adnl::AdnlNodeIdShort id) override; + void send_one_query_nodes(adnl::AdnlNodeIdShort id); void on_result(td::Result R, adnl::AdnlNodeIdShort dst); + void on_result_nodes(td::Result R, adnl::AdnlNodeIdShort dst); void finish(DhtNodesList list) override; std::string get_name() const override { return "find value"; @@ -129,6 +153,7 @@ class DhtQueryStore : public td::actor::Actor { DhtMember::PrintId print_id_; td::uint32 k_; td::uint32 a_; + td::int32 our_network_id_; td::Promise promise_; td::actor::ActorId node_; td::actor::ActorId adnl_; @@ -139,10 +164,11 @@ class DhtQueryStore : public td::actor::Actor { td::uint32 remaining_; DhtNodesList list_; DhtNode self_; + bool client_only_; public: DhtQueryStore(DhtValue key_value, DhtMember::PrintId print_id, adnl::AdnlNodeIdShort src, DhtNodesList list, - td::uint32 k, td::uint32 a, DhtNode self, td::actor::ActorId node, + td::uint32 k, td::uint32 a, td::int32 our_network_id, DhtNode self, bool client_only, td::actor::ActorId node, td::actor::ActorId adnl, td::Promise promise); void send_stores(td::Result res); void store_ready(td::Result res); @@ -152,6 +178,65 @@ class DhtQueryStore : public td::actor::Actor { } }; +class DhtQueryRegisterReverseConnection : public td::actor::Actor { + private: + DhtMember::PrintId print_id_; + td::uint32 k_; + td::uint32 a_; + td::int32 our_network_id_; + td::Promise promise_; + td::actor::ActorId node_; + td::actor::ActorId adnl_; + adnl::AdnlNodeIdShort src_; + DhtKeyId key_id_; + td::BufferSlice query_; + td::uint32 success_ = 0; + td::uint32 fail_ = 0; + td::uint32 remaining_; + DhtNodesList list_; + DhtNode self_; + bool client_only_; + + public: + DhtQueryRegisterReverseConnection(DhtKeyId key_id, adnl::AdnlNodeIdFull client, td::uint32 ttl, + td::BufferSlice signature, DhtMember::PrintId print_id, adnl::AdnlNodeIdShort src, + DhtNodesList list, td::uint32 k, td::uint32 a, td::int32 our_network_id, + DhtNode self, bool client_only, td::actor::ActorId node, + td::actor::ActorId adnl, td::Promise promise); + void send_queries(td::Result R); + void ready(td::Result R); + void start_up() override; + DhtMember::PrintId print_id() const { + return print_id_; + } +}; + +class DhtQueryRequestReversePing : public DhtQuery { + private: + td::Promise promise_; + td::BufferSlice query_; + + public: + DhtQueryRequestReversePing(adnl::AdnlNodeIdShort client, adnl::AdnlNode target, td::BufferSlice signature, + DhtMember::PrintId print_id, adnl::AdnlNodeIdShort src, DhtNodesList list, td::uint32 k, + td::uint32 a, td::int32 our_network_id, DhtNode self, bool client_only, + td::actor::ActorId node, td::actor::ActorId adnl, + td::Promise promise) + : DhtQuery(DhtMember::get_reverse_connection_key(client).compute_key_id(), print_id, src, k, a, our_network_id, + std::move(self), client_only, node, adnl) + , promise_(std::move(promise)) + , query_(create_serialize_tl_object(target.tl(), std::move(signature), + client.bits256_value(), k)) { + add_nodes(std::move(list)); + } + void send_one_query(adnl::AdnlNodeIdShort id) override; + void on_result(td::Result R, adnl::AdnlNodeIdShort dst); + void finish(DhtNodesList list) override; + std::string get_name() const override { + return "request remote ping"; + } +}; + inline td::StringBuilder &operator<<(td::StringBuilder &sb, const DhtQuery &dht) { sb << dht.print_id(); return sb; diff --git a/dht/dht-remote-node.cpp b/dht/dht-remote-node.cpp index a6328bd0..1273750c 100644 --- a/dht/dht-remote-node.cpp +++ b/dht/dht-remote-node.cpp @@ -14,19 +14,16 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "dht.hpp" #include "td/utils/tl_storers.h" #include "td/utils/crypto.h" -#include "td/utils/tl_parsers.h" #include "td/utils/Random.h" #include "td/utils/format.h" -#include "keys/encryptor.h" - #include "auto/tl/ton_api.hpp" #include "dht-remote-node.hpp" @@ -35,77 +32,100 @@ namespace ton { namespace dht { +static const double PING_INTERVAL_DEFAULT = 60.0; +static const double PING_INTERVAL_MULTIPLIER = 1.1; +static const double PING_INTERVAL_MAX = 3600.0 * 4; + +DhtRemoteNode::DhtRemoteNode(DhtNode node, td::uint32 max_missed_pings, td::int32 our_network_id) + : node_(std::move(node)) + , max_missed_pings_(max_missed_pings) + , our_network_id_(our_network_id) + , ping_interval_(PING_INTERVAL_DEFAULT) { + failed_from_ = td::Time::now_cached(); + id_ = node_.get_key(); +} + td::Status DhtRemoteNode::receive_ping(DhtNode node, td::actor::ActorId adnl, adnl::AdnlNodeIdShort self_id) { TRY_STATUS(update_value(std::move(node), adnl, self_id)); + receive_ping(); + return td::Status::OK(); +} + +void DhtRemoteNode::receive_ping() { missed_pings_ = 0; + ping_interval_ = PING_INTERVAL_DEFAULT; if (ready_from_ == 0) { ready_from_ = td::Time::now_cached(); } - return td::Status::OK(); } td::Status DhtRemoteNode::update_value(DhtNode node, td::actor::ActorId adnl, adnl::AdnlNodeIdShort self_id) { - CHECK(node.adnl_id() == node_.adnl_id()); + if (node.adnl_id() != node_.adnl_id()) { + return td::Status::Error("Wrong adnl id"); + } if (node.version() <= node_.version()) { return td::Status::OK(); } - - TRY_RESULT(enc, node.adnl_id().pubkey().create_encryptor()); - auto tl = node.tl(); - auto sig = std::move(tl->signature_); - TRY_STATUS_PREFIX(enc->check_signature(serialize_tl_object(tl, true).as_slice(), sig.as_slice()), - "bad node signature: "); + TRY_STATUS(node.check_signature()); node_ = std::move(node); td::actor::send_closure(adnl, &adnl::Adnl::add_peer, self_id, node_.adnl_id(), node_.addr_list()); return td::Status::OK(); } -void DhtRemoteNode::send_ping(td::actor::ActorId adnl, td::actor::ActorId node, +void DhtRemoteNode::send_ping(bool client_only, td::actor::ActorId adnl, td::actor::ActorId node, adnl::AdnlNodeIdShort src) { missed_pings_++; - if (missed_pings_ > max_missed_pings_ && ready_from_ > 0) { - ready_from_ = 0; - failed_from_ = td::Time::now_cached(); + if (missed_pings_ > max_missed_pings_) { + ping_interval_ = std::min(ping_interval_ * PING_INTERVAL_MULTIPLIER, PING_INTERVAL_MAX); + if (ready_from_ > 0) { + ready_from_ = 0; + failed_from_ = td::Time::now_cached(); + } } last_ping_at_ = td::Time::now_cached(); td::actor::send_closure(adnl, &adnl::Adnl::add_peer, src, node_.adnl_id(), node_.addr_list()); - auto P = td::PromiseCreator::lambda( - [key = id_, id = node_.adnl_id().compute_short_id(), node, src, adnl](td::Result R) mutable { - if (R.is_error()) { - LOG(ERROR) << "[dht]: failed to get self node"; - return; - } - auto P = td::PromiseCreator::lambda([key, node, adnl](td::Result R) { - if (R.is_error()) { - VLOG(DHT_INFO) << "[dht]: received error for query to " << key << ": " << R.move_as_error(); - return; - } - auto F = fetch_tl_object(R.move_as_ok(), true); + auto P = td::PromiseCreator::lambda([key = id_, id = node_.adnl_id().compute_short_id(), client_only, node, src, adnl, + our_network_id = our_network_id_](td::Result R) mutable { + if (R.is_error()) { + LOG(ERROR) << "[dht]: failed to get self node"; + return; + } + auto P = td::PromiseCreator::lambda([key, node, adnl, our_network_id](td::Result R) { + if (R.is_error()) { + VLOG(DHT_INFO) << "[dht]: received error for query to " << key << ": " << R.move_as_error(); + return; + } + auto F = fetch_tl_object(R.move_as_ok(), true); - if (F.is_ok()) { - auto N = DhtNode::create(F.move_as_ok()); - if (N.is_ok()) { - td::actor::send_closure(node, &DhtMember::receive_ping, key, N.move_as_ok()); - } else { - VLOG(DHT_WARNING) << "[dht]: bad answer from " << key - << ": dropping bad getSignedAddressList() query answer: " << N.move_as_error(); - } - } else { - VLOG(DHT_WARNING) << "[dht]: bad answer from " << key - << ": dropping invalid getSignedAddressList() query answer: " << F.move_as_error(); - } - }); - auto Q = create_serialize_tl_object(); - auto B = create_serialize_tl_object_suffix(Q.as_slice(), R.move_as_ok().tl()); - td::actor::send_closure(adnl, &adnl::Adnl::send_query, src, id, "dht ping", std::move(P), - td::Timestamp::in(10.0 + td::Random::fast(0, 100) * 0.1), std::move(B)); - }); + if (F.is_ok()) { + auto N = DhtNode::create(F.move_as_ok(), our_network_id); + if (N.is_ok()) { + td::actor::send_closure(node, &DhtMember::receive_ping, key, N.move_as_ok()); + } else { + VLOG(DHT_WARNING) << "[dht]: bad answer from " << key + << ": dropping bad getSignedAddressList() query answer: " << N.move_as_error(); + } + } else { + VLOG(DHT_WARNING) << "[dht]: bad answer from " << key + << ": dropping invalid getSignedAddressList() query answer: " << F.move_as_error(); + } + }); + auto Q = create_serialize_tl_object(); + td::BufferSlice B; + if (client_only) { + B = std::move(Q); + } else { + B = create_serialize_tl_object_suffix(Q.as_slice(), R.move_as_ok().tl()); + } + td::actor::send_closure(adnl, &adnl::Adnl::send_query, src, id, "dht ping", std::move(P), + td::Timestamp::in(10.0 + td::Random::fast(0, 100) * 0.1), std::move(B)); + }); td::actor::send_closure(node, &DhtMember::get_self_node, std::move(P)); } @@ -118,15 +138,10 @@ adnl::AdnlNodeIdFull DhtRemoteNode::get_full_id() const { return node_.adnl_id(); } -td::Result> DhtRemoteNode::create(DhtNode node, td::uint32 max_missed_pings) { - TRY_RESULT(enc, node.adnl_id().pubkey().create_encryptor()); - auto tl = node.tl(); - auto sig = std::move(tl->signature_); - - TRY_STATUS_PREFIX(enc->check_signature(serialize_tl_object(tl, true).as_slice(), sig.as_slice()), - "bad node signature: "); - - return std::make_unique(std::move(node), max_missed_pings); +td::Result> DhtRemoteNode::create(DhtNode node, td::uint32 max_missed_pings, + td::int32 our_network_id) { + TRY_STATUS(node.check_signature()); + return std::make_unique(std::move(node), max_missed_pings, our_network_id); } } // namespace dht diff --git a/dht/dht-remote-node.hpp b/dht/dht-remote-node.hpp index f1a2a739..8e7db489 100644 --- a/dht/dht-remote-node.hpp +++ b/dht/dht-remote-node.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -40,19 +40,18 @@ class DhtRemoteNode { DhtNode node_; td::uint32 max_missed_pings_; + td::int32 our_network_id_; td::uint32 missed_pings_ = 0; double last_ping_at_ = 0; double ready_from_ = 0; double failed_from_ = 0; + double ping_interval_; td::int32 version_; public: - DhtRemoteNode(DhtNode node, td::uint32 max_missed_pings) - : node_(std::move(node)), max_missed_pings_(max_missed_pings) { - failed_from_ = td::Time::now_cached(); - id_ = node_.get_key(); - } - static td::Result> create(DhtNode node, td::uint32 max_missed_pings); + DhtRemoteNode(DhtNode node, td::uint32 max_missed_pings, td::int32 our_network_id); + static td::Result> create(DhtNode node, td::uint32 max_missed_pings, + td::int32 our_network_id); DhtNode get_node() const { return node_.clone(); } @@ -76,8 +75,13 @@ class DhtRemoteNode { double last_ping_at() const { return last_ping_at_; } - void send_ping(td::actor::ActorId adnl, td::actor::ActorId node, adnl::AdnlNodeIdShort src); + double ping_interval() const { + return ping_interval_; + } + void send_ping(bool client_only, td::actor::ActorId adnl, td::actor::ActorId node, + adnl::AdnlNodeIdShort src); td::Status receive_ping(DhtNode node, td::actor::ActorId adnl, adnl::AdnlNodeIdShort self_id); + void receive_ping(); td::Status update_value(DhtNode node, td::actor::ActorId adnl, adnl::AdnlNodeIdShort self_id); }; diff --git a/dht/dht-types.cpp b/dht/dht-types.cpp index 052f9305..118df2a8 100644 --- a/dht/dht-types.cpp +++ b/dht/dht-types.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "dht-types.h" #include "td/utils/Random.h" @@ -209,6 +209,10 @@ td::Status DhtValue::check() const { return key_.update_rule()->check_value(*this); } +bool DhtValue::check_is_acceptable() const { + return key_.update_rule()->check_is_acceptable(*this); +} + DhtKeyId DhtValue::key_id() const { return key_.key().compute_key_id(); } @@ -360,6 +364,21 @@ td::Status DhtUpdateRuleOverlayNodes::update_value(DhtValue &value, DhtValue &&n return td::Status::OK(); } +bool DhtUpdateRuleOverlayNodes::check_is_acceptable(const ton::dht::DhtValue &value) { + auto F = fetch_tl_object(value.value().clone_as_buffer_slice(), true); + if (F.is_error()) { + return false; + } + auto L = F.move_as_ok(); + auto now = td::Clocks::system(); + for (auto &node : L->nodes_) { + if (node->version_ + 600 > now) { + return true; + } + } + return false; +} + tl_object_ptr DhtUpdateRuleOverlayNodes::tl() const { return create_tl_object(); } diff --git a/dht/dht-types.h b/dht/dht-types.h index 28bef5ce..45657d45 100644 --- a/dht/dht-types.h +++ b/dht/dht-types.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -119,6 +119,9 @@ class DhtUpdateRule { virtual td::Status check_value(const DhtValue &value) = 0; virtual td::Status update_value(DhtValue &value, DhtValue &&new_value) = 0; virtual bool need_republish() const = 0; + virtual bool check_is_acceptable(const DhtValue &value) { + return true; + } virtual tl_object_ptr tl() const = 0; static td::Result> create(tl_object_ptr obj); }; @@ -210,6 +213,7 @@ class DhtValue { void update_signature(td::BufferSlice signature); void update_signature(td::SharedSlice signature); td::Status check() const; + bool check_is_acceptable() const; DhtKeyId key_id() const; @@ -249,6 +253,7 @@ class DhtUpdateRuleOverlayNodes : public DhtUpdateRule { bool need_republish() const override { return false; } + bool check_is_acceptable(const DhtValue &value) override; tl_object_ptr tl() const override; static td::Result> create(); }; diff --git a/dht/dht.cpp b/dht/dht.cpp index 8231f89f..8d7b02b7 100644 --- a/dht/dht.cpp +++ b/dht/dht.cpp @@ -14,13 +14,12 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "dht.hpp" #include "td/utils/tl_storers.h" #include "td/utils/crypto.h" -#include "td/utils/tl_parsers.h" #include "td/utils/Random.h" #include "td/utils/base64.h" @@ -28,9 +27,6 @@ #include "td/db/RocksDb.h" -#include "keys/encryptor.h" -#include "adnl/utils.hpp" - #include "auto/tl/ton_api.hpp" #include "dht.h" @@ -44,9 +40,9 @@ namespace dht { td::actor::ActorOwn DhtMember::create(adnl::AdnlNodeIdShort id, std::string db_root, td::actor::ActorId keyring, - td::actor::ActorId adnl, td::uint32 k, td::uint32 a) { - return td::actor::ActorOwn( - td::actor::create_actor("dht", id, db_root, keyring, adnl, k, a)); + td::actor::ActorId adnl, td::int32 network_id, + td::uint32 k, td::uint32 a, bool client_only) { + return td::actor::create_actor("dht", id, db_root, keyring, adnl, network_id, k, a, client_only); } td::Result> Dht::create(adnl::AdnlNodeIdShort id, std::string db_root, @@ -56,52 +52,75 @@ td::Result> Dht::create(adnl::AdnlNodeIdShort id, std:: CHECK(conf->get_k() > 0); CHECK(conf->get_a() > 0); - auto D = DhtMember::create(id, db_root, keyring, adnl, conf->get_k(), conf->get_a()); + auto D = DhtMember::create(id, db_root, keyring, adnl, conf->get_network_id(), conf->get_k(), conf->get_a()); auto &nodes = conf->nodes(); for (auto &node : nodes.list()) { auto key = node.get_key(); - td::actor::send_closure(D, &DhtMember::add_full_node, key, node.clone()); + td::actor::send_closure(D, &DhtMember::add_full_node, key, node.clone(), true); + } + return std::move(D); +} + +td::Result> Dht::create_client(adnl::AdnlNodeIdShort id, std::string db_root, + std::shared_ptr conf, + td::actor::ActorId keyring, + td::actor::ActorId adnl) { + CHECK(conf->get_k() > 0); + CHECK(conf->get_a() > 0); + + auto D = DhtMember::create(id, db_root, keyring, adnl, conf->get_network_id(), conf->get_k(), conf->get_a(), true); + auto &nodes = conf->nodes(); + + for (auto &node : nodes.list()) { + auto key = node.get_key(); + td::actor::send_closure(D, &DhtMember::add_full_node, key, node.clone(), true); } return std::move(D); } void DhtMemberImpl::start_up() { - std::shared_ptr kv = std::make_shared( - td::RocksDb::open(PSTRING() << db_root_ << "/dht-" << td::base64url_encode(id_.as_slice())).move_as_ok()); std::vector methods = {ton_api::dht_getSignedAddressList::ID, ton_api::dht_findNode::ID, ton_api::dht_findValue::ID, ton_api::dht_store::ID, ton_api::dht_ping::ID, + ton_api::dht_registerReverseConnection::ID, + ton_api::dht_requestReversePing::ID, ton_api::dht_query::ID, - ton_api::dht_message::ID}; + ton_api::dht_message::ID, + ton_api::dht_requestReversePingCont::ID}; for (auto it : methods) { td::actor::send_closure(adnl_, &adnl::Adnl::subscribe, id_, adnl::Adnl::int_to_bytestring(it), std::make_unique(actor_id(this), id_)); } alarm_timestamp() = td::Timestamp::in(1.0); - for (td::uint32 bit = 0; bit < 256; bit++) { - auto key = create_hash_tl_object(bit); - std::string value; - auto R = kv->get(key.as_slice(), value); - R.ensure(); - if (R.move_as_ok() == td::KeyValue::GetStatus::Ok) { - auto V = fetch_tl_object(td::BufferSlice{value}, true); - V.ensure(); - auto nodes = std::move(V.move_as_ok()->nodes_); - auto s = nodes->nodes_.size(); - DhtNodesList list{std::move(nodes)}; - CHECK(list.size() == s); - auto &B = buckets_[bit]; - for (auto &node : list.list()) { - auto key = node.get_key(); - B.add_full_node(key, std::move(node), adnl_, id_); + + if (!db_root_.empty()) { + std::shared_ptr kv = std::make_shared( + td::RocksDb::open(PSTRING() << db_root_ << "/dht-" << td::base64url_encode(id_.as_slice())).move_as_ok()); + for (td::uint32 bit = 0; bit < 256; bit++) { + auto key = create_hash_tl_object(bit); + std::string value; + auto R = kv->get(key.as_slice(), value); + R.ensure(); + if (R.move_as_ok() == td::KeyValue::GetStatus::Ok) { + auto V = fetch_tl_object(td::BufferSlice{value}, true); + V.ensure(); + auto nodes = std::move(V.move_as_ok()->nodes_); + auto s = nodes->nodes_.size(); + DhtNodesList list{std::move(nodes), network_id_}; + CHECK(list.size() <= s); // Some nodes can be dropped due to a wrong network id + auto &B = buckets_[bit]; + for (auto &node : list.list()) { + auto key = node.get_key(); + B.add_full_node(key, std::move(node), adnl_, id_, network_id_); + } } } + db_ = DbType{std::move(kv)}; } - db_ = DbType{std::move(kv)}; } void DhtMemberImpl::tear_down() { @@ -110,8 +129,11 @@ void DhtMemberImpl::tear_down() { ton_api::dht_findValue::ID, ton_api::dht_store::ID, ton_api::dht_ping::ID, + ton_api::dht_registerReverseConnection::ID, + ton_api::dht_requestReversePing::ID, ton_api::dht_query::ID, - ton_api::dht_message::ID}; + ton_api::dht_message::ID, + ton_api::dht_requestReversePingCont::ID}; for (auto it : methods) { td::actor::send_closure(adnl_, &adnl::Adnl::unsubscribe, id_, adnl::Adnl::int_to_bytestring(it)); @@ -119,6 +141,9 @@ void DhtMemberImpl::tear_down() { } void DhtMemberImpl::save_to_db() { + if (db_root_.empty()) { + return; + } next_save_to_db_at_ = td::Timestamp::in(10.0); alarm_timestamp().relax(next_save_to_db_at_); @@ -275,16 +300,78 @@ void DhtMemberImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::dht_getSig get_self_node(std::move(P)); } +static td::BufferSlice register_reverse_connection_to_sign(adnl::AdnlNodeIdShort client, adnl::AdnlNodeIdShort dht_id, + td::uint32 ttl) { + td::BufferSlice result(32 + 32 + 4); + td::MutableSlice s = result.as_slice(); + s.copy_from(client.as_slice()); + s.remove_prefix(32); + s.copy_from(dht_id.as_slice()); + s.remove_prefix(32); + s.copy_from(std::string(reinterpret_cast(&ttl), 4)); + return result; +} + +void DhtMemberImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::dht_registerReverseConnection &query, + td::Promise promise) { + td::uint32 ttl = query.ttl_, now = (td::uint32)td::Clocks::system(); + if (ttl <= now) { + return; + } + PublicKey pub{query.node_}; + adnl::AdnlNodeIdShort client_id{pub.compute_short_id()}; + td::BufferSlice to_sign = register_reverse_connection_to_sign(client_id, src, ttl); + TRY_RESULT_PROMISE(promise, encryptor, pub.create_encryptor()); + TRY_STATUS_PROMISE(promise, encryptor->check_signature(to_sign, query.signature_)); + DhtKeyId key_id = get_reverse_connection_key(client_id).compute_key_id(); + reverse_connections_[client_id] = ReverseConnection{src, key_id, td::Timestamp::at_unix(std::min(ttl, now + 300))}; + promise.set_value(create_serialize_tl_object()); +} + +void DhtMemberImpl::process_query(adnl::AdnlNodeIdShort src, ton_api::dht_requestReversePing &query, + td::Promise promise) { + adnl::AdnlNodeIdShort client{query.client_}; + auto it = reverse_connections_.find(client); + if (it != reverse_connections_.end()) { + if (it->second.ttl_.is_in_past()) { + reverse_connections_.erase(it); + } else { + PublicKey pub{query.target_->id_}; + TRY_RESULT_PROMISE(promise, encryptor, pub.create_encryptor()); + TRY_STATUS_PROMISE(promise, + encryptor->check_signature(serialize_tl_object(query.target_, true), query.signature_)); + td::actor::send_closure(adnl_, &adnl::Adnl::send_message, id_, it->second.dht_node_, + create_serialize_tl_object( + std::move(query.target_), std::move(query.signature_), query.client_)); + promise.set_result(create_serialize_tl_object()); + return; + } + } + auto k = static_cast(query.k_); + if (k > max_k()) { + k = max_k(); + } + auto R = get_nearest_nodes(get_reverse_connection_key(client).compute_key_id(), k); + promise.set_value(create_serialize_tl_object(R.tl())); +} + void DhtMemberImpl::receive_query(adnl::AdnlNodeIdShort src, td::BufferSlice data, td::Promise promise) { + if (client_only_) { + return; + } { auto R = fetch_tl_prefix(data, true); if (R.is_ok()) { - auto N = DhtNode::create(std::move(R.move_as_ok()->node_)); + auto N = DhtNode::create(std::move(R.move_as_ok()->node_), network_id_); if (N.is_ok()) { auto node = N.move_as_ok(); - auto key = node.get_key(); - add_full_node(key, std::move(node)); + if (node.adnl_id().compute_short_id() == src) { + auto key = node.get_key(); + add_full_node(key, std::move(node), true); + } else { + VLOG(DHT_WARNING) << this << ": dropping bad node: unexpected adnl id"; + } } else { VLOG(DHT_WARNING) << this << ": dropping bad node " << N.move_as_error(); } @@ -308,10 +395,10 @@ void DhtMemberImpl::receive_query(adnl::AdnlNodeIdShort src, td::BufferSlice dat VLOG(DHT_EXTRA_DEBUG) << this << ": query to DHT from " << src << ": " << ton_api::to_string(Q); - ton_api::downcast_call(*Q.get(), [&](auto &object) { this->process_query(src, object, std::move(promise)); }); + ton_api::downcast_call(*Q, [&](auto &object) { this->process_query(src, object, std::move(promise)); }); } -void DhtMemberImpl::add_full_node(DhtKeyId key, DhtNode node) { +void DhtMemberImpl::add_full_node(DhtKeyId key, DhtNode node, bool set_active) { VLOG(DHT_EXTRA_DEBUG) << this << ": adding full node " << key; auto eid = key ^ key_; @@ -323,7 +410,7 @@ void DhtMemberImpl::add_full_node(DhtKeyId key, DhtNode node) { #endif if (bit < 256) { CHECK(key.get_bit(bit) != key_.get_bit(bit)); - buckets_[bit].add_full_node(key, std::move(node), adnl_, id_); + buckets_[bit].add_full_node(key, std::move(node), adnl_, id_, network_id_, set_active); } else { CHECK(key == key_); } @@ -342,6 +429,27 @@ void DhtMemberImpl::receive_ping(DhtKeyId key, DhtNode result) { } void DhtMemberImpl::receive_message(adnl::AdnlNodeIdShort src, td::BufferSlice data) { + auto F = fetch_tl_object(data, true); + if (F.is_ok()) { + auto S = [&]() -> td::Status { + auto f = F.move_as_ok(); + adnl::AdnlNodeIdShort client{f->client_}; + if (!our_reverse_connections_.count(client)) { + return td::Status::Error(PSTRING() << ": unknown id for reverse ping: " << client); + } + TRY_RESULT_PREFIX(node, adnl::AdnlNode::create(f->target_), "failed to parse node: "); + TRY_RESULT_PREFIX(encryptor, node.pub_id().pubkey().create_encryptor(), "failed to create encryptor: "); + TRY_STATUS_PREFIX(encryptor->check_signature(serialize_tl_object(f->target_, true), f->signature_), + "invalid signature: "); + VLOG(DHT_INFO) << this << ": sending reverse ping to " << node.compute_short_id(); + td::actor::send_closure(adnl_, &adnl::Adnl::add_peer, client, node.pub_id(), node.addr_list()); + td::actor::send_closure(adnl_, &adnl::Adnl::send_message, client, node.compute_short_id(), td::BufferSlice()); + return td::Status::OK(); + }(); + if (S.is_error()) { + VLOG(DHT_INFO) << this << ": " << S; + } + } } void DhtMemberImpl::set_value(DhtValue value, td::Promise promise) { @@ -358,23 +466,88 @@ void DhtMemberImpl::set_value(DhtValue value, td::Promise promise) { void DhtMemberImpl::get_value_in(DhtKeyId key, td::Promise result) { auto P = td::PromiseCreator::lambda([key, promise = std::move(result), SelfId = actor_id(this), print_id = print_id(), - adnl = adnl_, list = get_nearest_nodes(key, k_), k = k_, a = a_, - id = id_](td::Result R) mutable { + adnl = adnl_, list = get_nearest_nodes(key, k_ * 2), k = k_, a = a_, + network_id = network_id_, id = id_, + client_only = client_only_](td::Result R) mutable { R.ensure(); - td::actor::create_actor("FindValueQuery", key, print_id, id, std::move(list), k, a, - R.move_as_ok(), SelfId, adnl, std::move(promise)) + td::actor::create_actor("FindValueQuery", key, print_id, id, std::move(list), k, a, network_id, + R.move_as_ok(), client_only, SelfId, adnl, std::move(promise)) .release(); }); get_self_node(std::move(P)); } +void DhtMemberImpl::register_reverse_connection(adnl::AdnlNodeIdFull client, td::Promise promise) { + auto client_short = client.compute_short_id(); + td::uint32 ttl = (td::uint32)td::Clocks::system() + 300; + our_reverse_connections_.insert(client_short); + auto key_id = get_reverse_connection_key(client_short).compute_key_id(); + td::actor::send_closure(keyring_, &keyring::Keyring::sign_message, client_short.pubkey_hash(), + register_reverse_connection_to_sign(client_short, id_, ttl), + [=, print_id = print_id(), list = get_nearest_nodes(key_id, k_ * 2), SelfId = actor_id(this), + promise = std::move(promise)](td::Result R) mutable { + TRY_RESULT_PROMISE_PREFIX(promise, signature, std::move(R), "Failed to sign: "); + td::actor::send_closure(SelfId, &DhtMemberImpl::get_self_node, + [=, list = std::move(list), signature = std::move(signature), + promise = std::move(promise)](td::Result R) mutable { + R.ensure(); + td::actor::create_actor( + "RegisterReverseQuery", key_id, std::move(client), ttl, + std::move(signature), print_id, id_, std::move(list), k_, a_, + network_id_, R.move_as_ok(), client_only_, SelfId, adnl_, + std::move(promise)) + .release(); + }); + }); +} + +void DhtMemberImpl::request_reverse_ping(adnl::AdnlNode target, adnl::AdnlNodeIdShort client, + td::Promise promise) { + auto pubkey_hash = target.compute_short_id().pubkey_hash(); + td::BufferSlice to_sign = serialize_tl_object(target.tl(), true); + td::actor::send_closure(keyring_, &keyring::Keyring::sign_message, pubkey_hash, std::move(to_sign), + [SelfId = actor_id(this), promise = std::move(promise), target = std::move(target), + client](td::Result R) mutable { + TRY_RESULT_PROMISE(promise, signature, std::move(R)); + td::actor::send_closure(SelfId, &DhtMemberImpl::request_reverse_ping_cont, + std::move(target), std::move(signature), client, + std::move(promise)); + }); +} + +void DhtMemberImpl::request_reverse_ping_cont(adnl::AdnlNode target, td::BufferSlice signature, + adnl::AdnlNodeIdShort client, td::Promise promise) { + auto it = reverse_connections_.find(client); + if (it != reverse_connections_.end()) { + if (it->second.ttl_.is_in_past()) { + reverse_connections_.erase(it); + } else { + td::actor::send_closure(adnl_, &adnl::Adnl::send_message, id_, it->second.dht_node_, + create_serialize_tl_object( + target.tl(), std::move(signature), client.bits256_value())); + promise.set_result(td::Unit()); + return; + } + } + auto key_id = get_reverse_connection_key(client).compute_key_id(); + get_self_node([=, target = std::move(target), signature = std::move(signature), promise = std::move(promise), + SelfId = actor_id(this), print_id = print_id(), list = get_nearest_nodes(key_id, k_ * 2), + client_only = client_only_](td::Result R) mutable { + R.ensure(); + td::actor::create_actor( + "RequestReversePing", client, std::move(target), std::move(signature), print_id, id_, std::move(list), k_, a_, + network_id_, R.move_as_ok(), client_only, SelfId, adnl_, std::move(promise)) + .release(); + }); +} + void DhtMemberImpl::check() { VLOG(DHT_INFO) << this << ": ping=" << ping_queries_ << " fnode=" << find_node_queries_ << " fvalue=" << find_value_queries_ << " store=" << store_queries_ << " addrlist=" << get_addr_list_queries_; for (auto &bucket : buckets_) { - bucket.check(adnl_, actor_id(this), id_); + bucket.check(client_only_, adnl_, actor_id(this), id_); } if (next_save_to_db_at_.is_in_past()) { save_to_db(); @@ -427,6 +600,16 @@ void DhtMemberImpl::check() { } } } + if (reverse_connections_.size() > 0) { + auto it = reverse_connections_.upper_bound(last_check_reverse_conn_); + if (it == reverse_connections_.end()) { + it = reverse_connections_.begin(); + } + last_check_reverse_conn_ = it->first; + if (it->second.ttl_.is_in_past()) { + reverse_connections_.erase(it); + } + } if (republish_att_.is_in_past()) { auto it = our_values_.lower_bound(last_republish_key_); @@ -468,11 +651,12 @@ void DhtMemberImpl::check() { DhtKeyId key{x}; auto P = td::PromiseCreator::lambda([key, promise = std::move(promise), SelfId = actor_id(this), - print_id = print_id(), adnl = adnl_, list = get_nearest_nodes(key, k_), k = k_, - a = a_, id = id_](td::Result R) mutable { + print_id = print_id(), adnl = adnl_, list = get_nearest_nodes(key, k_ * 2), + k = k_, a = a_, network_id = network_id_, id = id_, + client_only = client_only_](td::Result R) mutable { R.ensure(); - td::actor::create_actor("FindNodesQuery", key, print_id, id, std::move(list), k, a, - R.move_as_ok(), SelfId, adnl, std::move(promise)) + td::actor::create_actor("FindNodesQuery", key, print_id, id, std::move(list), k, a, network_id, + R.move_as_ok(), client_only, SelfId, adnl, std::move(promise)) .release(); }); @@ -493,11 +677,12 @@ void DhtMemberImpl::send_store(DhtValue value, td::Promise promise) { auto key_id = value.key_id(); auto P = td::PromiseCreator::lambda([value = std::move(value), print_id = print_id(), id = id_, - list = get_nearest_nodes(key_id, k_), k = k_, a = a_, SelfId = actor_id(this), - adnl = adnl_, promise = std::move(promise)](td::Result R) mutable { + client_only = client_only_, list = get_nearest_nodes(key_id, k_ * 2), k = k_, + a = a_, network_id = network_id_, SelfId = actor_id(this), adnl = adnl_, + promise = std::move(promise)](td::Result R) mutable { R.ensure(); td::actor::create_actor("StoreQuery", std::move(value), print_id, id, std::move(list), k, a, - R.move_as_ok(), SelfId, adnl, std::move(promise)) + network_id, R.move_as_ok(), client_only, SelfId, adnl, std::move(promise)) .release(); }); @@ -506,45 +691,56 @@ void DhtMemberImpl::send_store(DhtValue value, td::Promise promise) { void DhtMemberImpl::get_self_node(td::Promise promise) { auto P = td::PromiseCreator::lambda([promise = std::move(promise), print_id = print_id(), id = id_, - keyring = keyring_](td::Result R) mutable { + keyring = keyring_, client_only = client_only_, + network_id = network_id_](td::Result R) mutable { R.ensure(); auto node = R.move_as_ok(); auto version = static_cast(td::Clocks::system()); - auto B = create_serialize_tl_object(node.pub_id().tl(), node.addr_list().tl(), version, - td::BufferSlice()); - CHECK(node.addr_list().size() > 0); - auto P = td::PromiseCreator::lambda( - [promise = std::move(promise), node = std::move(node), version](td::Result R) mutable { - R.ensure(); - DhtNode n{node.pub_id(), node.addr_list(), version, R.move_as_ok()}; - promise.set_result(std::move(n)); - }); + td::BufferSlice B = serialize_tl_object( + DhtNode{node.pub_id(), node.addr_list(), version, network_id, td::BufferSlice{}}.tl(), true); + if (!client_only) { + CHECK(node.addr_list().size() > 0); + } + auto P = td::PromiseCreator::lambda([promise = std::move(promise), node = std::move(node), version, + network_id](td::Result R) mutable { + R.ensure(); + DhtNode n{node.pub_id(), node.addr_list(), version, network_id, R.move_as_ok()}; + promise.set_result(std::move(n)); + }); td::actor::send_closure(keyring, &keyring::Keyring::sign_message, id.pubkey_hash(), std::move(B), std::move(P)); }); td::actor::send_closure(adnl_, &adnl::Adnl::get_self_node, id_, std::move(P)); } -td::Result> Dht::create_global_config(tl_object_ptr conf) { - td::uint32 k; - if (conf->k_ == 0) { +td::Result> Dht::create_global_config(tl_object_ptr conf) { + td::uint32 k = 0, a = 0; + td::int32 network_id = -1; + tl_object_ptr static_nodes; + ton_api::downcast_call(*conf, td::overloaded( + [&](ton_api::dht_config_global &f) { + k = f.k_; + a = f.a_; + network_id = -1; + static_nodes = std::move(f.static_nodes_); + }, + [&](ton_api::dht_config_global_v2 &f) { + k = f.k_; + a = f.a_; + network_id = f.network_id_; + static_nodes = std::move(f.static_nodes_); + })); + if (k == 0) { k = DhtMember::default_k(); - } else if (conf->k_ > 0 && static_cast(conf->k_) <= DhtMember::max_k()) { - k = conf->k_; - } else { - return td::Status::Error(ErrorCode::protoviolation, PSTRING() << "bad value k=" << conf->k_); + } else if (k > DhtMember::max_k()) { + return td::Status::Error(ErrorCode::protoviolation, PSTRING() << "bad value k=" << k); } - td::uint32 a; - if (conf->a_ == 0) { + if (a == 0) { a = DhtMember::default_a(); - } else if (conf->a_ > 0 && static_cast(conf->a_) <= DhtMember::max_a()) { - a = conf->a_; - } else { - return td::Status::Error(ErrorCode::protoviolation, PSTRING() << "bad value a=" << conf->a_); + } else if (a > DhtMember::max_a()) { + return td::Status::Error(ErrorCode::protoviolation, PSTRING() << "bad value a=" << a); } - - DhtNodesList l{std::move(conf->static_nodes_)}; - - return std::make_shared(k, a, std::move(l)); + DhtNodesList l{std::move(static_nodes), network_id}; + return std::make_shared(k, a, network_id, std::move(l)); } } // namespace dht diff --git a/dht/dht.h b/dht/dht.h index fea18e5d..b9c65c8a 100644 --- a/dht/dht.h +++ b/dht/dht.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -42,14 +42,22 @@ class Dht : public td::actor::Actor { std::shared_ptr conf, td::actor::ActorId keyring, td::actor::ActorId adnl); + static td::Result> create_client(adnl::AdnlNodeIdShort id, std::string db_root, + std::shared_ptr conf, + td::actor::ActorId keyring, + td::actor::ActorId adnl); static td::Result> create_global_config( - tl_object_ptr conf); + tl_object_ptr conf); virtual adnl::AdnlNodeIdShort get_id() const = 0; virtual void set_value(DhtValue key_value, td::Promise result) = 0; virtual void get_value(DhtKey key, td::Promise result) = 0; + virtual void register_reverse_connection(adnl::AdnlNodeIdFull client, td::Promise promise) = 0; + virtual void request_reverse_ping(adnl::AdnlNode target, adnl::AdnlNodeIdShort client, + td::Promise promise) = 0; + virtual void dump(td::StringBuilder &sb) const = 0; virtual ~Dht() = default; diff --git a/dht/dht.hpp b/dht/dht.hpp index 3a05dd34..9fb05e08 100644 --- a/dht/dht.hpp +++ b/dht/dht.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -52,15 +52,20 @@ class DhtGlobalConfig { auto get_a() const { return a_; } + auto get_network_id() const { + return network_id_; + } const auto &nodes() const { return static_nodes_; } - DhtGlobalConfig(td::uint32 k, td::uint32 a, DhtNodesList nodes) : k_(k), a_(a), static_nodes_(std::move(nodes)) { + DhtGlobalConfig(td::uint32 k, td::uint32 a, td::int32 network_id, DhtNodesList nodes) + : k_(k), a_(a), network_id_(network_id), static_nodes_(std::move(nodes)) { } private: td::uint32 k_; td::uint32 a_; + td::int32 network_id_; DhtNodesList static_nodes_; }; @@ -85,12 +90,12 @@ class DhtMember : public Dht { static td::actor::ActorOwn create(adnl::AdnlNodeIdShort id, std::string db_root, td::actor::ActorId keyring, - td::actor::ActorId adnl, td::uint32 k = 10, - td::uint32 a = 3); + td::actor::ActorId adnl, td::int32 network_id, + td::uint32 k = 10, td::uint32 a = 3, bool client_only = false); //virtual void update_addr_list(tl_object_ptr addr_list) = 0; //virtual void add_node(adnl::AdnlNodeIdShort id) = 0; - virtual void add_full_node(DhtKeyId id, DhtNode node) = 0; + virtual void add_full_node(DhtKeyId id, DhtNode node, bool set_active) = 0; virtual void receive_ping(DhtKeyId id, DhtNode result) = 0; @@ -101,6 +106,10 @@ class DhtMember : public Dht { virtual void get_self_node(td::Promise promise) = 0; virtual PrintId print_id() const = 0; + + static DhtKey get_reverse_connection_key(adnl::AdnlNodeIdShort node) { + return DhtKey{node.pubkey_hash(), "address", 0}; + } }; inline td::StringBuilder &operator<<(td::StringBuilder &sb, const DhtMember::PrintId &id) { diff --git a/dht/test/dht-test-ping.cpp b/dht/test/dht-test-ping.cpp deleted file mode 100644 index d51b34bb..00000000 --- a/dht/test/dht-test-ping.cpp +++ /dev/null @@ -1,303 +0,0 @@ -/* - This file is part of TON Blockchain source code. - - TON Blockchain is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 2 - of the License, or (at your option) any later version. - - TON Blockchain 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with TON Blockchain. If not, see . - - In addition, as a special exception, the copyright holders give permission - to link the code of portions of this program with the OpenSSL library. - You must obey the GNU General Public License in all respects for all - of the code used other than OpenSSL. If you modify file(s) with this - exception, you may extend this exception to your version of the file(s), - but you are not obligated to do so. If you do not wish to do so, delete this - exception statement from your version. If you delete this exception statement - from all source files in the program, then also delete it here. - - Copyright 2017-2019 Telegram Systems LLP -*/ -#include "adnl/adnl-network-manager.h" -#include "adnl/adnl-peer-table.h" -#include "adnl/utils.hpp" -#include "keys/encryptor.h" -#include "td/utils/Time.h" -#include "td/utils/format.h" -#include "td/utils/OptionsParser.h" -#include "td/utils/filesystem.h" -#include "dht/dht.h" -#include "auto/tl/ton_api_json.h" - -#include -#include - -template -std::ostream &operator<<(std::ostream &stream, const td::UInt &x) { - for (size_t i = 0; i < size / 8; i++) { - stream << td::format::hex_digit((x.raw[i] >> 4) & 15) << td::format::hex_digit(x.raw[i] & 15); - } - - return stream; -} - -class adnl::AdnlNode : public td::actor::Actor { - private: - std::vector ping_ids_; - - td::actor::ActorOwn network_manager_; - td::actor::ActorOwn peer_table_; - td::actor::ActorOwn dht_node_; - - td::UInt256 local_id_; - bool local_id_set_ = false; - - std::string host_ = "127.0.0.1"; - td::uint32 ip_ = 0x7f000001; - td::uint16 port_ = 2380; - - std::string local_config_ = "ton-local.config"; - std::string global_config_ = "ton-global.config"; - - void receive_message(td::UInt256 src, td::UInt256 dst, td::BufferSlice data) { - std::cout << "MESSAGE FROM " << src << " to " << dst << " of size " << std::to_string(data.size()) << "\n"; - } - - void receive_query(td::UInt256 src, td::UInt256 dst, td::uint64 query_id, td::BufferSlice data) { - std::cout << "QUERY " << std::to_string(query_id) << " FROM " << src << " to " << dst << " of size " - << std::to_string(data.size()) << "\n"; - td::actor::send_closure(peer_table_, &ton::adnl::AdnlPeerTable::answer_query, dst, src, query_id, - ton::create_tl_object()); - } - - std::unique_ptr make_callback() { - class Callback : public ton::adnl::AdnlPeerTable::Callback { - public: - void receive_message(td::UInt256 src, td::UInt256 dst, td::BufferSlice data) override { - td::actor::send_closure(id_, &adnl::AdnlNode::receive_message, src, dst, std::move(data)); - } - void receive_query(td::UInt256 src, td::UInt256 dst, td::uint64 query_id, td::BufferSlice data) override { - td::actor::send_closure(id_, &adnl::AdnlNode::receive_query, src, dst, query_id, std::move(data)); - } - Callback(td::actor::ActorId id) : id_(std::move(id)) { - } - - private: - td::actor::ActorId id_; - }; - - return std::make_unique(td::actor::actor_id(this)); - } - - public: - void set_local_config(std::string str) { - local_config_ = str; - } - void set_global_config(std::string str) { - global_config_ = str; - } - void start_up() override { - alarm_timestamp() = td::Timestamp::in(1); - } - adnl::AdnlNode() { - network_manager_ = ton::adnl::AdnlNetworkManager::create(); - peer_table_ = ton::adnl::AdnlPeerTable::create(); - td::actor::send_closure(network_manager_, &ton::adnl::AdnlNetworkManager::register_peer_table, peer_table_.get()); - td::actor::send_closure(peer_table_, &ton::adnl::AdnlPeerTable::register_network_manager, network_manager_.get()); - } - void listen_udp(td::uint16 port) { - td::actor::send_closure(network_manager_, &ton::adnl::AdnlNetworkManager::add_listening_udp_port, "0.0.0.0", port); - port_ = port; - } - void run() { - auto L = td::read_file(local_config_); - if (L.is_error()) { - LOG(FATAL) << "can not read local config: " << L.move_as_error(); - } - auto L2 = td::json_decode(L.move_as_ok().as_slice()); - if (L2.is_error()) { - LOG(FATAL) << "can not parse local config: " << L2.move_as_error(); - } - auto lc_j = L2.move_as_ok(); - if (lc_j.type() != td::JsonValue::Type::Object) { - LOG(FATAL) << "can not parse local config: expected json object"; - } - - ton::ton_api::config_local lc; - auto rl = ton::ton_api::from_json(lc, lc_j.get_object()); - if (rl.is_error()) { - LOG(FATAL) << "can not interpret local config: " << rl.move_as_error(); - } - - auto G = td::read_file(global_config_); - if (G.is_error()) { - LOG(FATAL) << "can not read global config: " << G.move_as_error(); - } - auto G2 = td::json_decode(G.move_as_ok().as_slice()); - if (G2.is_error()) { - LOG(FATAL) << "can not parse global config: " << G2.move_as_error(); - } - auto gc_j = G2.move_as_ok(); - if (gc_j.type() != td::JsonValue::Type::Object) { - LOG(FATAL) << "can not parse global config: expected json object"; - } - - ton::ton_api::config_global gc; - auto rg = ton::ton_api::from_json(gc, gc_j.get_object()); - if (rg.is_error()) { - LOG(FATAL) << "can not interpret local config: " << rg.move_as_error(); - } - - if (gc.adnl_) { - auto it = gc.adnl_->static_nodes_.begin(); - while (it != gc.adnl_->static_nodes_.end()) { - auto R = ton::adnl_validate_full_id(std::move((*it)->id_)); - if (R.is_error()) { - LOG(FATAL) << "can not apply global config: " << R.move_as_error(); - } - auto R2 = ton::adnl_validate_addr_list(std::move((*it)->addr_list_)); - if (R2.is_error()) { - LOG(FATAL) << "can not apply global config: " << R2.move_as_error(); - } - td::actor::send_closure(peer_table_, &ton::adnl::AdnlPeerTable::add_peer, R.move_as_ok(), R2.move_as_ok()); - it++; - } - } - - if (!gc.dht_) { - LOG(FATAL) << "global config does not contain dht section"; - } - if (lc.dht_.size() != 1) { - LOG(FATAL) << "local config must contain exactly one dht section"; - } - - auto R = ton::DhtNode::create_from_json(std::move(gc.dht_), std::move(lc.dht_[0]), peer_table_.get()); - if (R.is_error()) { - LOG(FATAL) << "fail creating dht node: " << R.move_as_error(); - } - - dht_node_ = R.move_as_ok(); - } - /* - void set_host(td::IPAddress ip, std::string host) { - ip_ = ip.get_ipv4(); - host_ = host; - } - void send_pings_to(td::UInt256 id) { - std::cout << "send pings to " << id << "\n"; - ping_ids_.push_back(id); - } - void add_local_id(ton::tl_object_ptr pk_) { - auto pub_ = ton::get_public_key(pk_); - local_id_ = ton::adnl_short_id(pub_); - std::cout << "local_id = '" << local_id_ << "'\n"; - auto x = ton::create_tl_object(ip_, port_); - auto v = std::vector>(); - v.push_back(ton::move_tl_object_as(x)); - auto y = - ton::create_tl_object(std::move(v), static_cast(td::Time::now())); - - LOG(INFO) << "local_addr_list: " << ton::ton_api::to_string(y); - td::actor::send_closure(peer_table_, &ton::adnl::AdnlPeerTable::add_id, ton::clone_tl_object(pk_), - ton::clone_tl_object(y)); - td::actor::send_closure(peer_table_, &ton::adnl::AdnlPeerTable::subscribe_custom, local_id_, "TEST", make_callback()); - local_id_set_ = true; - - dht_node_ = ton::DhtNode::create(std::move(pk_), peer_table_.get()); - td::actor::send_closure(dht_node_, &ton::DhtNode::update_addr_list, std::move(y)); - } - - void add_static_dht_node(ton::tl_object_ptr id, - ton::tl_object_ptr addr_list, - td::BufferSlice signature) { - auto Id = ton::adnl_short_id(id); - td::actor::send_closure( - dht_node_, &ton::DhtNode::add_full_node, Id, - ton::create_tl_object(std::move(id), std::move(addr_list), signature.as_slice().str())); - } - - void add_foreign(ton::tl_object_ptr id, - ton::tl_object_ptr addr_list) { - std::cout << ton::adnl_short_id(id) << "\n"; - td::actor::send_closure(peer_table_, &ton::adnl::AdnlPeerTable::add_peer, std::move(id), std::move(addr_list)); - } - - void alarm() override { - std::cout << "alarm\n"; - if (local_id_set_) { - for (auto it = ping_ids_.begin(); it != ping_ids_.end(); it++) { - auto P = td::PromiseCreator::lambda([](td::Result> result) { - if (result.is_error()) { - std::cout << "received error " << result.move_as_error().to_string() << "\n"; - } else { - auto message = result.move_as_ok(); - std::cout << "received answer to query\n"; - } - }); - td::actor::send_closure(peer_table_, &ton::adnl::AdnlPeerTable::send_query, local_id_, *it, std::move(P), - td::Timestamp::in(5), - ton::move_tl_object_as( - ton::create_tl_object("TEST"))); - } - } - } - */ -}; - -td::Result get_uint256(std::string str) { - if (str.size() != 64) { - return td::Status::Error("uint256 must have 64 bytes"); - } - td::UInt256 res; - for (size_t i = 0; i < 32; i++) { - res.raw[i] = static_cast(td::hex_to_int(str[2 * i]) * 16 + td::hex_to_int(str[2 * i + 1])); - } - return res; -} - -int main(int argc, char *argv[]) { - td::actor::ActorOwn x; - - td::OptionsParser p; - p.set_description("test basic adnl functionality"); - p.add_option('h', "help", "prints_help", [&]() { - char b[10240]; - td::StringBuilder sb({b, 10000}); - sb << p; - std::cout << sb.as_cslice().c_str(); - std::exit(2); - return td::Status::OK(); - }); - p.add_option('p', "port", "sets udp port", [&](td::Slice port) { - td::actor::send_closure(x, &adnl::AdnlNode::listen_udp, static_cast(std::stoi(port.str()))); - return td::Status::OK(); - }); - p.add_option('C', "global-config", "file to read global config", [&](td::Slice fname) { - td::actor::send_closure(x, &adnl::AdnlNode::set_global_config, fname.str()); - return td::Status::OK(); - }); - p.add_option('c', "local-config", "file to read local config", [&](td::Slice fname) { - td::actor::send_closure(x, &adnl::AdnlNode::set_local_config, fname.str()); - return td::Status::OK(); - }); - - td::actor::Scheduler scheduler({2}); - - scheduler.run_in_context([&] { - x = td::actor::create_actor(td::actor::ActorInfoCreator::Options().with_name("A").with_poll()); - }); - - scheduler.run_in_context([&] { p.run(argc, argv).ensure(); }); - scheduler.run_in_context([&] { td::actor::send_closure(x, &adnl::AdnlNode::run); }); - - scheduler.run(); - - return 0; -} diff --git a/dht/utils/dht-ping-servers.cpp b/dht/utils/dht-ping-servers.cpp new file mode 100644 index 00000000..0feafb9a --- /dev/null +++ b/dht/utils/dht-ping-servers.cpp @@ -0,0 +1,214 @@ +/* + This file is part of TON Blockchain source code. + + TON Blockchain is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + TON Blockchain 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TON Blockchain. If not, see . + + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement + from all source files in the program, then also delete it here. + + Copyright 2017-2020 Telegram Systems LLP +*/ +#include "adnl/adnl-network-manager.h" +#include "adnl/adnl.h" +#include "adnl/utils.hpp" +#include "keys/encryptor.h" +#include "td/utils/Time.h" +#include "td/utils/format.h" +#include "td/utils/OptionParser.h" +#include "td/utils/filesystem.h" +#include "dht/dht.hpp" +#include "auto/tl/ton_api_json.h" +#include "common/delay.h" +#include "td/utils/Random.h" +#include "terminal/terminal.h" + +#include + +class AdnlNode : public td::actor::Actor { + private: + td::actor::ActorOwn network_manager_; + td::actor::ActorOwn adnl_; + td::actor::ActorOwn keyring_; + ton::adnl::AdnlNodeIdShort local_id_; + + std::string host_ = "127.0.0.1"; + td::uint16 port_ = 2380; + + std::string global_config_ = "ton-global.config"; + + struct NodeInfo { + ton::adnl::AdnlNodeIdShort id; + td::uint32 sent = 0, received = 0; + double sum_time = 0.0; + explicit NodeInfo(ton::adnl::AdnlNodeIdShort id) : id(id) { + } + }; + std::vector nodes_; + + td::uint32 pings_remaining_ = 4; + td::uint32 pending_ = 1; + + public: + void set_global_config(std::string str) { + global_config_ = str; + } + void listen_udp(td::uint16 port) { + port_ = port; + } + + AdnlNode() { + } + + void run() { + network_manager_ = ton::adnl::AdnlNetworkManager::create(port_); + keyring_ = ton::keyring::Keyring::create(""); + adnl_ = ton::adnl::Adnl::create("", keyring_.get()); + td::actor::send_closure(adnl_, &ton::adnl::Adnl::register_network_manager, network_manager_.get()); + + td::IPAddress addr; + addr.init_host_port(host_, port_).ensure(); + ton::adnl::AdnlCategoryMask mask; + mask[0] = true; + td::actor::send_closure(network_manager_, &ton::adnl::AdnlNetworkManager::add_self_addr, addr, mask, 0); + auto pk = ton::privkeys::Ed25519::random(); + td::actor::send_closure(keyring_, &ton::keyring::Keyring::add_key, pk, true, [](td::Result) {}); + ton::adnl::AdnlNodeIdFull local_id_full(pk.pub()); + ton::adnl::AdnlAddressList addr_list; + addr_list.set_version(static_cast(td::Clocks::system())); + addr_list.set_reinit_date(ton::adnl::Adnl::adnl_start_time()); + td::actor::send_closure(adnl_, &ton::adnl::Adnl::add_id, local_id_full, std::move(addr_list), (td::uint8)0); + local_id_ = local_id_full.compute_short_id(); + + auto r_dht = get_dht_config(); + if (r_dht.is_error()) { + LOG(FATAL) << "Cannot get dht config: " << r_dht.move_as_error(); + } + auto dht = r_dht.move_as_ok(); + ton::adnl::AdnlNodesList static_nodes; + for (const auto &node : dht->nodes().list()) { + LOG(INFO) << "Node #" << nodes_.size() << " : " << node.adnl_id().compute_short_id(); + nodes_.emplace_back(node.adnl_id().compute_short_id()); + static_nodes.push(ton::adnl::AdnlNode(node.adnl_id(), node.addr_list())); + } + td::actor::send_closure(adnl_, &ton::adnl::Adnl::add_static_nodes_from_config, std::move(static_nodes)); + + ton::delay_action([SelfId = actor_id(this)]() { td::actor::send_closure(SelfId, &AdnlNode::send_pings); }, + td::Timestamp::in(1.0)); + } + + td::Result> get_dht_config() { + TRY_RESULT_PREFIX(conf_data, td::read_file(global_config_), "failed to read: "); + TRY_RESULT_PREFIX(conf_json, td::json_decode(conf_data.as_slice()), "failed to parse json: "); + ton::ton_api::config_global conf; + TRY_STATUS_PREFIX(ton::ton_api::from_json(conf, conf_json.get_object()), "json does not fit TL scheme: "); + if (!conf.dht_) { + return td::Status::Error(ton::ErrorCode::error, "does not contain [dht] section"); + } + TRY_RESULT_PREFIX(dht, ton::dht::Dht::create_global_config(std::move(conf.dht_)), "bad [dht] section: "); + return std::move(dht); + } + + void send_pings() { + CHECK(pings_remaining_); + --pings_remaining_; + for (size_t i = 0; i < nodes_.size(); ++i) { + auto id = nodes_[i].id; + LOG(INFO) << "Sending ping to " << id; + ++pending_; + td::actor::send_closure( + adnl_, &ton::adnl::Adnl::send_query, local_id_, id, "ping", + [SelfId = actor_id(this), i, timer = td::Timer()](td::Result R) { + td::actor::send_closure(SelfId, &AdnlNode::on_pong, i, timer.elapsed(), R.is_ok()); + }, td::Timestamp::in(5.0), + ton::create_serialize_tl_object(td::Random::fast_uint64())); + } + + if (pings_remaining_ == 0) { + --pending_; + try_finish(); + } else { + ton::delay_action([SelfId = actor_id(this)]() { td::actor::send_closure(SelfId, &AdnlNode::send_pings); }, + td::Timestamp::in(1.0)); + } + } + + void on_pong(size_t i, double time, bool success) { + auto &node = nodes_[i]; + ++node.sent; + if (success) { + ++node.received; + node.sum_time += time; + LOG(INFO) << "Pong from " << node.id << " in " << time << "s"; + } else { + LOG(INFO) << "Pong from " << node.id << " : timeout"; + } + --pending_; + try_finish(); + } + + void try_finish() { + if (pending_) { + return; + } + td::TerminalIO::out() << "Pinged " << nodes_.size() << " nodes:\n"; + for (const auto& node : nodes_) { + td::TerminalIO::out() << node.id << " : " << node.received << "/" << node.sent; + if (node.received > 0) { + td::TerminalIO::out() << " (avg. time = " << node.sum_time / node.received << ")"; + } + td::TerminalIO::out() << "\n"; + } + std::exit(0); + } +}; + +int main(int argc, char *argv[]) { + td::actor::ActorOwn x; + + td::OptionParser p; + p.set_description("ping dht servers from config"); + p.add_option('h', "help", "print help", [&]() { + char b[10240]; + td::StringBuilder sb(td::MutableSlice{b, 10000}); + sb << p; + std::cout << sb.as_cslice().c_str(); + std::exit(2); + }); + p.add_option('p', "port", "set udp port", [&](td::Slice port) { + td::actor::send_closure(x, &AdnlNode::listen_udp, static_cast(std::stoi(port.str()))); + }); + p.add_option('C', "global-config", "file to read global config from", + [&](td::Slice fname) { td::actor::send_closure(x, &AdnlNode::set_global_config, fname.str()); }); + p.add_option('v', "verbosity", "set verbosity", [&](td::Slice arg) { + int v = VERBOSITY_NAME(FATAL) + (td::to_integer(arg)); + SET_VERBOSITY_LEVEL(v); + }); + + td::actor::Scheduler scheduler({2}); + + scheduler.run_in_context([&] { x = td::actor::create_actor("AdnlNode"); }); + + scheduler.run_in_context([&] { p.run(argc, argv).ensure(); }); + scheduler.run_in_context([&] { td::actor::send_closure(x, &AdnlNode::run); }); + + scheduler.run(); + + return 0; +} diff --git a/dht/utils/dht-resolve.cpp b/dht/utils/dht-resolve.cpp new file mode 100644 index 00000000..5ab98d01 --- /dev/null +++ b/dht/utils/dht-resolve.cpp @@ -0,0 +1,219 @@ +/* + This file is part of TON Blockchain source code. + + TON Blockchain is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + TON Blockchain 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TON Blockchain. If not, see . + + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement + from all source files in the program, then also delete it here. + + Copyright 2017-2020 Telegram Systems LLP +*/ +#include "adnl/adnl-network-manager.h" +#include "adnl/adnl.h" +#include "adnl/utils.hpp" +#include "keys/encryptor.h" +#include "td/utils/Time.h" +#include "td/utils/format.h" +#include "td/utils/OptionParser.h" +#include "td/utils/filesystem.h" +#include "dht/dht.hpp" +#include "auto/tl/ton_api_json.h" +#include "common/delay.h" +#include "td/utils/Random.h" +#include "terminal/terminal.h" +#include "common/util.h" + +#include + +class Resolver : public td::actor::Actor { + private: + td::actor::ActorOwn network_manager_; + td::actor::ActorOwn adnl_; + td::actor::ActorOwn keyring_; + ton::adnl::AdnlNodeIdShort local_id_; + td::actor::ActorOwn dht_; + + std::string global_config_; + int server_idx_; + + std::string host_ = "127.0.0.1"; + td::uint16 port_; + ton::dht::DhtKey key_; + double timeout_; + + public: + Resolver(std::string global_config, int server_idx, td::uint16 port, ton::dht::DhtKey key, double timeout) + : global_config_(global_config), server_idx_(server_idx), port_(port), key_(std::move(key)), timeout_(timeout) { + } + + void run() { + network_manager_ = ton::adnl::AdnlNetworkManager::create(port_); + keyring_ = ton::keyring::Keyring::create(""); + adnl_ = ton::adnl::Adnl::create("", keyring_.get()); + td::actor::send_closure(adnl_, &ton::adnl::Adnl::register_network_manager, network_manager_.get()); + + td::IPAddress addr; + addr.init_host_port(host_, port_).ensure(); + ton::adnl::AdnlCategoryMask mask; + mask[0] = true; + td::actor::send_closure(network_manager_, &ton::adnl::AdnlNetworkManager::add_self_addr, addr, mask, 0); + auto pk = ton::privkeys::Ed25519::random(); + td::actor::send_closure(keyring_, &ton::keyring::Keyring::add_key, pk, true, [](td::Result) {}); + ton::adnl::AdnlNodeIdFull local_id_full(pk.pub()); + ton::adnl::AdnlAddressList addr_list; + addr_list.set_version(static_cast(td::Clocks::system())); + addr_list.set_reinit_date(ton::adnl::Adnl::adnl_start_time()); + td::actor::send_closure(adnl_, &ton::adnl::Adnl::add_id, local_id_full, std::move(addr_list), (td::uint8)0); + local_id_ = local_id_full.compute_short_id(); + + auto dht_config = get_dht_config(); + if (dht_config.is_error()) { + LOG(FATAL) << "Failed to load dht config: " << dht_config.move_as_error(); + } + auto D = ton::dht::Dht::create_client(local_id_, "", dht_config.move_as_ok(), keyring_.get(), adnl_.get()); + if (D.is_error()) { + LOG(FATAL) << "Failed to init dht client: " << D.move_as_error(); + } + dht_ = D.move_as_ok(); + LOG(INFO) << "Get value " << key_.public_key_hash() << " " << key_.name() << " " << key_.idx(); + + send_query(); + alarm_timestamp() = td::Timestamp::in(timeout_); + } + + void send_query() { + td::actor::send_closure(dht_, &ton::dht::Dht::get_value, key_, + [SelfId = actor_id(this)](td::Result R) { + td::actor::send_closure(SelfId, &Resolver::got_result, std::move(R)); + }); + } + + void got_result(td::Result R) { + if (R.is_error()) { + LOG(WARNING) << "Failed to get value, retrying: " << R.move_as_error(); + ton::delay_action([SelfId = actor_id(this)]() { td::actor::send_closure(SelfId, &Resolver::send_query); }, + td::Timestamp::in(0.25)); + return; + } + auto r = R.move_as_ok(); + LOG(INFO) << "Got result"; + td::TerminalIO::out() << "KEY: " << td::base64_encode(ton::serialize_tl_object(r.key().public_key().tl(), true)) + << "\n"; + td::TerminalIO::out() << "VALUE: " << td::base64_encode(r.value().as_slice()) << "\n"; + std::exit(0); + } + + void alarm() override { + LOG(FATAL) << "Failed to get value: timeout"; + } + + td::Result> get_dht_config() { + TRY_RESULT_PREFIX(conf_data, td::read_file(global_config_), "failed to read: "); + TRY_RESULT_PREFIX(conf_json, td::json_decode(conf_data.as_slice()), "failed to parse json: "); + ton::ton_api::config_global conf; + TRY_STATUS_PREFIX(ton::ton_api::from_json(conf, conf_json.get_object()), "json does not fit TL scheme: "); + if (!conf.dht_) { + return td::Status::Error(ton::ErrorCode::error, "does not contain [dht] section"); + } + ton::ton_api::dht_nodes* static_nodes = nullptr; + ton::ton_api::downcast_call(*conf.dht_, [&](auto &f) { static_nodes = f.static_nodes_.get(); }); + auto &nodes = static_nodes->nodes_; + if (server_idx_ >= 0) { + CHECK(server_idx_ < (int)nodes.size()); + LOG(INFO) << "Using server #" << server_idx_; + std::swap(nodes[0], nodes[server_idx_]); + nodes.resize(1); + } else { + LOG(INFO) << "Using all " << nodes.size() << " servers"; + } + TRY_RESULT_PREFIX(dht, ton::dht::Dht::create_global_config(std::move(conf.dht_)), "bad [dht] section: "); + return std::move(dht); + } +}; + +td::Result parse_bits256(td::Slice s) { + td::BufferSlice str = td::base64_decode(s, true); + if (str.size() != 32) { + return td::Status::Error("Invalid bits256"); + } + return td::Bits256(td::BitPtr((unsigned char *)str.data())); +} + +int main(int argc, char *argv[]) { + td::actor::ActorOwn x; + + td::optional global_config; + int server_idx = -1; + td::uint16 port = 2380; + td::optional key_id; + td::optional key_name; + td::uint32 key_idx = 0; + double timeout = 5.0; + + td::OptionParser p; + p.set_description("find value in dht by the given key (key-id, key-name, ket-idx)"); + p.add_option('h', "help", "print help", [&]() { + char b[10240]; + td::StringBuilder sb(td::MutableSlice{b, 10000}); + sb << p; + std::cout << sb.as_cslice().c_str(); + std::exit(2); + }); + p.add_option('C', "global-config", "global config", [&](td::Slice arg) { global_config = arg.str(); }); + p.add_checked_option('s', "server-idx", "index of dht server from global config (default: all)", [&](td::Slice arg) { + TRY_RESULT_ASSIGN(server_idx, td::to_integer_safe(arg)); + return td::Status::OK(); + }); + p.add_checked_option('p', "port", "set udp port", [&](td::Slice arg) { + TRY_RESULT_ASSIGN(port, td::to_integer_safe(arg)); + return td::Status::OK(); + }); + p.add_option('v', "verbosity", "set verbosity", [&](td::Slice arg) { + int v = VERBOSITY_NAME(FATAL) + (td::to_integer(arg)); + SET_VERBOSITY_LEVEL(v); + }); + p.add_checked_option('k', "key-id", "set key id (256-bit, base64)", [&](td::Slice arg) { + TRY_RESULT_ASSIGN(key_id, parse_bits256(arg)); + return td::Status::OK(); + }); + p.add_option('n', "key-name", "set key name", [&](td::Slice arg) { key_name = arg.str(); }); + p.add_checked_option('i', "key-idx", "set key idx (default: 0)", [&](td::Slice arg) { + TRY_RESULT_ASSIGN(key_idx, td::to_integer_safe(arg)); + return td::Status::OK(); + }); + p.add_option('t', "timeout", "set timeout (default: 5s)", [&](td::Slice arg) { timeout = td::to_double(arg); }); + + td::actor::Scheduler scheduler({2}); + + scheduler.run_in_context([&] { p.run(argc, argv).ensure(); }); + scheduler.run_in_context([&] { + LOG_IF(FATAL, !global_config) << "global config is not set"; + LOG_IF(FATAL, !key_id) << "key-id is not set"; + LOG_IF(FATAL, !key_name) << "key-name is not set"; + x = td::actor::create_actor( + "Resolver", global_config.value(), server_idx, port, + ton::dht::DhtKey{ton::PublicKeyHash(key_id.value()), key_name.value(), key_idx}, timeout); + }); + scheduler.run_in_context([&] { td::actor::send_closure(x, &Resolver::run); }); + + scheduler.run(); + + return 0; +} diff --git a/doc/ConfigParam-HOWTO b/doc/ConfigParam-HOWTO new file mode 100644 index 00000000..639014bb --- /dev/null +++ b/doc/ConfigParam-HOWTO @@ -0,0 +1,298 @@ +The aim of this document is to provide basic explanation of configuration parameters of TON Blockchain, and to give step-by-step instructions for changing these parameters by a consensus of a majority of validators. We assume that the reader is already familiar with Fift and the Lite Client as explained in LiteClient-HOWTO, and with FullNode-HOWTO and Validator-HOWTO in the sections where validators' voting for the configuration proposals is described. + +1. Configuration parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The *configuration parameters* are certain values that affect the behavior of validators and/or fundamental smart contracts of TON Blockchain. The current values of all configuration parameters are stored as a special part of the masterchain state, and are extracted from the current masterchain state when needed. Therefore, it makes sense to speak of the values of the configuration parameters with respect to a certain masterchain block. Each shardchain block contains a reference to the latest known masterchain block; the values from the corresponding masterchain state are assumed to be active for this shardchain block, and are used during its generation and validation. For masterchain blocks, the state of the previous masterchain block is used to extract the active configuration parameters. Therefore, even if one tries to change some configuration parameters inside a masterchain block, the changes will become active only for the next masterchain block. + +Each configuration parameter is identified by a signed 32-bit integer index, called *configuration parameter index* or simply *index*. The value of a configuration parameter always is a Cell. Some configuration parameters may be missing; then it is sometimes assumed that the value of this parameter is Null. There also is a list of *mandatory* configuration parameters that must be always present; this list is stored in configuration parameter #10. + +All configuration parameters are combined into a *configuration dictionary* - a Hashmap with signed 32-bit keys (configuration parameter indices) and values consisting of exactly one cell reference. In other words, a configuration dictionary is a value of TL-B type (HashmapE 32 ^Cell). In fact, the collection of all configuration parameters is stored in the masterchain state as a value of TL-B type `ConfigParams`: + + _ config_addr:bits256 config:^(Hashmap 32 ^Cell) = ConfigParams; + +We see that, apart from the configuration dictionary, `ConfigParams` contains `config_addr` -- 256-bit address of the configuration smart contract in the masterchain. More details on the configuration smart contract will be provided later. + +The configuration dictionary containing the active values of all configuration parameters is available via special TVM register *c7* to all smart contracts when their code is executed in a transaction. More precisely, when a smart contract is executed, *c7* is initialized by a Tuple, the only element of which is a Tuple with several "context" values useful for the execution of the smart contract, such as the current Unix time (as registered in the block header). The tenth entry of this Tuple (i.e., the one with zero-based index 9) contains a Cell representing the configuration dictionary. Therefore, it can be accessed by means of TVM instructions "PUSH c7; FIRST; INDEX 9", or by equivalent instruction "CONFIGROOT". In fact, special TVM instructions "CONFIGPARAM" and "CONFIGOPTPARAM" combine the previous actions with a dictionary lookup, returning any configuration parameter by its index. We refer to the TVM documentation for more details on these instructions. What is relevant here is that all configuration parameters are easily accessible from all smart contracts (masterchain or shardchain), and smart contracts may inspect them and use them to perform specific checks. For instance, a smart contract might extract workchain data storage prices from a configuration parameter to compute the price for storing a chunk of user-provided data. + +The values of configuration parameters are not arbitrary. In fact, if the configuration parameter index *i* is non-negative, then the value of this parameter must be a valid value of TL-B type (ConfigParam i). This restriction is enforced by the validators, which will not accept changes to configuration parameters with non-negative indices unless they are valid values of the corresponding TL-B type. + +Therefore, the structure of such parameters is determined in source file `crypto/block/block.tlb`, where (ConfigParam i) are defined for different values of *i*. For instance, + + _ config_addr:bits256 = ConfigParam 0; + _ elector_addr:bits256 = ConfigParam 1; + _ dns_root_addr:bits256 = ConfigParam 4; // root TON DNS resolver + + capabilities#c4 version:uint32 capabilities:uint64 = GlobalVersion; + _ GlobalVersion = ConfigParam 8; // all zero if absent + +We see that configuration parameter #8 contains a Cell with no references and exactly 104 data bits. The first four bits must be 11000100, then 32 bits with the currently enabled "global version" are stored, and 64-bit integer with flags corresponding to currently enabled capabilities follow. A more detailed description of all configuration parameters will be provided in an appendix to the TON Blockchain documentation; for now, one can inspect the TL-B scheme in `crypto/block/block.tlb` and check how different parameters are used in the validator sources. + +In contrast with configuration parameters with non-negative indices, configuration parameters with negative indices can contain arbitrary values. At least, no restrictions on their values are enforced by the validators. Therefore, they can be used to store important information (such as the Unixtime when certain smart contracts must start operating) that is not crucial for the block generation, but is used by some of the fundamental smart contracts. + +2. Changing configuration parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We have already explained that the current values of configuration parameters are stored in a special portion of the masterchain state. How do they ever get changed? + +In fact, there is a special smart contract residing in the masterchain, called the *configuration smart contract*. Its address is determined by the `config_addr` field in `ConfigParams`, which we have described before. The first cell reference in its data must contain an up-to-date copy of all configuration parameters. When a new masterchain block is generated, the configuration smart contract is looked up by its address `config_addr`, and the new configuration dictionary is extracted from the first cell reference of its data. After some validity checks (such as verifying that any value with non-negative 32-bit index *i* is indeed a valid value of TL-B type (ConfigParam i)) the validator copies this new configuration dictionary into the portion of masterchain containing ConfigParams. This is performed after all transactions have been created, so only the final version of the new configuration dictionary stored in the configuration smart contract is inspected. If the validity checks fail, then the "true" configuration dictionary is left unchanged. In this way the configuration smart contract cannot install invalid values of configuration parameters. If the new configuration dictionary coincides with the current configuration dictionary, then no checks are performed and no changes made. + +In this way, all changes in configuration parameters are performed by the configuration smart contract, and it is its code that determines the rules for changing configuration parameters. Currently, the configuration smart contract supports two modes for changing configuration parameters: + +1) By means of an external message signed by a specific private key, corresponding to a public key stored in the data of the configuration smart contract. This is the method employed in the public testnet and probably in the smaller private test networks, controlled by one entity, because it enables the operator to easily change the values of any configuration parameters. Note that this public key may be changed by a special external message signed by an old key, and that if it is changed to zero, then this mechanism is disabled. Therefore, one might use it for fine-tuning immediately after the launch, and then disable for good. +2) By means of creating "configuration proposals" that are subsequently voted for or against by validators. Typically, a configuration proposal has to collect votes from more than 3/4 of all validators (by weight), and not only in one round, but in several rounds (i.e., several consecutive sets of validators have to confirm the proposed parameter change). This is the distributed governance mechanism to be used by TON Blockchain mainnet. + +We would like to describe the second way of changing configuration parameters in more detail. + +3. Creating configuration proposals +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A new *configuration proposal* contains the following data: +- the index of the configuration parameter to be changed +- the new value of the configuration parameter (or Null, if it is to be deleted) +- the expiration Unixtime of the proposal +- a flag indicating whether the proposal is *critical* or not +- an optional *old value hash* with the cell hash of the current value (the proposal can be activated only if the current value has indicated hash) + +Anybody with a wallet in the masterchain can create a new configuration proposal, provided he pays an adequate fee. However, only validators can vote for or against existing configuration proposals. + +Note that there are *critical* and *ordinary* configuration proposals. A critical configuration proposal can change any configuration parameter, including one of the so-called critical ones (the list of critical configuration parameters is stored in configuration parameter #10, which is itself critical). However, creating critical configuration proposals is more expensive, and they usually need to collect more validator votes in more rounds (the precise voting requirements for ordinary and critical configuration proposals are stored in critical configuration parameter #11). On the other hand, ordinary configuration proposals are cheaper, but they cannot change the critical configuration parameters. + +In order to create a new configuration proposal, one first has to generate a BoC (bag-of-cells) file containing the proposed new value. The exact way of doing this depends on the configuration parameter being changed. For instance, if we want to create parameter -239 containing UTF-8 string "TEST" (i.e., 0x54455354), we could create `config-param-239.boc` as follows: invoke Fift and then type + + 2 boc+>B "config-param-239.boc" B>file + bye + +As a result, 21-byte file `config-param-239.boc` will be created, containing the serialization of the required value. + +For more sophisticated cases, and especially for configuration parameters with non-negative indices this straightforward approach is not easily applicable. We recommend using `create-state` (available as `crypto/create-state` in the build directory) instead of `fift`, and copying and editing suitable portions from source files `crypto/smartcont/gen-zerostate.fif` and `crypto/smartcont/CreateState.fif`, which are usually employed to create the zero state (corresponding to the "genesis block" of other blockchain architectures) of the TON Blockchain. + +Consider, for instance, configuration parameter #8, which contains the currently-enabled global blockchain version and capabilities: + + capabilities#c4 version:uint32 capabilities:uint64 = GlobalVersion; + _ GlobalVersion = ConfigParam 8; + +We can inspect its current value by running the lite-client and typing `getconfig 8`: + +> getconfig 8 +... +ConfigParam(8) = ( + (capabilities version:1 capabilities:6)) + +x{C4000000010000000000000006} + +Now suppose that we want to enable the capability represented by bit #3 (+8), which is capReportVersion (when enabled, this capability forces all collators to report their supported version and capabilities in the block headers of the blocks they generate). Therefore, we want to have version=1 and capabilities=14. In this example, we still can guess the correct serialization and create the BoC file directly by typing in Fift + + x{C400000001000000000000000E} s>c 2 boc+>B "config-param8.boc" B>file + +(A 30-byte file `config-param8.boc` containing the desired value is created as a result.) + +However, in more complicated cases this might not be an option, so let's do this example differently. Namely, we can inspect source files `crypto/smartcont/gen-zerostate.fif` and `crypto/smartcont/CreateState.fif` for relevant portions: + + // version capabilities -- + { 8 config! } : config.version! + 1 constant capIhr + 2 constant capCreateStats + 4 constant capBounceMsgBody + 8 constant capReportVersion + 16 constant capSplitMergeTransactions + +and + + // version capabilities + 1 capCreateStats capBounceMsgBody or capReportVersion or config.version! + +We see that "config.version!" without the last "8 config!" essentially does what we need, so we can create a temporary Fift script, say, `create-param8.fif`: + +================================================== +#!/usr/bin/fift -s +"TonUtil.fif" include + +1 constant capIhr +2 constant capCreateStats +4 constant capBounceMsgBody +8 constant capReportVersion +16 constant capSplitMergeTransactions +{ } : prepare-param8 + +// create new value for config param #8 +1 capCreateStats capBounceMsgBody or capReportVersion or prepare-param8 +// check the validity of this value +dup 8 is-valid-config? not abort"not a valid value for chosen configuration parameter" +// print +dup ."Serialized value = " B $1 tuck B>file +."(Saved into file " type .")" cr +================================================== + +Now if we run `fift -s create-param8.fif config-param8.boc`, or, even better, `crypto/create-state -s create-param8.fif config-param8.boc` (from the build directory), we see the following output: + + Serialized value = x{C400000001000000000000000E} + (Saved into file config-param8.boc) + +and we obtain 30-byte file `config-param8.boc` with the same content as before. + +Once we have a file with the desired value of the configuration parameter, we invoke script `create-config-proposal.fif` that can be found in directory `crypto/smartcont` of the source tree with suitable arguments. Again, we recommend using `create-state` (available as `crypto/create-state` from the build directory) instead of `fift`, because it is a special extended version of Fift that is able to do more blockchain-related validity checks: + +============================================ +$ crypto/create-state -s create-config-proposal.fif 8 config-param8.boc -x 1100000 + +Loading new value of configuration parameter 8 from file config-param8.boc +x{C400000001000000000000000E} + +Non-critical configuration proposal will expire at 1586779536 (in 1100000 seconds) +Query id is 6810441749056454664 +resulting internal message body: x{6E5650525E838CB0000000085E9455904_} + x{F300000008A_} + x{C400000001000000000000000E} + +B5EE9C7241010301002C0001216E5650525E838CB0000000085E9455904001010BF300000008A002001AC400000001000000000000000ECD441C3C +(a total of 104 data bits, 0 cell references -> 59 BoC data bytes) +(Saved to file config-msg-body.boc) +============================================ + +We have obtained the body of an internal message to be sent to the configuration smart contract with a suitable amount of grams from any (wallet) smartcontract residing in the masterchain. The address of the configuration smart contract may be obtained by typing `getconfig 0` in the lite-client: + +> getconfig 0 +ConfigParam(0) = ( config_addr:x5555555555555555555555555555555555555555555555555555555555555555) +x{5555555555555555555555555555555555555555555555555555555555555555} + +We see that the address of the configuration smart contract is -1:5555...5555. By running suitable get-methods of this smart contract, we can find out the required payment for creating this configuration proposal: + +==================================================== +> runmethod -1:5555555555555555555555555555555555555555555555555555555555555555 proposal_storage_price 0 1100000 104 0 +... +arguments: [ 0 1100000 104 0 75077 ] +result: [ 2340800000 ] +remote result (not to be trusted): [ 2340800000 ] +==================================================== + +The parameters to get-method `proposal_storage_price` are the critical flag (0 in this case), the time interval during which this proposal will be active (1.1 Megaseconds), the total amount of bits (104) and cell references (0) in the data. The latter two quantities can be seen in the output of `create-config-proposal.fif`. + +We see that one has to pay 2.3408 test Grams to create this proposal. It is better to add at least 1.5 test grams to the message to pay for the processing fees, so we are going to send 4 test Grams along with the request (all excess test Grams will be returned back). Now we use `wallet.fif` (or the corresponding Fift script for the wallet we are using) to create a transfer from our wallet to the configuration smart contract carrying 4 test Grams and the body from `config-msg-body.boc`. This usually looks like + +==================================================== +$ fift -s wallet.fif my-wallet -1:5555555555555555555555555555555555555555555555555555555555555555 31 4. -B config-msg-body.boc +... +Transferring GR$4. to account kf9VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQft = -1:5555555555555555555555555555555555555555555555555555555555555555 seqno=0x1c bounce=-1 +Body of transfer message is x{6E5650525E835154000000085E9293944_} + x{F300000008A_} + x{C400000001000000000000000E} + +signing message: x{0000001C03} + x{627FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA773594000000000000000000000000000006E5650525E835154000000085E9293944_} + x{F300000008A_} + x{C400000001000000000000000E} + +resulting external message: x{89FE000000000000000000000000000000000000000000000000000000000000000007F0BAA08B4161640FF1F5AA5A748E480AFD16871E0A089F0F017826CDC368C118653B6B0CEBF7D3FA610A798D66522AD0F756DAEECE37394617E876EFB64E9800000000E01C_} + x{627FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA773594000000000000000000000000000006E5650525E835154000000085E9293944_} + x{F300000008A_} + x{C400000001000000000000000E} + +B5EE9C724101040100CB0001CF89FE000000000000000000000000000000000000000000000000000000000000000007F0BAA08B4161640FF1F5AA5A748E480AFD16871E0A089F0F017826CDC368C118653B6B0CEBF7D3FA610A798D66522AD0F756DAEECE37394617E876EFB64E9800000000E01C010189627FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA773594000000000000000000000000000006E5650525E835154000000085E9293944002010BF300000008A003001AC400000001000000000000000EE1F80CD3 +(Saved to file wallet-query.boc) +==================================================== + +Now we send the external message `wallet-query.boc` to the blockchain with the aid of the lite-client: + + > sendfile wallet-query.boc + .... + external message status is 1 + +After waiting for some time, we can inspect the incoming messages of our wallet to check for response messages from the configuration smart contract, or, if we feel lucky, simply inspect the list of all active configuration proposals by means of method `list_proposals` of the configuration smart contract: + +==================================================== +> runmethod -1:5555555555555555555555555555555555555555555555555555555555555555 list_proposals +... +arguments: [ 107394 ] +result: [ ([64654898543692093106630260209820256598623953458404398631153796624848083036321 [1586779536 0 [8 C{FDCD887EAF7ACB51DA592348E322BBC0BD3F40F9A801CB6792EFF655A7F43BBC} -1] 112474791597373109254579258586921297140142226044620228506108869216416853782998 () 864691128455135209 3 0 0]]) ] +remote result (not to be trusted): [ ([64654898543692093106630260209820256598623953458404398631153796624848083036321 [1586779536 0 [8 C{FDCD887EAF7ACB51DA592348E322BBC0BD3F40F9A801CB6792EFF655A7F43BBC} -1] 112474791597373109254579258586921297140142226044620228506108869216416853782998 () 864691128455135209 3 0 0]]) ] +... caching cell FDCD887EAF7ACB51DA592348E322BBC0BD3F40F9A801CB6792EFF655A7F43BBC +==================================================== + +We see that the list of all active configuration proposals consists of exactly one entry, represented by pair + + [6465...6321 [1586779536 0 [8 C{FDCD...} -1] 1124...2998 () 8646...209 3 0 0]] + +Here the first number 6465..6321 is the unique identifier of the configuration proposal, equal to its 256-bit hash. The second component of this pair is a Tuple describing the status of this configuration proposal. The first component of this Tuple is the expiration Unixtime of the configuration proposal (1586779536). The second component (0) is the criticality flag. Next comes the configuration proposal proper, described by triple [8 C{FDCD...} -1], where 8 is the index of the configuration parameter to be modified, C{FDCD...} is the cell with the new value (represented by the hash of this cell), and -1 is the optional hash of the old value of this parameter (-1 means that this hash has not been specified). Next we see a large number 1124...2998 representing the identifier of the current validator set, then an empty list () representing the set of all currently active validators that have voted for this proposal so far, then *weight_remaining* equal to 8646...209 - a number that is positive if the proposal has not yet collected enough validator votes in this round, and negative otherwise. Then we see three numbers 3 0 0. These numbers are *rounds_remaining* (this proposal will survive at most three rounds, i.e., changes of the current validator set), *wins* (the count of rounds where the proposal collected votes of more than 3/4 of all validators by weight) and *losses* (the count of rounds where the proposal failed to collect 3/4 of all validator votes). + +We can inspect the proposed value for configuration parameter #8 by asking the lite-client to expand cell C{FDCD...} using its hash FDCD... or a sufficiently long prefix of this hash to uniquely identify the cell in question: + +> dumpcell FDC +C{FDCD887EAF7ACB51DA592348E322BBC0BD3F40F9A801CB6792EFF655A7F43BBC} = + x{C400000001000000000000000E} + +We see that the value is x{C400000001000000000000000E}, which is indeed the value we have embedded into our configuration proposal. We can even ask the lite-client to display this Cell as a value of TL-B type (ConfigParam 8): + +> dumpcellas ConfigParam8 FDC +dumping cells as values of TLB type (ConfigParam 8) +C{FDCD887EAF7ACB51DA592348E322BBC0BD3F40F9A801CB6792EFF655A7F43BBC} = + x{C400000001000000000000000E} +( + (capabilities version:1 capabilities:14)) + +This is especially useful when we consider configuration proposals created by other people. + +Note that the configuration proposal is henceforth identified by its 256-bit hash -- the huge decimal number 6465...6321. We can inspect the current status of a specific configuration proposal by running get-method `get_proposal` with the only argument equal to the identifier of the configuration proposal: + +> runmethod -1:5555555555555555555555555555555555555555555555555555555555555555 get_proposal 64654898543692093106630260209820256598623953458404398631153796624848083036321 +... +arguments: [ 64654898543692093106630260209820256598623953458404398631153796624848083036321 94347 ] +result: [ [1586779536 0 [8 C{FDCD887EAF7ACB51DA592348E322BBC0BD3F40F9A801CB6792EFF655A7F43BBC} -1] 112474791597373109254579258586921297140142226044620228506108869216416853782998 () 864691128455135209 3 0 0] ] + +We obtain essentially the same result as before, but for only one configuration proposal, and without the identifier of the configuration proposal at the beginning. + +4. Voting for configuration proposals +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once a configuration proposal is created, it is supposed to collect votes from more than 3/4 of all current validators (by weight, i.e., by stake) in the current and maybe in several subsequent rounds (elected validator sets). In this way the decision to change a configuration parameters must be approved by a significant majority not only of the current set of validators, but also of several subsequent sets of validators. + +Voting for a configuration proposal is possible only for current validators, listed (with their permanent public keys) in configuration parameter #34. The process is approximately the following: + +- The operator of a validator looks up *val-idx*, the (0-based) index of his validator in the current set of validators as stored in configuration parameter #34. +- The operator invokes special Fift script `config-proposal-vote-req.fif`, found in directory `crypto/smartcont' of the source tree, indicating *val-idx* and *config-proposal-id* as its arguments: + + $ fift -s config-proposal-vote-req.fif -i 0 64654898543692093106630260209820256598623953458404398631153796624848083036321 + Creating a request to vote for configuration proposal 0x8ef1603180dad5b599fa854806991a7aa9f280dbdb81d67ce1bedff9d66128a1 on behalf of validator with index 0 + 566F744500008EF1603180DAD5B599FA854806991A7AA9F280DBDB81D67CE1BEDFF9D66128A1 + Vm90RQAAjvFgMYDa1bWZ-oVIBpkaeqnygNvbgdZ84b7f-dZhKKE= + Saved to file validator-to-sign.req + +- After that, the vote request has to be signed by the current validator private key, using `sign 566F744...28A1` in `validator-engine-console` connected to the validator. This process is similar to that described in Validator-HOWTO for participating in validator elections, but this time the currently active key has to be used. +- Next, another script `config-proposal-signed.fif` has to be invoked. It has similar arguments to `config-proposal-req.fif`, but it expects two extra arguments: the base64 representation of the public key used to sign the vote request, and the base64 representation of the signature itself. Again, this is quite similar to the process described in Validator-HOWTO. +- In this way file `vote-msg-body.boc` containing the body of an internal message carrying a signed vote for this configuration proposal is created. +- After that, `vote-msg-body.boc` has to be carried in an internal message from any smart contract residing in the masterchain (typically the controlling smart contract of the validator will be used) along with a small amount of test Grams for processing (typically 1.5 Grams should suffice). This is again completely similar to the procedure employed during validator elections. This is typically achieved by means of running + + $ fift -s wallet.fif my_wallet_id -1:5555555555555555555555555555555555555555555555555555555555555555 1 1.5 -B vote-msg-body.boc + +(if a simple wallet is used to control the validator) and then sending the resulting file `wallet-query.boc` from the lite-client: + + > sendfile wallet-query.boc + +You can monitor answer messages from the configuration smart contract to the controlling smart contract to learn the status of your voting queries. Alternatively, you can inspect the status of the configuration proposal by means of get-method `show_proposal` of the configuration smart contract: + +> runmethod -1:5555555555555555555555555555555555555555555555555555555555555555 get_proposal 64654898543692093106630260209820256598623953458404398631153796624848083036321 +... +arguments: [ 64654898543692093106630260209820256598623953458404398631153796624848083036321 94347 ] +result: [ [1586779536 0 [8 C{FDCD887EAF7ACB51DA592348E322BBC0BD3F40F9A801CB6792EFF655A7F43BBC} -1] 112474791597373109254579258586921297140142226044620228506108869216416853782998 (0) 864691128455135209 3 0 0] ] + +This time the list of indices of validators that voted for this configuration proposal should be non-empty, and it should contain the index of your validator. In this example, this list is (0), meaning that only the validator with index 0 in configuration parameter #34 has voted. If the list becomes large enough, the last-but-one integer (the first zero in "3 0 0") in the proposal status will increase by one, indicating a new win by this proposal. If the number of wins becomes greater than or equal to the value indicated in configuration parameter #11, then the configuration proposal is automatically accepted and the proposed changes become effective immediately. On the other hand, when the validator set changes, then the list of validators that have already voted becomes empty, the value of *rounds_remaining* (three in "3 0 0") is decreased by one, and if it becomes negative, the configuration proposal is destroyed. If it is not destroyed, and if it did not win in this round, then the number of losses (the second zero in "3 0 0") is increased. If it becomes larger than a value specified in configuration parameter #11, then the configuration proposal is discarded. In this way all validators that have abstained from voting in a round have implicitly voted against. + +5. An automated way for voting for configuration proposals +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Similarly to the automation provided by command `createelectionbid` of `validator-engine-console` for participating in validator elections, `validator-engine` and `validator-engine-console` offer an automated way of performing most of the steps explained in the previous section, producing a `vote-msg-body.boc` ready to be used with the controlling wallet. In order to use this method, you must install Fift scripts `config-proposal-vote-req.fif` and `config-proposal-vote-signed.fif` into the same directory that the validator-engine uses to look up `validator-elect-req.fif` and `validator-elect-signed.fif` as explained in Section 5 of Validator-HOWTO. After that, you simply run + + createproposalvote 64654898543692093106630260209820256598623953458404398631153796624848083036321 vote-msg-body.boc + +in validator-engine-console to create `vote-msg-body.boc` with the body of the internal message to be sent to the configuration smart contract. + +6. Upgrading the code of configuration smart contract and the elector smart contract +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It may happen that the code of the configuration smart contract itself or the code of the elector smart contract has to be upgraded. To this end, the same mechanism as described above is used. The new code is to be stored into the only reference of a value cell, and this value cell has to be proposed as the new value of configuration parameter -1000 (for upgrading the configuration smart contract) or -1001 (for upgrading the elector smart contract). These parameters pretend to be critical, so a lot of validator votes is needed to change the configuration smart contract (this is akin to adopting a new constitution). We expect that such changes will involve first testing them in a test network, and discussing the proposed changes in public forums before each validator operator decides to vote for or against proposed changes. + +Alternatively, critical configuration parameters 0 (the address of the configuration smart contract) or 1 (the address of the elector smart contract) can be changed to other values, that must correspond to already existing and correctly initialized smart contracts. In particular, the new configuration smart contract must contain a valid configuration dictionary in the first reference of its persistent data. Since it is not so easy to correctly transfer changing data (such as the list of active configuration proposals, or the previous and current participant lists of validator elections) between different smart contracts, in most cases it is better to upgrade the code of existing smart contract rather than to change the configuration smart contract address. + +There are two auxiliary scripts used to create such configuration proposals to upgrade the code of the configuration or elector smart contract. Namely, `create-config-upgrade-proposal.fif` loads a Fift assembler source file (`auto/config-code.fif` by default, corresponding to the code automatically generated by FunC compiler from `crypto/smartcont/config-code.fc`) and creates the corresponding configuration proposal (for configuration parameter -1000). Similarly, `create-elector-upgrade-proposal.fif` loads a Fift assembler source file (`auto/elector-code.fif` by default) and uses it to create a configuration proposal for configuration parameter -1001. In this way creating configuration proposals to upgrade one of these two smart contracts should be very simple. However, one should also publish the modified FunC source of the smart contract, the exact version of the FunC compiler used to compile it, so that all validators (or rather their operators) would be able to reproduce the code in the configuration proposal (and compare the hashes), and study and discuss the source code and the changes in this code before deciding to vote for or against proposed changes. diff --git a/doc/DNS-HOWTO b/doc/DNS-HOWTO new file mode 100644 index 00000000..3f4fb4b3 --- /dev/null +++ b/doc/DNS-HOWTO @@ -0,0 +1,173 @@ +The aim of this document is to provide a very brief introduction to TON DNS, a service for translating human-readable domain names (such as `test.ton` or `mysite.temp.ton`) into TON smart contract addresses, ADNL addresses employed by services running in the TON Network (such as TON Sites), and so on. + +1. Domain names +~~~~~~~~~~~~~~~ + +TON DNS employs familiarly-looking domain names, consisting of a UTF-8 encoded string up to 126 bytes, with different sections of the domain name separated by dots (`.`). Null characters (i.e. zero bytes) and, more generally, bytes in range 0..32 are not allowed in domain names. For instance, `test.ton` and `mysite.temp.ton` are valid TON DNS domains. A major difference from usual domain names is that TON DNS domains are case-sensitive; one could convert all domains to lowercase before performing a TON DNS lookup in order to obtain case-insensitivity if desired. + +Currently, only domains ending in `.ton` are recognized as valid TON DNS domains. This could change in the future. Notice, however, that it is a bad idea to define first-level domains coinciding with first-level domains already existing in the Internet, such as `.com` or `.to`, because one could then register a TON domain `google.com`, deploy a TON site there, create a hidden link to a page at this TON site from his other innocently-looking TON site, and steal `google.com` cookies from unsuspecting visitors. + +Internally, TON DNS transforms domain names as follows. First, a domain name is split into its components delimited by dot characters `.`. Then null characters are appended to each component, and all components are concatenated in reverse order. For example, `google.com` becomes `com\0google\0`. + +2. Resolving TON DNS domains +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A TON DNS domain is resolved as follows. First, the *root DNS smart contract* is located by inspecting the value of configuration parameter #4 in a recent masterchain state. This parameter contains the 256-bit address of the root DNS smart contract inside the masterchain. + +Then a special get-method `dnsresolve` (method id 123660) is invoked for the root DNS smart contract, with two parameters. The first parameter is a CellSlice with *8n* data bits containing the internal representation of the domain being resolved, where *n* is the length of the internal representation in bytes (at most 127). The second parameter is a signed 16-bit Integer containing the required *category*. If the category is zero, then all categories are requested. + +If this get-method fails, then the TON DNS lookup is unsuccessful. Otherwise the get-method returns two values. The first is *8m*, the length (in bits) of the prefix of the internal representation of the domain that has been resolved, 0 < m <= n. The second is a Cell with the TON DNS record for the required domain in the required category, or the root a Dictionary with 16-bit signed integer keys (categories) and values equal to the serializations of corresponding TON DNS records. If the domain cannot be resolved by the root DNS smart contract, i.e. if no non-empty prefix is a valid domain known to the smart contract, then (0, null) is returned. In other words, m = 0 means that the TON DNS lookup has found no data for the required domain. In that case, the TON DNS lookup is also unsuccessful. + +If m = n, then the second component of the result is either a Cell with a valid TON DNS record for the required domain and category, or a Null if there is no TON DNS record for this domain with this category. In either case, the resolution process stops, and the TON DNS record thus obtained is deserialized and the required information (such as the type of the record and its parameters, such as a smart contract address or a ADNL address). + +Finally, if m < n, then the lookup is successful so far, but only a partial result is available for the m-byte prefix of the original internal representation of the domain. The longest of all such prefixes known to the DNS smart contract is returned. For instance, an attempt to look up `mysite.test.ton` (i.e. `ton\0test\0mysite\0` in the internal representation) in the root DNS smart contract might return 8m=72, corresponding to prefix `ton\0test\0`, i.e. to subdomain "test.ton" in the usual domain representation. In that case, dnsresolve() returns the value for category -1 for this prefix regardless of the category originally requested by the client. By convention, category -1 usually contains a TON DNS Record of type *dns_next_resolver*, containing the address of next resolver smart contract (which can reside in any other workchain, such as the basechain). If that is indeed the case, the resolution process continues by running get-method `dnsresolve` for the next resolver, with the internal representation of the domain name containing only its part unresolved so far (if we were looking up `ton\0test\0mysite\0`, and prefix `ton\0test\0` was found by the root DNS smart contract, then the next `dnsresolve` will be invoked with `mysite\0` as its first argument). Then either the next resolver smart contract reports an error or the absence of any records for the required domain or any of its prefixes, or the final result is obtained, or another prefix and next resolver smart contract is returned. In the latter case, the process continues in the same fashion until all of the original domain is resolved. + +3. Using LiteClient and TonLib to resolve TON DNS domains +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The above process can be invoked automatically using the TON LiteClient or TONLib. For instance, one can invoke the command `dnsresolve test.ton 1` in the LiteClient to resolve "test.ton" with category 1 and obtain the following result: + +================================================ +> dnsresolve test.ton +... +Result for domain 'test.ton' category 1 +raw data: x{AD011B3CBBE404F47FFEF92D0D7894C5C6F215F677732A49E544F16D1E75643D46AB00} + +category #1 : (dns_adnl_address adnl_addr:x1B3CBBE404F47FFEF92D0D7894C5C6F215F677732A49E544F16D1E75643D46AB flags:0) + adnl address 1B3CBBE404F47FFEF92D0D7894C5C6F215F677732A49E544F16D1E75643D46AB = UNTZO7EAT2H77XZFUGXRFGFY3ZBL5TXOMVETZKE6FWR45LEHVDKXAUY +================================================ + +In this case, the TON DNS record for "test.ton" is a `dns_adnl_address` record containing ADNL address UNTZO7EAT2H77XZFUGXRFGFY3ZBL5TXOMVETZKE6FWR45LEHVDKXAUY + +Alternatively, one can invoke `tonlib-cli` and enter the following command: +================================================ +> dns resolve root test.ton 1 +Redirect resolver +... +Done + test.ton 1 ADNL:untzo7eat2h77xzfugxrfgfy3zbl5txomvetzke6fwr45lehvdkxauy +================================================ + +This is a more compact representation of the same result. + +Finally, if one uses RLDP-HTTP Proxy in the client mode to access TON Sites from a browser as explained in `TONSites-HOWTO.txt`, the TONLib resolver is automatically invoked to resolve all domains entered by the end user, so that a HTTP query to `http://test.ton/testnet/last` is automatically forwarded to ADNL address `untzo7eat2h77xzfugxrfgfy3zbl5txomvetzke6fwr45lehvdkxauy` via RLDP. + +4. Registering new domains +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Suppose that you have a new TON Site with a newly-generated ADNL address, such as `vcqmha5j3ceve35ammfrhqty46rkhi455otydstv66pk2tmf7rl25f3`. Of course, the end user might type `http://vcqmha5j3ceve35ammfrhqty46rkhi455otydstv66pk2tmf7rl25f3.adnl/` to visit your TON Site from a browser using a RLDP-HTTP Proxy in client mode, but this is not very convenient. Instead, you could register a new domain, say, `mysite.temp.ton` with a `dns_adnl_address` record in category 1 containing the ADNL address vcq...25f3 of your TON Site. Then the user would access your TON Site by simply typing `mysite.temp.ton` in a browser. + +In general, you would need to contact the owner of the higher-level domain and ask him to add a record for your subdomain in his DNS resolver smart contract. However, the TestNet of the TON Blockchain has a special resolver smart contract for `temp.ton` that allows anyone to automatically register any subdomains of `temp.ton` not registered yet, provided a small fee (in test Grams) is paid to that smart contract. In our case, we first need to find out the address of this smart contract, for example by using the Lite Client: + +================================================ +> dnsresolve temp.ton -1 +... +category #-1 : (dns_next_resolver + resolver:(addr_std + anycast:nothing workchain_id:0 address:x190BD756F6C0E7948DC26CB47968323177FB20344F8F9A50918CAF87ECB34B79)) + next resolver 0:190BD756F6C0E7948DC26CB47968323177FB20344F8F9A50918CAF87ECB34B79 = EQAZC9dW9sDnlI3CbLR5aDIxd_sgNE-PmlCRjK-H7LNLeUXN +================================================ + +We see that the address of this automatic DNS smart contract is EQAZC9dW9sDnlI3CbLR5aDIxd_sgNE-PmlCRjK-H7LNLeUXN. We can run several get methods to compute the required price for registering a subdomain, and to learn the period for which the subdomain will be registered: + +================================================ +> runmethod EQAZC9dW9sDnlI3CbLR5aDIxd_sgNE-PmlCRjK-H7LNLeUXN getstdperiod +... +arguments: [ 67418 ] +result: [ 700000 ] +remote result (not to be trusted): [ 700000 ] +> runmethod EQAZC9dW9sDnlI3CbLR5aDIxd_sgNE-PmlCRjK-H7LNLeUXN getppr +... +arguments: [ 109522 ] +result: [ 100000000 ] +remote result (not to be trusted): [ 100000000 ] +================================================ + +We see that subdomains are registered for 700000 seconds (about eight days), and that the registration price is 100000000ng = 0.1 test Grams per domain, plus a price for each bit and cell of stored data, which can be learned by running get-methods `getppb` and `getppc`. + +Now we want this smart contract to register our subdomain. In order to do this, we have to create a special message from our wallet to the automatic DNS smart contract. Let us assume that we have a wallet `my_new_wallet` with address kQABzslAMKOVwkSkkWfelS1pYSDOSyTcgn0yY_loQvyo_ZgI. Then we run the following Fift script (from the subdirectory `crypto/smartcont` of the source tree): + + fift -s auto-dns.fif add owner cat 1 adnl + +For example: + +=============================================== +$ fift -s auto-dns.fif EQAZC9dW9sDnlI3CbLR5aDIxd_sgNE-PmlCRjK-H7LNLeUXN add 'mysite' 700000 owner kQABzslAMKOVwkSkkWfelS1pYSDOSyTcgn0yY_loQvyo_ZgI cat 1 adnl vcqmha5j3ceve35ammfrhqty46rkhi455otydstv66pk2tmf7rl25f3 +Automatic DNS smart contract address = 0:190bd756f6c0e7948dc26cb47968323177fb20344f8f9a50918caf87ecb34b79 +kQAZC9dW9sDnlI3CbLR5aDIxd_sgNE-PmlCRjK-H7LNLef5H +Action: add mysite 1583865040 +Operation code: 0x72656764 +Value: x{2_} + x{BC000C_} + x{AD0145061C1D4EC44A937D0318589E13C73D151D1CEF5D3C0E53AFBCF56A6C2FE2BD00} + x{BFFFF4_} + x{9FD3800039D928061472B84894922CFBD2A5AD2C2419C9649B904FA64C7F2D085F951FA01_} + +Internal message body is: x{726567645E5D2E700481CE3F0EDAF2E6D2E8CA00BCCFB9A1_} + x{2_} + x{BC000C_} + x{AD0145061C1D4EC44A937D0318589E13C73D151D1CEF5D3C0E53AFBCF56A6C2FE2BD00} + x{BFFFF4_} + x{9FD3800039D928061472B84894922CFBD2A5AD2C2419C9649B904FA64C7F2D085F951FA01_} + +B5EE9C7241010601007800012F726567645E5D2E700481CE3F0EDAF2E6D2E8CA00BCCFB9A10102012002030105BC000C040105BFFFF4050046AD0145061C1D4EC44A937D0318589E13C73D151D1CEF5D3C0E53AFBCF56A6C2FE2BD0000499FD3800039D928061472B84894922CFBD2A5AD2C2419C9649B904FA64C7F2D085F951FA01070E6337D +Query_id is 6799642071046147647 = 0x5E5D2E700481CE3F +(Saved to file dns-msg-body.boc) +================================================ + +We see that the internal message body for this query has been created and saved into file `dns-msg-body.boc`. Now you have to send a payment from your wallet kQAB..ZgI to the automatic DNS smart contract EQA..UXN, along with message body from file `dns-msg-body.boc`, so that the automatic DNS smart contract knows what you want it to do. If your wallet has been created by means of `new-wallet.fif`, you can simply use `-B` command-line argument to `wallet.fif` while performing this transfer: + +================================================ +$ fift -s wallet.fif my_new_wallet EQAZC9dW9sDnlI3CbLR5aDIxd_sgNE-PmlCRjK-H7LNLeUXN 1 1.7 -B dns-msg-body.boc +Source wallet address = 0:01cec94030a395c244a49167de952d696120ce4b24dc827d3263f96842fca8fd +kQABzslAMKOVwkSkkWfelS1pYSDOSyTcgn0yY_loQvyo_ZgI +Loading private key from file my_new_wallet.pk +Transferring GR$1.7 to account kQAZC9dW9sDnlI3CbLR5aDIxd_sgNE-PmlCRjK-H7LNLef5H = 0:190bd756f6c0e7948dc26cb47968323177fb20344f8f9a50918caf87ecb34b79 seqno=0x1 bounce=-1 +Body of transfer message is x{726567645E5D2E700481CE3F0EDAF2E6D2E8CA00BCCFB9A1_} + x{2_} + x{BC000C_} + x{AD0145061C1D4EC44A937D0318589E13C73D151D1CEF5D3C0E53AFBCF56A6C2FE2BD00} + x{BFFFF4_} + x{9FD3800039D928061472B84894922CFBD2A5AD2C2419C9649B904FA64C7F2D085F951FA01_} + +signing message: x{0000000103} + x{62000C85EBAB7B6073CA46E1365A3CB41918BBFD901A27C7CD2848C657C3F659A5BCA32A9F880000000000000000000000000000726567645E5D2E700481CE3F0EDAF2E6D2E8CA00BCCFB9A1_} + x{2_} + x{BC000C_} + x{AD0145061C1D4EC44A937D0318589E13C73D151D1CEF5D3C0E53AFBCF56A6C2FE2BD00} + x{BFFFF4_} + x{9FD3800039D928061472B84894922CFBD2A5AD2C2419C9649B904FA64C7F2D085F951FA01_} + +resulting external message: x{8800039D928061472B84894922CFBD2A5AD2C2419C9649B904FA64C7F2D085F951FA050E3817FC01F564AECE810B8077D72E3EE15C81392E8B4AE9CDD0D6575821481C996AE8FFBABA0513F131E10E27C006C6544E99D71E0A6AACF7D02C677342B040000000081C_} + x{62000C85EBAB7B6073CA46E1365A3CB41918BBFD901A27C7CD2848C657C3F659A5BCA32A9F880000000000000000000000000000726567645E5D2E700481CE3F0EDAF2E6D2E8CA00BCCFB9A1_} + x{2_} + x{BC000C_} + x{AD0145061C1D4EC44A937D0318589E13C73D151D1CEF5D3C0E53AFBCF56A6C2FE2BD00} + x{BFFFF4_} + x{9FD3800039D928061472B84894922CFBD2A5AD2C2419C9649B904FA64C7F2D085F951FA01_} + +B5EE9C72410207010001170001CF8800039D928061472B84894922CFBD2A5AD2C2419C9649B904FA64C7F2D085F951FA050E3817FC01F564AECE810B8077D72E3EE15C81392E8B4AE9CDD0D6575821481C996AE8FFBABA0513F131E10E27C006C6544E99D71E0A6AACF7D02C677342B040000000081C01019762000C85EBAB7B6073CA46E1365A3CB41918BBFD901A27C7CD2848C657C3F659A5BCA32A9F880000000000000000000000000000726567645E5D2E700481CE3F0EDAF2E6D2E8CA00BCCFB9A10202012003040105BC000C050105BFFFF4060046AD0145061C1D4EC44A937D0318589E13C73D151D1CEF5D3C0E53AFBCF56A6C2FE2BD0000499FD3800039D928061472B84894922CFBD2A5AD2C2419C9649B904FA64C7F2D085F951FA01031E3A74C +(Saved to file wallet-query.boc) +===================================================== + +(You have to replace 1 with the correct sequence number for your wallet.) Once you obtain a signed external message in `wallet-query.boc`, addressed to your wallet and instructing it to transfer 1.7 test Grams to the automatic DNS smart contract along with the description of your new domain to be registered, you can upload this message using the LiteClient by typing + +===================================================== +> sendfile wallet-query.boc +[ 1][t 1][!testnode] sending query from file wallet-query.boc +[ 3][t 1][!query] external message status is 1 +===================================================== + +If all works correctly, you'll obtain some change from the automatic DNS smart contract in a confirmation message (it will charge only the storage fees for your subdomain and processing fees for running the smart contract and sending messages, and return the rest), and your new domain will be registered: + +===================================================== +> last +... +> dnsresolve mysite.temp.ton 1 +... +Result for domain 'mysite.temp.ton' category 1 +category #1 : (dns_adnl_address adnl_addr:x45061C1D4EC44A937D0318589E13C73D151D1CEF5D3C0E53AFBCF56A6C2FE2BD flags:0) + adnl address 45061C1D4EC44A937D0318589E13C73D151D1CEF5D3C0E53AFBCF56A6C2FE2BD = vcqmha5j3ceve35ammfrhqty46rkhi455otydstv66pk2tmf7rl25f3 +===================================================== + +You can modify or prolong this domain in essentially the same manner, by first creating a request in file `dns-msg-body.boc` by means of `auto-dns.fif`, using such actions as `update` or `prolong`, and then embedding this request into a message from your wallet to the automatic DNS smart contract using `wallet.fif` or a similar script with command-line argument `-B dns-msg-body.boc`. diff --git a/doc/FullNode-HOWTO b/doc/FullNode-HOWTO index 2778b02f..480dc935 100644 --- a/doc/FullNode-HOWTO +++ b/doc/FullNode-HOWTO @@ -1,3 +1,5 @@ +The most convenient way to compile and run a node is provided by MyTonCtrl: https://github.com/ton-blockchain/mytonctrl/. More info is available on https://ton.org/docs/#/nodes/run-node. + The aim of this document is to provide step-by-step instructions for setting up a full node for the TON Blockchain. We assume some familiarity with the TON Blockchain Lite Client, at least to the extent explained in the Lite Client HOWTO. Note that you need a machine with a public IP address and a high-bandwidth network connection to run a TON Blockchain Full Node. Typically you'll need a sufficiently powerful server in a datacenter with good network connectivity, using at least a 1 Gbit/s connection to reliably accommodate peak loads (the average load is expected to be approximately 100 Mbit/s). We recommend a dual-processor server with at least eight cores in each processor, at least 256 MiB RAM, at least 8 TB of conventional HDD storage and at least 512 GB of faster SSD storage. It is a bad idea to run a Full Node on your home computer; instead, you could run a Full Node on a remote server, and use the TON Blockchain Lite Client to connect to it from home. @@ -39,11 +41,11 @@ An approximate layout of the working directory of the TON Blockchain Full Node s In order to set up your Full Node, you'll need a special JSON file called the "global configuration (file)". It is called this because it is the same for all full nodes, and even nodes participating in different instances of the TON Blockchain (e.g., "testnet" vs. "mainnet") share an almost identical global configuration. -The "testnet" global configuration can be downloaded at https://test.ton.org/ton-global.config.json as follows: +The "mainnet" global configuration can be downloaded at https://ton.org/global-config.json as follows: -$ wget https://test.ton.org/ton-global.config.json +$ wget https://ton.org/global-config.json -You may wish to put this file into /var/ton-work/etc/ton-global.config.json . +You may wish to put this file into /var/ton-work/etc/global-config.json . We'll discuss the structure of this file later in more detail. For now, let us remark that the bulk of this file consists of a list of known TON DHT nodes required for the bootstrapping of the TON Network. A smaller section near the end of this file describes the particular instance of the TON Blockchain that we wish to connect to. @@ -56,9 +58,9 @@ It is important to distinguish this "global configuration file", used for settin Once the global configuration file is downloaded, it can be used to create the initial local configuration in ${DB_ROOT}/config.json. To do this, execute validator-engine once: -$ validator-engine -C /var/ton-work/etc/ton-global.config.json --db /var/ton-work/db/ --ip : -l /var/ton-work/log +$ validator-engine -C /var/ton-work/etc/global-config.json --db /var/ton-work/db/ --ip : -l /var/ton-work/log -Here `/var/ton-work/log` is the log directory of `validator-engine`, where it will create its log files. The argument to the `-C` command-line option is the global configuration file downloaded from test.ton.org as explained above, and `/var/ton-work/db/` is the working directory ${DB_ROOT}. Finally, : are the global IP address of this full node (you need to indicate a public IPv4 address here) and the UDP port used to run TON Network protocols such as ADNL and RLDP. Make sure that your firewall is configured to pass UDP packets with source or destination : at least for the `validator-engine` binary. +Here `/var/ton-work/log` is the log directory of `validator-engine`, where it will create its log files. The argument to the `-C` command-line option is the global configuration file downloaded from ton.org as explained above, and `/var/ton-work/db/` is the working directory ${DB_ROOT}. Finally, : are the global IP address of this full node (you need to indicate a public IPv4 address here) and the UDP port used to run TON Network protocols such as ADNL and RLDP. Make sure that your firewall is configured to pass UDP packets with source or destination : at least for the `validator-engine` binary. When validator-engine is invoked as above, and ${DB_ROOT}/config.json does not exist, it creates a new local configuration file ${DB_ROOT}/config.json using the information from the global configuration file and from the command-line options such as --ip, and then exits. If ${DB_ROOT}/config.json already exists, it is not rewritten; instead validator-engine starts up as a daemon using both the local and the global configuration. @@ -114,9 +116,9 @@ Replace it with the following: To run the full node, simply run the validator-engine binary in a console: -$ validator-engine --db ${DB_ROOT} -C /var/ton-work/etc/ton-global.config.json -l /var/ton-work/log +$ validator-engine --db ${DB_ROOT} -C /var/ton-work/etc/global-config.json -l /var/ton-work/log -It will read the global configuration from /var/ton-work/etc/ton-global.config.json, the local configuration from ${DB_ROOT}/config.json, and continue running silently. You should write suitable scripts for invoking validator-engine as a daemon (so that it does not terminate when the console is closed), but we'll skip these considerations for simplicity. (The command-line option `-d` of validator-engine should be sufficient for this on most *nix systems.) +It will read the global configuration from /var/ton-work/etc/global-config.json, the local configuration from ${DB_ROOT}/config.json, and continue running silently. You should write suitable scripts for invoking validator-engine as a daemon (so that it does not terminate when the console is closed), but we'll skip these considerations for simplicity. (The command-line option `-d` of validator-engine should be sufficient for this on most *nix systems.) If the configuration is invalid, validator-engine will terminate immediately and, in most cases, output nothing. You'll have to study the log files under /var/ton-work/log to find out what went wrong. Otherwise, validator-engine will keep working silently. Again, you can understand what's going on by inspecting the log files, and by looking into subdirectories of the ${DB_ROOT} directory. diff --git a/doc/GlobalVersions.md b/doc/GlobalVersions.md new file mode 100644 index 00000000..6c176552 --- /dev/null +++ b/doc/GlobalVersions.md @@ -0,0 +1,99 @@ +# Global versions +Global version is a parameter specified in `ConfigParam 8` ([block.tlb](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb#L595)). +Various features are enabled depending on the global version. + +## Version 4 + +### New TVM instructions +* `PREVMCBLOCKS`, `PREVKEYBLOCK` +* `GLOBALID` +* `HASHEXT(A)(R)` +* `ECRECOVER` +* `SENDMSG` +* `RUNVM`, `RUNVMX` +* `GASCONSUMED` +* `RIST255_...` instructions +* `BLS_...` instructions +* `P256_CHKSIGNS`, `P256_CHKSIGNU` + +### Division +[Division instruction](https://ton.org/docs/learn/tvm-instructions/instructions#52-division) can add a number to the +intermediate value before division (e.g. `(xy+w)/z`). + +### Stack operations +* Arguments of `PICK`, `ROLL`, `ROLLREV`, `BLKSWX`, `REVX`, `DROPX`, `XCHGX`, `CHKDEPTH`, `ONLYTOPX`, `ONLYX` are now unlimited. +* `ROLL`, `ROLLREV`, `BLKSWX`, `REVX`, `ONLYTOPX` consume more gas when arguments are big. + +### c7 tuple +**c7** tuple extended from 10 to 14 elements: +* **10**: code of the smart contract. +* **11**: value of the incoming message. +* **12**: fees collected in the storage phase. +* **13**: information about previous blocks. + +### Action phase +* If "send message" action fails, the account is required to pay for processing cells of the message. +* Flag +16 in actions "Send message", "Reserve", "Change library" causes bounce if action fails. + +### Storage phase +* Unpaid storage fee is now saved to `due_payment` + +## Version 5 + +### Gas limits +Version 5 enables higher gas limits for special contracts. + +* Gas limit for all transactions on special contracts is set to `special_gas_limit` from `ConfigParam 20` (which is 35M at the moment of writing). +Previously only ticktock transactions had this limit, while ordinary transactions had a default limit of `gas_limit` gas (1M). +* Gas usage of special contracts is not taken into account when checking block limits. This allows keeping masterchain block limits low +while having high gas limits for elector. +* Gas limit on `EQD_v9j1rlsuHHw2FIhcsCFFSD367ldfDdCKcsNmNpIRzUlu` is increased to `special_gas_limit * 2` until 2024-02-29. +See [this post](https://t.me/tonstatus/88) for details. + +### Loading libraries +* Loading "nested libraries" (i.e. a library cell that points to another library cell) throws an exception. +* Loading a library consumes gas for cell load only once (for the library cell), not twice (both for the library cell and the cell in the library). +* `XLOAD` now works differently. When it takes a library cell, it returns the cell that it points to. This allows loading "nested libraries", if needed. + +## Version 6 + +### c7 tuple +**c7** tuple extended from 14 to 17 elements: +* **14**: tuple that contains some config parameters as cell slices. If the parameter is absent from the config, the value is null. Asm opcode: `UNPACKEDCONFIGTUPLE`. + * **0**: `StoragePrices` from `ConfigParam 18`. Not the whole dict, but only the one StoragePrices entry (one which corresponds to the current time). + * **1**: `ConfigParam 19` (global id). + * **2**: `ConfigParam 20` (mc gas prices). + * **3**: `ConfigParam 21` (gas prices). + * **4**: `ConfigParam 24` (mc fwd fees). + * **5**: `ConfigParam 25` (fwd fees). + * **6**: `ConfigParam 43` (size limits). +* **15**: "[due payment](https://github.com/ton-blockchain/ton/blob/8a9ff339927b22b72819c5125428b70c406da631/crypto/block/block.tlb#L237)" - current debt for storage fee (nanotons). Asm opcode: `DUEPAYMENT`. +* **16**: "precompiled gas usage" - gas usage for the current contract if it is precompiled (see `ConfigParam 45`), `null` otherwise. Asm opcode: `GETPRECOMPILEDGAS`. + +### New TVM instructions + +#### Fee calculation +* `GETGASFEE` (`gas_used is_mc - price`) - calculates gas fee. +* `GETSTORAGEFEE` (`cells bits seconds is_mc - price`) - calculates storage fees (only current StoragePrices entry is used). +* `GETFORWARDFEE` (`cells bits is_mc - price`) - calculates forward fee. +* `GETPRECOMPILEDGAS` (`- x`) - returns gas usage for the current contract if it is precompiled, `null` otherwise. +* `GETORIGINALFWDFEE` (`fwd_fee is_mc - orig_fwd_fee`) - calculate `fwd_fee * 2^16 / first_frac`. Can be used to get the original `fwd_fee` of the message. +* `GETGASFEESIMPLE` (`gas_used is_mc - price`) - same as `GETGASFEE`, but without flat price (just `(gas_used * price) / 2^16`). +* `GETFORWARDFEESIMPLE` (`cells bits is_mc - price`) - same as `GETFORWARDFEE`, but without lump price (just `(bits*bit_price + cells*cell_price) / 2^16`). + +`gas_used`, `cells`, `bits`, `time_delta` are integers in range `0..2^63-1`. + +#### Cell operations +Operations for working with Merkle proofs, where cells can have non-zero level and multiple hashes. +* `CLEVEL` (`cell - level`) - returns level of the cell. +* `CLEVELMASK` (`cell - level_mask`) - returns level mask of the cell. +* `i CHASHI` (`cell - hash`) - returns `i`th hash of the cell. +* `i CDEPTHI` (`cell - depth`) - returns `i`th depth of the cell. +* `CHASHIX` (`cell i - hash`) - returns `i`th hash of the cell. +* `CDEPTHIX` (`cell i - depth`) - returns `i`th depth of the cell. + +`i` is in range `0..3`. + +### Other changes +* `GLOBALID` gets `ConfigParam 19` from the tuple, not from the config dict. This decreases gas usage. +* `SENDMSG` gets `ConfigParam 24/25` (message prices) from the tuple, not from the config dict, and also uses `ConfigParam 43` to get max_msg_cells. \ No newline at end of file diff --git a/doc/TestGrams-HOWTO b/doc/TestGrams-HOWTO new file mode 100644 index 00000000..8156e975 --- /dev/null +++ b/doc/TestGrams-HOWTO @@ -0,0 +1,163 @@ +The aim of this text is to describe how to quickly obtain a small amount of test Grams for test purposes, or a larger amount of test Grams for running a validator in the test network. We assume familiarity with the TON Blockchain LiteClient as explained in the LiteClient-HOWTO, and with the procedure required to compile the LiteClient and other software. For obtaining larger amount of test Grams required for running a validator, we also assume acquaintance with the FullNode-HOWTO and Validator-HOWTO. You will also need a dedicated server powerful enough for running a Full Node in order to obtain the larger amount of test Grams. Obtaining small amounts of test Grams does not require a dedicated server and may be done in several minutes on a home computer. + +1. Proof-of-Work TestGiver smart contracts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to prevent a small number of malicious parties to collect all test Grams reserved for test purposes, a special kind of "Proof-of-Work TestGiver" smart contracts have been deployed in the masterchain of the test network. The addresses of these smart contacts are: + +Small testgivers (deliver from 10 to 100 test Grams every several minutes): + +kf-kkdY_B7p-77TLn2hUhM6QidWrrsl8FYWCIvBMpZKprBtN +kf8SYc83pm5JkGt0p3TQRkuiM58O9Cr3waUtR9OoFq716lN- +kf-FV4QTxLl-7Ct3E6MqOtMt-RGXMxi27g4I645lw6MTWraV +kf_NSzfDJI1A3rOM0GQm7xsoUXHTgmdhN5-OrGD8uwL2JMvQ +kf8gf1PQy4u2kURl-Gz4LbS29eaN4sVdrVQkPO-JL80VhOe6 +kf8kO6K6Qh6YM4ddjRYYlvVAK7IgyW8Zet-4ZvNrVsmQ4EOF +kf-P_TOdwcCh0AXHhBpICDMxStxHenWdLCDLNH5QcNpwMHJ8 +kf91o4NNTryJ-Cw3sDGt9OTiafmETdVFUMvylQdFPoOxIsLm +kf9iWhwk9GwAXjtwKG-vN7rmXT3hLIT23RBY6KhVaynRrIK7 +kf8JfFUEJhhpRW80_jqD7zzQteH6EBHOzxiOhygRhBdt4z2N + +Large testgivers (deliver 10000 test Grams at least once a day): + +kf8guqdIbY6kpMykR8WFeVGbZcP2iuBagXfnQuq0rGrxgE04 +kf9CxReRyaGj0vpSH0gRZkOAitm_yDHvgiMGtmvG-ZTirrMC +kf-WXA4CX4lqyVlN4qItlQSWPFIy00NvO2BAydgC4CTeIUme +kf8yF4oXfIj7BZgkqXM6VsmDEgCqWVSKECO1pC0LXWl399Vx +kf9nNY69S3_heBBSUtpHRhIzjjqY0ChugeqbWcQGtGj-gQxO +kf_wUXx-l1Ehw0kfQRgFtWKO07B6WhSqcUQZNyh4Jmj8R4zL +kf_6keW5RniwNQYeq3DNWGcohKOwI85p-V2MsPk4v23tyO3I +kf_NSPpF4ZQ7mrPylwk-8XQQ1qFD5evLnx5_oZVNywzOjSfh +kf-uNWj4JmTJefr7IfjBSYQhFbd3JqtQ6cxuNIsJqDQ8SiEA +kf8mO4l6ZB_eaMn1OqjLRrrkiBcSt7kYTvJC_dzJLdpEDKxn + +The first ten smart contracts enable a tester willing to obtain a small amount of test Grams to obtain some without spending too much computing power (typically, several minutes of work of a home computer should suffice). The remaining smart contracts are for obtaining larger amounts of test Grams required for running a validator in the test network; typically, a day of work of a dedicated server powerful enough to run a validator should suffice to obtain the necessary amount. + +You should randomly choose one of these "proof-of-work testgiver" smart contracts (from one of these two lists depending on your purpose) and obtain test Grams from this smart contract by a procedure similar to "mining". Essentially, you have to present an external message containing the proof of work and the address of your wallet to the chosen "proof-of-work testgiver" smart contract, and then the necessary amount will be sent to you. + +2. The "mining" process +~~~~~~~~~~~~~~~~~~~~~~~ + +In order to create an external message containing the "proof of work", you should run a special "mining" utility, compiled from the TON sources located in the GitHub repository. The utility is located in file './crypto/pow-miner' with respect to the build directory, and can be compiled by typing 'make pow-miner' in the build directory. + +However, before running "pow-miner", you need to know the actual values of "seed" and "complexity" parameters of the chosen "proof-of-work testgiver" smart contract. This can be done by invoking get-method "get_pow_params" of this smart contract. For instance, if you use testgiver smart contract kf-kkdY_B7p-77TLn2hUhM6QidWrrsl8FYWCIvBMpZKprBtN, you can simply type + + > runmethod kf-kkdY_B7p-77TLn2hUhM6QidWrrsl8FYWCIvBMpZKprBtN get_pow_params + +in the LiteClient console and obtain output like + + ... + arguments: [ 101616 ] + result: [ 229760179690128740373110445116482216837 53919893334301279589334030174039261347274288845081144962207220498432 100000000000 256 ] + remote result (not to be trusted): [ 229760179690128740373110445116482216837 53919893334301279589334030174039261347274288845081144962207220498432 100000000000 256 ] + +The two first large numbers in the "result:" line are the "seed" and the "complexity" of this smart contract. In this example, the seed is 229760179690128740373110445116482216837 and the complexity is 53919893334301279589334030174039261347274288845081144962207220498432. + +Next, you invoke the pow-miner utility as follows: + + $ crypto/pow-miner -vv -w -t + +Here is the number of CPU cores that you want to use for mining, is the maximal amount of seconds that the miner would run before admitting failure, is the address of your wallet (possibly not initialized yet), either in the masterchain or in the workchain (note that you need a masterchain wallet to control a validator), and are the most recent values obtained by running get-method 'get-pow-params', is the address of the chosen proof-of-work testgiver smartcontract, and is the filename of the output file where the external message with the proof of work will be saved in the case of success. + +For example, if your wallet address is kQBWkNKqzCAwA9vjMwRmg7aY75Rf8lByPA9zKXoqGkHi8SM7, you might run + + $ crypto/pow-miner -vv -w7 -t100 kQBWkNKqzCAwA9vjMwRmg7aY75Rf8lByPA9zKXoqGkHi8SM7 229760179690128740373110445116482216837 53919893334301279589334030174039261347274288845081144962207220498432 100000000000 kf-kkdY_B7p-77TLn2hUhM6QidWrrsl8FYWCIvBMpZKprBtN mined.boc + +The program will run for some time (at most 100 seconds in this case) and either terminate successfully (with zero exit code) and save the required proof of work into file "mined.boc", or terminate with a non-zero exit code if no proof of work was found. + +In the case of failure, you will see something like + + [ expected required hashes for success: 2147483648 ] + [ hashes computed: 1192230912 ] + +and the program will terminate with a non-zero exit code. Then you have to obtain the "seed" and "complexity" again (because they may have changed in the meantime as a result of processing requests from more successful miners) and re-run the "pow-miner" with the new parameters, repeating the process again and again until success. + +In the case of success, you will see something like + + [ expected required hashes for success: 2147483648 ] + 4D696E65005EFE49705690D2AACC203003DBE333046683B698EF945FF250723C0F73297A2A1A41E2F1A1F533B3BC4F5664D6C743C1C5C74BB3342F3A7314364B3D0DA698E6C80C1EA4ACDA33755876665780BAE9BE8A4D6385A1F533B3BC4F5664D6C743C1C5C74BB3342F3A7314364B3D0DA698E6C80C1EA4 + Saving 176 bytes of serialized external message into file `mined.boc` + [ hashes computed: 1122036095 ] + +Then you can use the LiteClient to send external message from file "mined.boc" to the proof-of-work testgiver smart contract (and you must do this as soon as possible): + +> sendfile mined.boc +... external message status is 1 + +You can wait for several seconds and check the state of your wallet: + +> last +> getaccount kQBWkNKqzCAwA9vjMwRmg7aY75Rf8lByPA9zKXoqGkHi8SM7 +... +account state is (account + addr:(addr_std + anycast:nothing workchain_id:0 address:x5690D2AACC203003DBE333046683B698EF945FF250723C0F73297A2A1A41E2F1) + storage_stat:(storage_info + used:(storage_used + cells:(var_uint len:1 value:1) + bits:(var_uint len:1 value:111) + public_cells:(var_uint len:0 value:0)) last_paid:1593722498 + due_payment:nothing) + storage:(account_storage last_trans_lt:7720869000002 + balance:(currencies + grams:(nanograms + amount:(var_uint len:5 value:100000000000)) + other:(extra_currencies + dict:hme_empty)) + state:account_uninit)) +x{C005690D2AACC203003DBE333046683B698EF945FF250723C0F73297A2A1A41E2F12025BC2F7F2341000001C169E9DCD0945D21DBA0004_} +last transaction lt = 7720869000001 hash = 83C15CDED025970FEF7521206E82D2396B462AADB962C7E1F4283D88A0FAB7D4 +account balance is 100000000000ng + +If nobody has sent a valid proof of work with this *seed* and *complexity* before you, the proof-of-work testgiver will accept your proof of work and this will be reflected in the balance of your wallet (10 or 20 seconds may elapse after sending the external message before this happens; be sure to make several attempts and type "last" each time before checking the balance of your wallet to refresh the LiteClient state). In the case of success, you will see that the balance has been increased (and even that your wallet has been created in uninitialized state if it did not exist before). In the case of failure, you will have to obtain the new "seed" and "complexity" and repeat the mining process from the very beginning. + +If you have been lucky and the balance of your wallet has been increased, you may want to initialize the wallet if it wasn't initialized before (more information on wallet creation can be found in LiteClient-HOWTO): + +> sendfile new-wallet-query.boc +... external message status is 1 +> last +> getaccount kQBWkNKqzCAwA9vjMwRmg7aY75Rf8lByPA9zKXoqGkHi8SM7 +... +account state is (account + addr:(addr_std + anycast:nothing workchain_id:0 address:x5690D2AACC203003DBE333046683B698EF945FF250723C0F73297A2A1A41E2F1) + storage_stat:(storage_info + used:(storage_used + cells:(var_uint len:1 value:3) + bits:(var_uint len:2 value:1147) + public_cells:(var_uint len:0 value:0)) last_paid:1593722691 + due_payment:nothing) + storage:(account_storage last_trans_lt:7720945000002 + balance:(currencies + grams:(nanograms + amount:(var_uint len:5 value:99995640998)) + other:(extra_currencies + dict:hme_empty)) + state:(account_active + ( + split_depth:nothing + special:nothing + code:(just + value:(raw@^Cell + x{} + x{FF0020DD2082014C97BA218201339CBAB19C71B0ED44D0D31FD70BFFE304E0A4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54} + )) + data:(just + value:(raw@^Cell + x{} + x{00000001CE6A50A6E9467C32671667F8C00C5086FC8D62E5645652BED7A80DF634487715} + )) + library:hme_empty)))) +x{C005690D2AACC203003DBE333046683B698EF945FF250723C0F73297A2A1A41E2F1206811EC2F7F23A1800001C16B0BC790945D20D1929934_} + x{FF0020DD2082014C97BA218201339CBAB19C71B0ED44D0D31FD70BFFE304E0A4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54} + x{00000001CE6A50A6E9467C32671667F8C00C5086FC8D62E5645652BED7A80DF634487715} +last transaction lt = 7720945000001 hash = 73353151859661AB0202EA5D92FF409747F201D10F1E52BD0CBB93E1201676BF +account balance is 99995640998ng + +Now you are a happy owner of 100 test Grams that can be used for whatever testing purposes you had in mind. Congratulations! + +3. Automating the mining process in the case of failure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you fail to obtain your test Grams for a long time, this may happen because too many other testers are simultaneously "mining" from the same proof-of-work testgiver smart contract. Maybe you should choose another proof-of-work testgiver smart contract from one of the lists given above. Alternatively, you can write a simple script to automatically run `pow-miner` with the correct parameters again and again until success (detected by checking the exit code of `pow-miner`) and invoke the lite-client with parameter -c 'sendfile mined.boc' to send the external message immediately after it is found. + diff --git a/doc/Tests.md b/doc/Tests.md new file mode 100644 index 00000000..c883731a --- /dev/null +++ b/doc/Tests.md @@ -0,0 +1,24 @@ +# Tests execution +TON contains multiple unit-tests, that facilitate detection of erroneous blockchain behaviour on each commit. +## Build tests +Go inside the build directory and, if you use ninja, build the tests using the following command: + +```ninja test-ed25519 test-ed25519-crypto test-bigint test-vm test-fift test-cells test-smartcont test-net test-tdactor test-tdutils test-tonlib-offline test-adnl test-dht test-rldp test-rldp2 test-catchain test-fec test-tddb test-db test-validator-session-state``` + +For more details on how to build TON artifacts, please refer to any of Github actions. + +For cmake use: + +```cmake --build . --target test-ed25519 test-ed25519-crypto test-bigint test-vm test-fift test-cells test-smartcont test-net test-tdactor test-tdutils test-tonlib-offline test-adnl test-dht test-rldp test-rldp2 test-catchain test-fec test-tddb test-db test-validator-session-state``` + +## Run tests +Go inside the build directory and with ninja execute: + +```ninja test``` + +with ctest: + +```ctest``` + +## Integration of tests into CI +Most relevant GitHub actions include the step ```Run tests``` that executes the tests. If any of tests fails, the action will be interrupted and no artifacts will be provided. \ No newline at end of file diff --git a/doc/TonSites-HOWTO b/doc/TonSites-HOWTO new file mode 100644 index 00000000..8975a39f --- /dev/null +++ b/doc/TonSites-HOWTO @@ -0,0 +1,101 @@ +The aim of this document is to provide a gentle introduction into TON Sites, which are (TON) Web sites accessed through the TON Network. TON Sites may be used as a convenient entry point for other TON Services. In particular, HTML pages downloaded from TON Sites may contain links to ton://... URIs representing payments that can be performed by the user by clicking to the link, provided a TON Wallet is installed on the user's device. + +From the technical perspective, TON Sites are very much like the usual Web sites, but they are accessed through the TON Network (which is an overlay network inside the Internet) instead of the Internet. More specifically, they have an ADNL address (instead of a more customary IPv4 or IPv6 address), and they accept HTTP queries via RLDP protocol (which is a higher-level RPC protocol built upon ADNL, the main protocol of TON Network) instead of the usual TCP/IP. All encryption is handled by ADNL, so there is no need to use HTTPS (i.e., TLS). + +In order to access existing and create new TON Sites one needs special gateways between the "ordinary" internet and the TON Network. Essentially, TON Sites are accessed with the aid of a HTTP->RLDP proxy running locally on the client's machine, and they are created by means of a reverse RLDP->HTTP proxy running on a remote web server. + +1. Compiling RLDP-HTTP Proxy +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The RLDP-HTTP Proxy is a special utility specially designed for accessing and creating TON Sites. Its current (alpha) version is a part of the general TON Blockchain source tree, available at GitHub repository ton-blockchain/ton. In order to compile the RLDP-HTTP Proxy, follow the instructions outlined in README and Validator-HOWTO. The Proxy binary will be located as + + rldp-http-proxy/rldp-http-proxy + +in the build directory. Alternatively, you may want to build just the Proxy instead of building all TON Blockchain projects. This can be done by invoking + + cmake --build . --target rldp-http-proxy + +in the build directory. + +2. Running RLDP-HTTP Proxy to access TON Sites +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to access existing TON Sites, you need a running instance of RLDP-HTTP Proxy on your computer. It can be invoked as follows: + + rldp-http-proxy/rldp-http-proxy -p 8080 -c 3333 -C global-config.json + +or + + rldp-http-proxy/rldp-http-proxy -p 8080 -a :3333 -C global-config.json + +where is your public IPv4 address, provided you have one on your home computer. The TON Network global configuration file `global-config.json` can be downloaded at https://ton.org/global-config.json : + + wget https://ton.org/global-config.json + +In the above example, 8080 is the TCP port that will be listened to at localhost for incoming HTTP queries, and 3333 is the UDP port that will be used for all outbound and inbound RLDP and ADNL activity, i.e., for connecting to the TON Sites via the TON Network. + +If you have done everything correctly, the Proxy will not terminate, but it will continue running in the terminal. It can be used now for accessing TON Sites. When you don't need it anymore, you can terminate it by pressing Ctrl-C, or simply by closing the terminal window. + +3. Accessing TON Sites +~~~~~~~~~~~~~~~~~~~~~~ + +Now suppose that you have a running instance of the RLDP-HTTP Proxy running on your computer and listening on localhost:8080 for inbound TCP connections, as explained above in Section 2. + +A simple test that everything is working property may be performed using programs such as Curl or WGet. For example, + + curl -x 127.0.0.1:8080 http://test.ton + +attempts to download the main page of (TON) Site `test.ton` using the proxy at `127.0.0.1:8080`. If the proxy is up and running, you'll see something like + + +

TON Blockchain Test Network — files and resources

+

News

+