├── .ci └── package.sh ├── .devcontainer ├── Dockerfile ├── devcontainer.json ├── install_llvm16.sh └── install_rust.sh ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── enhancement.md │ ├── feature-request.md │ └── general-question.md ├── pull_request_template.md └── workflows │ ├── auto-assign.yml │ ├── commit-lint.yml │ ├── dtvm_cpp_dev_docker_release.yml │ ├── dtvm_cpp_test.yml │ ├── nightly_release.yml │ └── release.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LEGAL.md ├── LICENSE ├── Makefile ├── README.md ├── build_lib.sh ├── commitlint.config.js ├── contractlib └── v1 │ ├── .gitignore │ ├── contractlib.cpp │ ├── contractlib.hpp │ ├── encoding.hpp │ ├── hostapi.h │ ├── hostio.hpp │ ├── math.hpp │ ├── storage.hpp │ ├── storage.inc.cpp │ ├── storage_slot.hpp │ ├── types.hpp │ ├── utils.hpp │ ├── wasi.hpp │ └── wasi.inc.cpp ├── cpp_tests ├── .gitignore ├── CMakeLists.txt ├── hostapi_mock.cpp ├── test_encoding.cpp ├── test_main.cpp ├── test_math.cpp ├── test_storage.cpp └── utils.hpp ├── docker ├── Dockerfile ├── cargo_config ├── docker_build.sh ├── install_llvm16.sh └── install_rust.sh ├── docs ├── COMMIT_CONVENTION.md ├── VERSIONING.md ├── advanced_features │ ├── contract_interaction.md │ ├── contract_interfaces.md │ └── solidity_integration.md ├── basic_concepts │ ├── constructors.md │ ├── events.md │ ├── storage.md │ └── types.md ├── best_practices │ └── coding_standards.md ├── getting_started │ ├── installation.md │ └── quick_start.md ├── readme.md ├── tutorial.md └── usage.md ├── examples ├── c_erc20 │ ├── .gitignore │ ├── build.sh │ ├── erc20_c.c │ ├── hostapi.h │ └── test.sh ├── erc20 │ ├── .gitignore │ ├── CMakeLists.txt │ ├── build_wasm.sh │ ├── build_wasm_steps.sh │ ├── my_erc20.cpp │ └── test_in_mock_cli.sh ├── example1 │ ├── .gitignore │ ├── CMakeLists.txt │ ├── build_wasm.sh │ ├── build_wasm_steps.sh │ ├── generate_meta.sh │ ├── manual_generated │ │ ├── .gitignore │ │ └── my_token_decl.hpp │ ├── my_token.cpp │ └── test_in_mock_cli.sh └── fibonacci │ ├── .gitignore │ ├── build.sh │ ├── build_fib_no_contract.sh │ ├── counter.c │ ├── fib.c │ ├── fib_no_contract.c │ ├── fib_recur.c │ ├── hostapi.h │ └── test.sh ├── solidcpp ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── src │ ├── commands │ │ ├── fetch_cpp_sol.rs │ │ ├── generate_hpp.rs │ │ ├── mod.rs │ │ ├── sol_types_utils.rs │ │ └── utils.rs │ ├── lib.rs │ └── main.rs ├── test_example1_fetch_sol_from_cpp.sh ├── test_example1_fetch_sol_json.sh └── test_example1_generate_hpp.sh └── tools ├── chain_mockcli ├── linux_x86 │ └── chain_mockcli-linux-ubuntu22.04-0.1.0.zip └── mac_arm │ └── chain_mockcli-mac-arm-0.1.0.zip └── format.sh /.ci/package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | CUR_PATH=`pwd` 5 | 6 | export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static 7 | export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup 8 | # Install cargo if it doesn't exist 9 | if ! command -v cargo &> /dev/null; then 10 | curl -sSf https://mirrors.ustc.edu.cn/misc/rustup-install.sh | sh -s -- -y 11 | . "$HOME/.cargo/env" 12 | mkdir -vp ${CARGO_HOME:-$HOME/.cargo} 13 | cp docker/cargo_config ${CARGO_HOME:-$HOME/.cargo}/config 14 | rustup install 1.81.0 15 | fi 16 | rustup default 1.81.0 17 | 18 | if ! command -v emcc --version &> /dev/null; then 19 | cd /opt/emsdk 20 | # activate emscripten(emsdk 3.1.69 tested) 21 | ./emsdk activate 3.1.69 22 | . "/opt/emsdk/emsdk_env.sh" 23 | emcc --version 24 | cd $CUR_PATH 25 | fi 26 | 27 | # build contractlib.o 28 | bash build_lib.sh 29 | # build solidcpp 30 | cd solidcpp 31 | cargo build --release 32 | 33 | cd $CUR_PATH 34 | mkdir -vp tmp_build 35 | cd tmp_build 36 | cp ../solidcpp/target/release/solidcpp . 37 | # Choose tools/chain_mockcli/linux_x86/*.zip or tools/chain_mockcli/mac_arm/*.zip based on Linux/Mac 38 | if [[ "$OSTYPE" == "linux-gnu" ]]; then 39 | cp ../tools/chain_mockcli/linux_x86/*.zip . 40 | elif [[ "$OSTYPE" == "darwin"* ]]; then 41 | cp ../tools/chain_mockcli/mac_arm/*.zip . 42 | fi 43 | 44 | cp -r ../examples ./examples 45 | mkdir -p contractlib/ && mkdir -p contractlib/v1 46 | cp -r ../contractlib/v1/*.hpp ./contractlib/v1/ 47 | cp -r ../contractlib/v1/*.h ./contractlib/v1/ 48 | cp -r ../contractlib/v1/*.o ./contractlib/v1/ 49 | 50 | rm -f DTVM_CppSDK.nightly.latest.tar.gz 51 | tar -czf DTVM_CppSDK.nightly.latest.tar.gz * 52 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG VARIANT="16" 2 | FROM mcr.microsoft.com/devcontainers/javascript-node:1-${VARIANT} 3 | 4 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 5 | && apt-get -y install --no-install-recommends build-essential ca-certificates clang-format-15 clang-tidy-15 cmake curl git libzstd-dev ninja-build python3 python3-pip ssh sudo wabt wget zlib1g-dev wget git-lfs zlib1g-dev wget libffi-dev libncurses5-dev libncursesw5-dev libxml2-dev binaryen unzip 6 | 7 | # RUN pip3 install cmake-format lit --no-cache-dir 8 | # RUN cd /usr/bin/ && ln -s python3 python && ln -s clang-format-15 clang-format && ln -s clang-tidy-15 clang-tidy && ln -s run-clang-tidy-15 run-clang-tidy 9 | 10 | RUN mkdir -p /opt 11 | WORKDIR /opt 12 | 13 | COPY install_llvm16.sh /opt/install_llvm16.sh 14 | RUN chmod +x /opt/install_llvm16.sh 15 | 16 | RUN wget https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.4/clang+llvm-16.0.4-x86_64-linux-gnu-ubuntu-22.04.tar.xz && /opt/install_llvm16.sh 17 | 18 | RUN echo "export PATH=/opt/llvm16/bin:/opt:\$PATH" >> $HOME/.profile 19 | RUN echo "export LLVM_SYS_160_PREFIX=/opt/llvm16" >> $HOME/.profile 20 | ENV PATH=/opt/llvm16/bin:/opt:$PATH 21 | ENV LLVM_SYS_160_PREFIX=/opt/llvm16 22 | 23 | RUN wget -O /opt/solc https://github.com/ethereum/solidity/releases/download/v0.8.29/solc-static-linux 24 | RUN chmod +x /opt/solc 25 | RUN wget -O /opt/googletest-1.15.2.tar.gz https://github.com/google/googletest/releases/download/v1.15.2/googletest-1.15.2.tar.gz 26 | 27 | RUN git clone https://github.com/emscripten-core/emsdk.git 28 | WORKDIR /opt/emsdk 29 | RUN ./emsdk install 3.1.69 30 | RUN ./emsdk activate 3.1.69 31 | 32 | RUN curl -sSf https://mirrors.ustc.edu.cn/misc/rustup-install.sh | sh -s -- -y 33 | RUN bash -c ". /root/.cargo/env" 34 | RUN bash -c ". ~/.cargo/env && rustup install 1.81.0 && rustup default 1.81.0" 35 | 36 | RUN mkdir -p /root/.cargo && touch /root/.cargo/env 37 | RUN echo "source \"$HOME/.cargo/env\"" >> $HOME/.profile 38 | 39 | # install foundry 40 | RUN curl -L https://foundry.paradigm.xyz | bash 41 | ENV PATH=~/.foundry/bin:$PATH 42 | # do it manually 43 | # RUN bash -c "source ~/.bashrc && foundryup" 44 | 45 | WORKDIR /opt 46 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | // ... 3 | // "build": { "dockerfile": "Dockerfile" }, 4 | "image": "dtvmdev1/dtvm-cpp-dev-x64:main" 5 | // ... 6 | } 7 | -------------------------------------------------------------------------------- /.devcontainer/install_llvm16.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | cd /opt 4 | ls 5 | tar -xvf clang+llvm-16.0.4-x86_64-linux-gnu-ubuntu-22.04.tar.xz 6 | rm -rf clang+llvm-16.0.4-x86_64-linux-gnu-ubuntu-22.04.tar.xz 7 | mv clang+llvm-16.0.4-x86_64-linux-gnu-ubuntu-22.04 llvm16 8 | -------------------------------------------------------------------------------- /.devcontainer/install_rust.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | curl -sSf https://mirrors.ustc.edu.cn/misc/rustup-install.sh | sh -s -- -y 4 | . "$HOME/.cargo/env" 5 | rustup install 1.81.0 6 | rustup default 1.81.0 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | tools/chain_mockcli/linux_x86/chain_mockcli-linux-ubuntu22.04-0.1.0.zip filter=lfs diff=lfs merge=lfs -text 2 | tools/chain_mockcli/mac_arm/chain_mockcli-mac-arm-0.1.0.zip filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug Report" 3 | about: As a User, I want to report a Bug. 4 | labels: type/bug 5 | --- 6 | 7 | ## Bug Report 8 | 9 | Please answer these questions before submitting your issue. Thanks! 10 | 11 | ### 1. Minimal reproduce step (Required) 12 | 13 | 14 | 15 | ### 2. What did you expect to see? (Required) 16 | 17 | ### 3. What did you see instead (Required) 18 | 19 | ### 4. What is the version of this project you are using? (Required) -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Enhancement" 3 | about: As a developer, I want to make an enhancement. 4 | labels: type/enhancement 5 | --- 6 | 7 | ## Enhancement 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Feature Request" 3 | about: As a user, I want to request a New Feature on the product. 4 | labels: type/feature-request 5 | --- 6 | 7 | ## Feature Request 8 | 9 | **Is your feature request related to a problem? Please describe:** 10 | 11 | 12 | **Describe the feature you'd like:** 13 | 14 | 15 | **Describe alternatives you've considered:** 16 | 17 | 18 | **Teachability, Documentation, Adoption, Migration Strategy:** 19 | 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/general-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F914 Ask a Question" 3 | about: I want to ask a question. 4 | labels: type/question 5 | --- 6 | 7 | ## General Question 8 | 9 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 17 | 18 | #### 1. Does this PR affect any open issues?(Y/N) and add issue references (e.g. "fix #123", "re #123".): 19 | 20 | - [ ] N 21 | - [ ] Y 22 | 23 | 28 | 29 | #### 2. What is the scope of this PR (e.g. component or file name): 30 | 31 | 36 | 37 | #### 3. Provide a description of the PR(e.g. more details, effects, motivations or doc link): 38 | 39 | 40 | - [ ] Affects user behaviors 41 | - [ ] Contains CI/CD configuration changes 42 | - [ ] Contains documentation changes 43 | - [ ] Contains experimental features 44 | - [ ] Performance regression: Consumes more CPU 45 | - [ ] Performance regression: Consumes more Memory 46 | - [ ] Other 47 | 48 | 53 | 54 | #### 4. Are there any breaking changes?(Y/N) and describe the breaking changes(e.g. more details, motivations or doc link): 55 | 56 | - [ ] N 57 | - [ ] Y 58 | 59 | 64 | 65 | #### 5. Are there test cases for these changes?(Y/N) select and add more details, references or doc links: 66 | 67 | 68 | - [ ] Unit test 69 | - [ ] Integration test 70 | - [ ] Benchmark (add benchmark stats below) 71 | - [ ] Manual test (add detailed scripts or steps below) 72 | - [ ] Other 73 | 74 | 81 | 82 | #### 6. Release note 83 | 84 | 85 | 86 | ```release-note 87 | None 88 | ``` -------------------------------------------------------------------------------- /.github/workflows/auto-assign.yml: -------------------------------------------------------------------------------- 1 | name: Auto Assign 2 | on: 3 | issues: 4 | types: [opened] 5 | pull_request: 6 | types: [opened] 7 | jobs: 8 | run: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | steps: 14 | - name: 'Auto-assign issue' 15 | uses: pozil/auto-assign-issue@v1 16 | with: 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | assignees: zoowii 19 | numOfAssignee: 1 20 | -------------------------------------------------------------------------------- /.github/workflows/commit-lint.yml: -------------------------------------------------------------------------------- 1 | name: Commit Lint 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, edited] 6 | push: 7 | branches: 8 | - '**' 9 | 10 | jobs: 11 | commitlint: 12 | name: Lint Commit Messages 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: '16' 24 | 25 | - name: Install dependencies 26 | run: | 27 | npm install --save-dev @commitlint/cli @commitlint/config-conventional 28 | 29 | - name: Create commitlint config 30 | run: | 31 | cat > commitlint.config.js << 'EOL' 32 | module.exports = { 33 | extends: ['@commitlint/config-conventional'], 34 | rules: { 35 | 'type-enum': [2, 'always', [ 36 | 'feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore' 37 | ]], 38 | 'scope-enum': [2, 'always', [ 39 | 'core', 'api', 'contract', 'solidcpp', 'examples', 'docs', 'tools', 'deps', 'ci', 'test', 'other', '' 40 | ]], 41 | 'body-max-line-length': [1, 'always', 100], 42 | 'subject-case': [0, 'never'], 43 | }, 44 | helpUrl: 'https://github.com/DTVMStack/DTVM_CppSDK/blob/main/docs/COMMIT_CONVENTION.md', 45 | }; 46 | EOL 47 | 48 | - name: Lint commit messages 49 | run: | 50 | if [ "${{ github.event_name }}" = "pull_request" ]; then 51 | # For PR events, check all commits in the PR 52 | if [ -n "${{ github.event.pull_request.base.sha }}" ] && [ -n "${{ github.event.pull_request.head.sha }}" ]; then 53 | echo "Linting PR commits from ${{ github.event.pull_request.base.sha }} to ${{ github.event.pull_request.head.sha }}" 54 | npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose 55 | else 56 | echo "PR SHA values missing, checking last commit only" 57 | npx commitlint --from HEAD~1 --to HEAD --verbose || echo "No previous commit to check" 58 | fi 59 | else 60 | # For push events, check only the new commits with better error handling 61 | if [ -n "${{ github.event.before }}" ] && [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ] && [ -n "${{ github.event.after }}" ]; then 62 | # Verify the before commit exists in the repo 63 | if git cat-file -e ${{ github.event.before }}; then 64 | echo "Linting push commits from ${{ github.event.before }} to ${{ github.event.after }}" 65 | npx commitlint --from ${{ github.event.before }} --to ${{ github.event.after }} --verbose 66 | else 67 | echo "Before commit ${{ github.event.before }} not found, checking last commit only" 68 | npx commitlint --from HEAD~1 --to HEAD --verbose || echo "First commit, nothing to check" 69 | fi 70 | else 71 | echo "Initial push or empty before SHA, checking last commit only" 72 | # Check if we have at least one commit 73 | if git rev-parse HEAD~1 > /dev/null 2>&1; then 74 | npx commitlint --from HEAD~1 --to HEAD --verbose 75 | else 76 | echo "First commit in the repository, nothing to check" 77 | exit 0 78 | fi 79 | fi 80 | fi -------------------------------------------------------------------------------- /.github/workflows/dtvm_cpp_dev_docker_release.yml: -------------------------------------------------------------------------------- 1 | name: DTVM_CppSDK development Image Release CI 2 | 3 | on: 4 | # push: 5 | workflow_dispatch: 6 | branches: 7 | - main 8 | paths: 9 | - 'docker/**' 10 | permissions: 11 | contents: write 12 | 13 | jobs: 14 | devm-cpp-dev-docker-release: 15 | name: Build and release DTVM_CppSDK development docker image on linux 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Check out code 19 | uses: actions/checkout@v3 20 | # Prerequisite 21 | - name: Prepare Dockerfile and docker build dependencies 22 | run: ./docker/docker_build.sh prepare 23 | shell: bash 24 | - name: Login to Docker Hub 25 | uses: docker/login-action@v2 26 | with: 27 | username: ${{ secrets.DOCKER_USERNAME }} 28 | password: ${{ secrets.DOCKER_PASSWORD }} 29 | - name: Extract metadata (tags, labels) for Docker 30 | id: meta 31 | uses: docker/metadata-action@v4 32 | with: 33 | images: dtvmdev1/dtvm-cpp-dev-x64 34 | - name: Build and push Docker image 35 | uses: docker/build-push-action@v3 36 | with: 37 | context: docker 38 | push: ${{ github.event_name != 'pull_request' }} 39 | tags: ${{ steps.meta.outputs.tags }} 40 | labels: ${{ steps.meta.outputs.labels }} 41 | -------------------------------------------------------------------------------- /.github/workflows/dtvm_cpp_test.yml: -------------------------------------------------------------------------------- 1 | name: DTVM_CppSDK test CI 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'docker/**' 7 | - "docs/**" 8 | - "*.md" 9 | - ".gitignore" 10 | pull_request: 11 | paths-ignore: 12 | - 'docker/**' 13 | - "docs/**" 14 | - "*.md" 15 | - ".gitignore" 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | build_test_on_x86: 21 | name: Build and test DTVM_CppSDK on x86 22 | runs-on: ubuntu-latest 23 | container: 24 | image: dtvmdev1/dtvm-cpp-dev-x64:main 25 | steps: 26 | - name: Check out code 27 | uses: actions/checkout@v3 28 | with: 29 | lfs: true 30 | submodules: "true" 31 | - name: Run Tests 32 | run: | 33 | export CUR_PATH=$(pwd) 34 | cd /opt 35 | cp $CUR_PATH/tools/chain_mockcli/linux_x86/chain_mockcli-linux-ubuntu22.04-0.1.0.zip chain_mockcli.zip 36 | unzip chain_mockcli.zip 37 | chmod +x chain_mockcli 38 | 39 | # install rust 40 | /opt/install_rust.sh 41 | 42 | cd $CUR_PATH 43 | export LLVM_SYS_160_PREFIX=/opt/llvm16 44 | export LLVM_DIR=$LLVM_SYS_160_PREFIX/lib/cmake/llvm 45 | export PATH=$LLVM_SYS_160_PREFIX/bin:$PATH 46 | 47 | . "$HOME/.cargo/env" 48 | export PATH=$HOME/.cargo/bin:$PATH 49 | 50 | ./tools/format.sh check 51 | 52 | export PATH=/opt:$PATH 53 | cd $CUR_PATH 54 | 55 | cd /opt/emsdk 56 | ./emsdk activate 3.1.69 57 | . "/opt/emsdk/emsdk_env.sh" 58 | emcc --version 59 | 60 | cd $CUR_PATH 61 | ./tools/format.sh check 62 | 63 | # build solidcpp 64 | cd solidcpp 65 | cargo build --release 66 | cp target/release/solidcpp /opt/ 67 | export PATH=/opt:$PATH 68 | 69 | # example1 test 70 | cd $CUR_PATH/examples/example1 71 | # build wasm 72 | ./build_wasm.sh 73 | ./test_in_mock_cli.sh 74 | 75 | # erc20 test 76 | cd $CUR_PATH/examples/erc20 77 | # build wasm 78 | ./build_wasm.sh 79 | ./test_in_mock_cli.sh 80 | 81 | # unit test 82 | # prepare cpp test framework 83 | cd $CUR_PATH 84 | cp /opt/googletest-1.15.2.tar.gz . 85 | tar -xzvf googletest-1.15.2.tar.gz 86 | mv googletest-1.15.2 cpp_tests/googletest 87 | 88 | # code format check 89 | make fmt_check 90 | 91 | # run unit tests 92 | cd $CUR_PATH/cpp_tests 93 | cmake -S . -B build 94 | cmake --build build -j6 95 | cd build && ./runUnitTests 96 | 97 | cd $CUR_PATH 98 | 99 | # build solidcpp 100 | cd solidcpp 101 | cargo test -- --nocapture 102 | -------------------------------------------------------------------------------- /.github/workflows/nightly_release.yml: -------------------------------------------------------------------------------- 1 | name: DTVM_CppSDK Nightly Release 2 | 3 | on: 4 | workflow_dispatch: 5 | paths: 6 | - '**' 7 | 8 | permissions: 9 | contents: write # Needed to create releases/upload artifacts 10 | 11 | jobs: 12 | build_and_package: 13 | name: Build and Package DTVM_CppSDK Nightly 14 | runs-on: ubuntu-latest 15 | container: 16 | image: dtvmdev1/dtvm-cpp-dev-x64:main # Use the same dev container as the test workflow 17 | steps: 18 | - name: Check out code 19 | uses: actions/checkout@v4 20 | with: 21 | lfs: true 22 | submodules: "true" 23 | 24 | - name: Run package script 25 | run: | 26 | export CUR_PATH=$(pwd) 27 | # Make package script executable 28 | chmod +x .ci/package.sh 29 | # Run the package script 30 | .ci/package.sh 31 | # The script should create DTVM_CppSDK.nightly.latest.tar.gz in tmp_build 32 | 33 | - name: Upload Nightly Artifact 34 | uses: actions/upload-artifact@v4 35 | with: 36 | name: dtvm-cppsdk-nightly-latest 37 | path: tmp_build/DTVM_CppSDK.nightly.latest.tar.gz -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: DTVM_CppSDK Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' # Trigger on version tags like v1.0.0 7 | 8 | permissions: 9 | contents: write # Needed to create releases/upload artifacts 10 | 11 | jobs: 12 | build_release_and_upload: 13 | name: Build, Create Release, and Upload Artifact 14 | runs-on: ubuntu-latest 15 | container: 16 | image: dtvmdev1/dtvm-cpp-dev-x64:main # Use the same dev container 17 | steps: 18 | - name: Check out code 19 | uses: actions/checkout@v4 20 | with: 21 | lfs: true 22 | submodules: "true" 23 | 24 | - name: Run package script 25 | run: | 26 | export CUR_PATH=$(pwd) 27 | # Make package script executable 28 | chmod +x .ci/package.sh 29 | # Run the package script 30 | .ci/package.sh 31 | # The script creates DTVM_CppSDK.nightly.latest.tar.gz in tmp_build 32 | 33 | - name: Prepare Release Artifact 34 | id: prepare_artifact 35 | run: | 36 | cd tmp_build 37 | RELEASE_VERSION=${{ github.ref_name }} 38 | ARTIFACT_NAME="DTVM_CppSDK.${RELEASE_VERSION}.tar.gz" 39 | mv DTVM_CppSDK.nightly.latest.tar.gz "${ARTIFACT_NAME}" 40 | echo "artifact_path=$(pwd)/${ARTIFACT_NAME}" >> $GITHUB_OUTPUT 41 | echo "artifact_name=${ARTIFACT_NAME}" >> $GITHUB_OUTPUT 42 | cd .. 43 | 44 | - name: Create GitHub Release 45 | uses: softprops/action-gh-release@v2 46 | with: 47 | files: ${{ steps.prepare_artifact.outputs.artifact_path }} 48 | tag_name: ${{ github.ref_name }} 49 | name: Release ${{ github.ref_name }} 50 | body: "Release for DTVM C++ SDK version ${{ github.ref_name }}" 51 | prerelease: true # Set to true if you want pre-releases 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /cmake-build-debug 3 | /cmake-build-release 4 | /logs 5 | /build 6 | *.log 7 | /solc-windows 8 | /solc-macos 9 | /generated 10 | /*.wasm 11 | /*.wat 12 | /test.db 13 | .DS_Store 14 | /.vscode 15 | 16 | # scan tmp 17 | /cov-analysis-* 18 | /tmp_build 19 | /idir 20 | /coverity-result.json 21 | /__MACOSX 22 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at {{ email }}. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to DTVM_CppSDK 2 | 3 | Thank you for considering contributing to DTVM_CppSDK! This document provides guidelines and instructions for contributing to this project. 4 | 5 | ## Code of Conduct 6 | 7 | Please be respectful and considerate of others when contributing to this project. 8 | 9 | ## How to Contribute 10 | 11 | 1. **Fork the repository** 12 | 2. **Create a branch**: `git checkout -b feature/your-feature-name` 13 | 3. **Make your changes** 14 | 4. **Test your changes**: Ensure that your changes don't break existing functionality 15 | 5. **Submit a pull request** 16 | 17 | ## Development Environment Setup 18 | 19 | 1. **Install dependencies**: 20 | ```bash 21 | # Ubuntu 22.04 (recommended) 22 | apt update -y 23 | apt install -y git python3 xz-utils 24 | 25 | # Install Emscripten 3.1.69 26 | mkdir -p /opt 27 | cd /opt 28 | git clone https://github.com/emscripten-core/emsdk.git 29 | cd emsdk 30 | ./emsdk install 3.1.69 31 | ./emsdk activate 3.1.69 32 | source "/opt/emsdk/emsdk_env.sh" 33 | ``` 34 | 35 | 2. **Clone the repository**: 36 | ```bash 37 | git clone https://github.com/DTVMStack/DTVM_CppSDK.git 38 | cd DTVM_CppSDK 39 | ``` 40 | 41 | 3. **Build the library**: 42 | ```bash 43 | bash build_lib.sh 44 | ``` 45 | 46 | ## Coding Standards 47 | 48 | - Follow the C++17 standard 49 | - Use consistent indentation (4 spaces) 50 | - Provide clear and concise comments 51 | - Write unit tests for new functionality 52 | - Document public API changes 53 | 54 | ## Pull Request Process 55 | 56 | 1. Ensure your code follows the coding standards 57 | 2. Update documentation if necessary 58 | 3. Include test cases for new functionality 59 | 4. Your pull request will be reviewed by maintainers 60 | 61 | ## Reporting Bugs 62 | 63 | Please report bugs using the issue tracker with the following information: 64 | - A clear, descriptive title 65 | - Steps to reproduce the issue 66 | - Expected behavior 67 | - Actual behavior 68 | - Environment details (OS, compiler version, etc.) 69 | 70 | ## Feature Requests 71 | 72 | Feature requests are welcome. Please provide: 73 | - A clear, descriptive title 74 | - Detailed description of the proposed feature 75 | - Any relevant examples or use cases 76 | 77 | Thank you for contributing to DTVM_CppSDK! 78 | -------------------------------------------------------------------------------- /LEGAL.md: -------------------------------------------------------------------------------- 1 | Legal Disclaimer 2 | 3 | Within this source code, the comments in Chinese shall be the original, governing version. Any comment in other languages are for reference only. In the event of any conflict between the Chinese language version comments and other language version comments, the Chinese language version shall prevail. 4 | 5 | 法律免责声明 6 | 7 | 关于代码注释部分,中文注释为官方版本,其它语言注释仅做参考。中文注释可能与其它语言注释存在不一致,当中文注释与其它语言注释存在不一致时,请以中文注释为准。 -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: Makefile echo 3 | 4 | echo: 5 | echo "stdlib make done" 6 | 7 | fmt: 8 | clang-format -i contractlib/v1/*.h contractlib/v1/*.hpp contractlib/v1/*.cpp cpp_tests/*.cpp cpp_tests/*.hpp 9 | 10 | fmt_check: 11 | clang-format --dry-run --Werror contractlib/v1/*.h contractlib/v1/*.hpp contractlib/v1/*.cpp cpp_tests/*.cpp cpp_tests/*.hpp 12 | 13 | .DEFAULT_GOAL := error 14 | 15 | error: 16 | @echo "Error: You must specify either 'make fmt/fmt_check'" 17 | @exit 1 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DTVM_CppSDK 2 | 3 | [![Build Status](https://travis-ci.org/DTVMStack/DTVM_CppSDK.svg?branch=master)](https://travis-ci.org/DTVMStack/DTVM_CppSDK) 4 | ![license](https://img.shields.io/badge/license-Apache--2.0-green.svg) 5 | 6 | 7 | DTVM_CppSDK is a C++ SDK for developing smart contracts on blockchain. This SDK is part of the DTVM ecosystem and enables developers to write smart contracts in C++ that compile to WebAssembly (WASM) bytecode for deployment on EVM-compatible wasm contract blockchains. 8 | 9 | ## Overview 10 | 11 | DTVM_CppSDK provides a familiar development environment for C++ developers while leveraging the performance benefits of WASM. It includes tools and libraries to simplify the development of blockchain-based applications. 12 | 13 | ## Dependencies 14 | 15 | * **solc** - Solidity compiler 16 | * **Rust** - Required for some build tools 17 | * **Emscripten 3.1.69** - C++ to WebAssembly compiler 18 | * **Clang** - With C++17 support 19 | 20 | ## Project Structure 21 | 22 | * **contractlib/** - Core contract library providing blockchain abstractions 23 | * **examples/** - Example contracts 24 | * **erc20/** - ERC20 token implementation 25 | * **c_erc20/** - Alternative ERC20 implementation 26 | * **example1/** - Basic token example with detailed comments 27 | * **docs/** - Documentation 28 | * **solidcpp/** - C++ to WASM compiler tooling 29 | 30 | ## Getting Started 31 | 32 | For detailed installation and usage instructions, see the [documentation](docs/readme.md). 33 | 34 | Quick start: 35 | ```bash 36 | # Build the library 37 | bash build_lib.sh 38 | 39 | # Navigate to an example 40 | cd examples/example1 41 | 42 | # Build the example 43 | bash build_wasm.sh 44 | 45 | # Test the example locally 46 | bash test_in_mock_cli.sh 47 | ``` 48 | 49 | ## Community 50 | [Issues](https://github.com/DTVMStack/DTVM_CppSDK/issues) - Report issues or request features. 51 | 52 | ## Contributing 53 | 54 | Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details. 55 | 56 | ## License 57 | 58 | This project is licensed under the [LICENSE](LICENSE) file. 59 | 60 | -------------------------------------------------------------------------------- /build_lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | em++ -std=c++17 -o contractlib/v1/contractlib.o -O2 -c contractlib/v1/contractlib.cpp -I . 5 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserPreset: 'conventional-changelog-conventionalcommits', 3 | rules: { 4 | 'body-leading-blank': [1, 'always'], 5 | 'body-max-line-length': [2, 'always', 100], 6 | 'footer-leading-blank': [1, 'always'], 7 | 'footer-max-line-length': [2, 'always', 100], 8 | 'header-max-length': [2, 'always', 100], 9 | 'header-trim': [2, 'always'], 10 | 'subject-case': [0, 'never'], 11 | 'subject-empty': [2, 'never'], 12 | 'subject-full-stop': [2, 'never', '.'], 13 | 'type-case': [2, 'always', 'lower-case'], 14 | 'type-empty': [2, 'never'], 15 | 'type-enum': [ 16 | 2, 17 | 'always', 18 | [ 19 | 'feat', 20 | 'fix', 21 | 'docs', 22 | 'style', 23 | 'refactor', 24 | 'perf', 25 | 'test', 26 | 'build', 27 | 'ci', 28 | 'chore' 29 | ] 30 | ], 31 | 'scope-enum': [ 32 | 2, 33 | 'always', 34 | [ 35 | 'core', 36 | 'api', 37 | 'contract', 38 | 'solidcpp', 39 | 'examples', 40 | 'docs', 41 | 'tools', 42 | 'deps', 43 | 'ci', 44 | 'test', 45 | 'other', 46 | '' 47 | ] 48 | ] 49 | }, 50 | prompt: { 51 | questions: { 52 | type: { 53 | description: 'Select the type of change that you\'re committing', 54 | enum: { 55 | feat: { 56 | description: 'A new feature', 57 | title: 'Features', 58 | emoji: '✨', 59 | }, 60 | fix: { 61 | description: 'A bugfix', 62 | title: 'Bug Fixes', 63 | emoji: '🐛', 64 | }, 65 | docs: { 66 | description: 'Documentation only changes', 67 | title: 'Documentation', 68 | emoji: '📚', 69 | }, 70 | style: { 71 | description: 'Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)', 72 | title: 'Styles', 73 | emoji: '💎', 74 | }, 75 | refactor: { 76 | description: 'A code change that neither fixes a bug nor adds a feature', 77 | title: 'Code Refactoring', 78 | emoji: '📦', 79 | }, 80 | perf: { 81 | description: 'A code change that improves performance', 82 | title: 'Performance Improvements', 83 | emoji: '🚀', 84 | }, 85 | test: { 86 | description: 'Adding missing tests or correcting existing tests', 87 | title: 'Tests', 88 | emoji: '🚨', 89 | }, 90 | build: { 91 | description: 'Changes that affect the build system or external dependencies (example: make, cmake)', 92 | title: 'Builds', 93 | emoji: '🛠', 94 | }, 95 | ci: { 96 | description: 'Changes to CI configuration files and scripts', 97 | title: 'Continuous Integrations', 98 | emoji: '⚙️', 99 | }, 100 | chore: { 101 | description: 'Other changes that don\'t modify src or test files', 102 | title: 'Chores', 103 | emoji: '♻️', 104 | } 105 | } 106 | }, 107 | scope: { 108 | description: 'What is the scope of this change (e.g. core, api, contract)', 109 | enum: { 110 | core: { 111 | description: 'Core library code', 112 | }, 113 | api: { 114 | description: 'API related', 115 | }, 116 | contract: { 117 | description: 'Contract library related', 118 | }, 119 | solidcpp: { 120 | description: 'SolidCpp compiler code', 121 | }, 122 | examples: { 123 | description: 'Example code', 124 | }, 125 | docs: { 126 | description: 'Documentation related', 127 | }, 128 | tools: { 129 | description: 'Tool related', 130 | }, 131 | deps: { 132 | description: 'Dependency related', 133 | }, 134 | ci: { 135 | description: 'CI related', 136 | }, 137 | test: { 138 | description: 'Test related', 139 | }, 140 | other: { 141 | description: 'Other changes', 142 | } 143 | } 144 | }, 145 | subject: { 146 | description: 'Write a short, imperative tense description of the change' 147 | }, 148 | body: { 149 | description: 'Provide a longer description of the change' 150 | }, 151 | isBreaking: { 152 | description: 'Are there any breaking changes?' 153 | }, 154 | breaking: { 155 | description: 'Describe the breaking changes' 156 | }, 157 | isIssueAffected: { 158 | description: 'Does this change affect any open issues?' 159 | }, 160 | issues: { 161 | description: 'Add issue references (e.g. "Closes #123, #456")' 162 | } 163 | } 164 | } 165 | }; -------------------------------------------------------------------------------- /contractlib/v1/.gitignore: -------------------------------------------------------------------------------- 1 | /*.o 2 | -------------------------------------------------------------------------------- /contractlib/v1/contractlib.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #include "storage.inc.cpp" 5 | #include "wasi.inc.cpp" 6 | -------------------------------------------------------------------------------- /contractlib/v1/hostapi.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #pragma once 5 | #include 6 | // Determine the type representing pointer integers based on the environment, 7 | // allowing for easier testing in 64-bit environments. 8 | 9 | #if INTPTR_MAX == INT64_MAX 10 | #define ADDRESS_UINT uint64_t 11 | #else 12 | #define ADDRESS_UINT uint32_t 13 | #define IN_WASM_ENV 14 | #endif 15 | 16 | extern "C" { 17 | // hostapis 18 | 19 | #ifndef NDEBUG 20 | __attribute__((import_module("env"), import_name("debug_bytes"))) void 21 | debug_bytes(ADDRESS_UINT data_offset, int32_t data_length); 22 | #else 23 | #define debug_bytes(data_offset, data_length) 24 | #endif 25 | 26 | __attribute__((import_module("env"), import_name("getAddress"))) void 27 | getAddress(ADDRESS_UINT result_offset); 28 | 29 | __attribute__((import_module("env"), import_name("getCaller"))) void 30 | getCaller(ADDRESS_UINT result_offset); 31 | 32 | __attribute__((import_module("env"), import_name("getCallValue"))) void 33 | getCallValue(ADDRESS_UINT result_offset); 34 | 35 | __attribute__((import_module("env"), import_name("getCallDataSize"))) int32_t 36 | getCallDataSize(); 37 | 38 | __attribute__((import_module("env"), import_name("callDataCopy"))) void 39 | callDataCopy(ADDRESS_UINT target, ADDRESS_UINT offset, int32_t len); 40 | 41 | __attribute__((import_module("env"), import_name("getBlockHash"))) int32_t 42 | getBlockHash(int64_t number, ADDRESS_UINT result_offset); 43 | 44 | __attribute__((import_module("env"), import_name("getBlockCoinbase"))) void 45 | getBlockCoinbase(ADDRESS_UINT result_offset); 46 | 47 | __attribute__((import_module("env"), import_name("getBlockPrevRandao"))) void 48 | getBlockPrevRandao(ADDRESS_UINT result_offset); 49 | 50 | __attribute__((import_module("env"), import_name("getBlockGasLimit"))) int64_t 51 | getBlockGasLimit(); 52 | 53 | __attribute__((import_module("env"), import_name("getBlockTimestamp"))) int64_t 54 | getBlockTimestamp(); 55 | 56 | __attribute__((import_module("env"), import_name("getGasLeft"))) int64_t 57 | getGasLeft(); 58 | 59 | __attribute__((import_module("env"), import_name("getBlockNumber"))) int64_t 60 | getBlockNumber(); 61 | 62 | __attribute__((import_module("env"), import_name("getTxGasPrice"))) void 63 | getTxGasPrice(ADDRESS_UINT value_offset); 64 | 65 | __attribute__((import_module("env"), import_name("getTxOrigin"))) void 66 | getTxOrigin(ADDRESS_UINT result_offset); 67 | 68 | __attribute__((import_module("env"), import_name("getBaseFee"))) void 69 | getBaseFee(ADDRESS_UINT result_offset); 70 | 71 | __attribute__((import_module("env"), import_name("getBlobBaseFee"))) void 72 | getBlobBaseFee(ADDRESS_UINT result_offset); 73 | 74 | __attribute__((import_module("env"), import_name("getChainId"))) void 75 | getChainId(ADDRESS_UINT result_offset); 76 | 77 | __attribute__((import_module("env"), import_name("getExternalBalance"))) void 78 | getExternalBalance(ADDRESS_UINT address_offset, ADDRESS_UINT result_offset); 79 | 80 | __attribute__((import_module("env"), import_name("getExternalCodeHash"))) void 81 | getExternalCodeHash(ADDRESS_UINT address_offset, ADDRESS_UINT result_offset); 82 | 83 | __attribute__((import_module("env"), import_name("storageLoad"))) void 84 | storageLoad(ADDRESS_UINT key_offset, ADDRESS_UINT result_offset); 85 | 86 | __attribute__((import_module("env"), import_name("storageStore"))) void 87 | storageStore(ADDRESS_UINT key_offset, ADDRESS_UINT value_offset); 88 | 89 | __attribute__((import_module("env"), import_name("transientStore"))) void 90 | transientStore(ADDRESS_UINT key_offset, ADDRESS_UINT value_offset); 91 | 92 | __attribute__((import_module("env"), import_name("transientLoad"))) void 93 | transientLoad(ADDRESS_UINT key_offset, ADDRESS_UINT result_offset); 94 | 95 | __attribute__((import_module("env"), import_name("codeCopy"))) void 96 | codeCopy(ADDRESS_UINT result_offset, int32_t code_offset, int32_t length); 97 | 98 | __attribute__((import_module("env"), import_name("getCodeSize"))) int32_t 99 | getCodeSize(); 100 | 101 | __attribute__((import_module("env"), import_name("externalCodeCopy"))) void 102 | externalCodeCopy(ADDRESS_UINT address_offset, ADDRESS_UINT result_offset, 103 | int32_t code_offset, int32_t length); 104 | 105 | __attribute__((import_module("env"), import_name("getExternalCodeSize"))) 106 | int32_t 107 | getExternalCodeSize(ADDRESS_UINT address_offset); 108 | 109 | __attribute__((import_module("env"), import_name("callContract"))) int32_t 110 | callContract(int64_t gas, ADDRESS_UINT addressOffset, ADDRESS_UINT valueOffset, 111 | ADDRESS_UINT dataOffset, int32_t dataLength); 112 | 113 | __attribute__((import_module("env"), import_name("callCode"))) int32_t 114 | callCode(int64_t gas, ADDRESS_UINT addressOffset, ADDRESS_UINT valueOffset, 115 | ADDRESS_UINT dataOffset, int32_t dataLength); 116 | 117 | __attribute__((import_module("env"), import_name("callDelegate"))) int32_t 118 | callDelegate(int64_t gas, ADDRESS_UINT addressOffset, ADDRESS_UINT dataOffset, 119 | int32_t dataLength); 120 | 121 | __attribute__((import_module("env"), import_name("callStatic"))) int32_t 122 | callStatic(int64_t gas, ADDRESS_UINT addressOffset, ADDRESS_UINT dataOffset, 123 | int32_t dataLength); 124 | 125 | __attribute__((import_module("env"), import_name("createContract"))) int32_t 126 | createContract(ADDRESS_UINT valueOffset, ADDRESS_UINT codeOffset, 127 | int32_t codeLength, ADDRESS_UINT dataOffset, int32_t dataLength, 128 | ADDRESS_UINT saltOffset, int32_t is_create2, 129 | ADDRESS_UINT resultOffset); 130 | 131 | __attribute__((import_module("env"), import_name("finish"))) void 132 | finish(ADDRESS_UINT data_offset, int32_t length); 133 | 134 | __attribute__((import_module("env"), import_name("revert"))) void 135 | revert(ADDRESS_UINT data_offset, int32_t length); 136 | 137 | __attribute__((import_module("env"), import_name("emitLogEvent"))) void 138 | emitLogEvent(ADDRESS_UINT data_offset, int32_t length, int32_t number_of_topics, 139 | ADDRESS_UINT topic1, ADDRESS_UINT topic2, ADDRESS_UINT topic3, 140 | ADDRESS_UINT topic4); 141 | 142 | __attribute__((import_module("env"), import_name("getReturnDataSize"))) int32_t 143 | getReturnDataSize(); 144 | 145 | __attribute__((import_module("env"), import_name("returnDataCopy"))) void 146 | returnDataCopy(ADDRESS_UINT resultOffset, int32_t dataOffset, int32_t length); 147 | 148 | __attribute__((import_module("env"), import_name("selfDestruct"))) void 149 | selfDestruct(ADDRESS_UINT addressOffset); 150 | 151 | __attribute__((import_module("env"), import_name("keccak256"))) void 152 | keccak256(ADDRESS_UINT inputOffset, int32_t inputLength, 153 | ADDRESS_UINT resultOffset); 154 | 155 | __attribute__((import_module("env"), import_name("sha256"))) void 156 | sha256(ADDRESS_UINT inputOffset, int32_t inputLength, 157 | ADDRESS_UINT resultOffset); 158 | } 159 | -------------------------------------------------------------------------------- /contractlib/v1/hostio.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #pragma once 5 | #include "hostapi.h" 6 | #include "math.hpp" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace dtvm { 14 | 15 | #ifndef NDEBUG 16 | inline void debug_print(const std::string &str) { 17 | ::debug_bytes((ADDRESS_UINT) reinterpret_cast(str.data()), 18 | (int32_t)str.size()); 19 | } 20 | #else 21 | #define debug_print(str) 22 | #endif 23 | 24 | namespace hostio { 25 | 26 | inline void finish(const uint8_t *data, int32_t len) { 27 | ::finish((ADDRESS_UINT) reinterpret_cast(data), len); 28 | } 29 | 30 | inline void revert(const uint8_t *data, int32_t len) { 31 | ::revert((ADDRESS_UINT) reinterpret_cast(data), len); 32 | } 33 | 34 | inline void revert(const std::string &msg) { 35 | ::revert((ADDRESS_UINT) reinterpret_cast(msg.data()), 36 | (int32_t)msg.size()); 37 | } 38 | 39 | inline uint8_t *get_args() { 40 | int len = getCallDataSize(); 41 | if (len <= 0) { 42 | return nullptr; 43 | } 44 | uint8_t *args = (uint8_t *)malloc(len); 45 | if (!args) { 46 | revert("malloc failed"); 47 | return nullptr; 48 | } 49 | callDataCopy((ADDRESS_UINT) reinterpret_cast(args), 0, len); 50 | return args; 51 | } 52 | inline uint32_t get_args_len() { return (uint32_t)getCallDataSize(); } 53 | 54 | inline void emit_log(const std::vector> &topics, 55 | const std::vector &data) { 56 | int topics_count = (int)topics.size(); 57 | const uint8_t *topic1 = nullptr; 58 | const uint8_t *topic2 = nullptr; 59 | const uint8_t *topic3 = nullptr; 60 | const uint8_t *topic4 = nullptr; 61 | if (topics_count > 0) { 62 | topic1 = topics[0].data(); 63 | } 64 | if (topics_count > 1) { 65 | topic2 = topics[1].data(); 66 | } 67 | if (topics_count > 2) { 68 | topic3 = topics[2].data(); 69 | } 70 | if (topics_count > 3) { 71 | topic4 = topics[3].data(); 72 | } 73 | ::emitLogEvent((ADDRESS_UINT) reinterpret_cast(data.data()), 74 | (int32_t)data.size(), (int32_t)topics_count, 75 | (ADDRESS_UINT) reinterpret_cast(topic1), 76 | (ADDRESS_UINT) reinterpret_cast(topic2), 77 | (ADDRESS_UINT) reinterpret_cast(topic3), 78 | (ADDRESS_UINT) reinterpret_cast(topic4)); 79 | } 80 | 81 | inline uint64_t get_gas_left() { return (uint64_t)::getGasLeft(); } 82 | 83 | inline bytes32 read_storage(const dtvm::uint256 &key) { 84 | // if not exist, return 32 bytes zero 85 | bytes32 result = {0}; 86 | ::storageLoad((ADDRESS_UINT) reinterpret_cast(key.bytes().data()), 87 | (ADDRESS_UINT) reinterpret_cast(result.data())); 88 | return result; 89 | } 90 | 91 | inline void write_storage(const dtvm::uint256 &key, const bytes32 &value) { 92 | ::storageStore((ADDRESS_UINT) reinterpret_cast(key.bytes().data()), 93 | (ADDRESS_UINT) reinterpret_cast(value.data())); 94 | } 95 | 96 | inline bytes32 keccak256(const std::vector &data) { 97 | bytes32 result; 98 | ::keccak256((ADDRESS_UINT) reinterpret_cast(data.data()), 99 | (int32_t)data.size(), 100 | (ADDRESS_UINT) reinterpret_cast(result.data())); 101 | return result; 102 | } 103 | 104 | inline bytes32 keccak256(const bytes32 &data) { 105 | bytes32 result{}; 106 | ::keccak256((ADDRESS_UINT) reinterpret_cast(data.data()), 107 | (int32_t)data.size(), 108 | (ADDRESS_UINT) reinterpret_cast(result.data())); 109 | return result; 110 | } 111 | 112 | // Wrap these call functions to have a unified function signature, making it 113 | // easier to call them using templates 114 | inline int32_t callDelegateWithZeroValue(int64_t gas, int32_t addressOffset, 115 | int32_t valueOffset, 116 | int32_t dataOffset, 117 | int32_t dataLength) { 118 | return ::callDelegate(gas, addressOffset, dataOffset, dataLength); 119 | } 120 | inline int32_t callStaticWithZeroValue(int64_t gas, int32_t addressOffset, 121 | int32_t valueOffset, int32_t dataOffset, 122 | int32_t dataLength) { 123 | return ::callStatic(gas, addressOffset, dataOffset, dataLength); 124 | } 125 | } // namespace hostio 126 | } // namespace dtvm 127 | -------------------------------------------------------------------------------- /contractlib/v1/math.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #pragma once 5 | #include "utils.hpp" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace dtvm { 12 | 13 | typedef std::array bytes32; 14 | typedef std::array bytes20; 15 | 16 | struct uint256 { 17 | __uint128_t high; 18 | __uint128_t low; 19 | uint256() : high(0), low(0) {} 20 | uint256(__uint128_t low) : high(0), low(low) {} 21 | uint256(const uint256 &other) : high(other.high), low(other.low) {} 22 | 23 | uint256(__uint128_t high_part, __uint128_t low_part) 24 | : high(high_part), low(low_part) {} 25 | uint256(const bytes32 &bs) { // big endian bytes 26 | __uint128_t highB_bytes = 0, low_bytes = 0; 27 | // Convert byte sequence from big-endian to little-endian __uint128_t 28 | for (size_t i = 0; i < sizeof(high); ++i) { 29 | highB_bytes |= static_cast<__uint128_t>(bs[i]) 30 | << (8 * (sizeof(high) - i - 1)); 31 | } 32 | for (size_t i = 0; i < sizeof(low); ++i) { 33 | low_bytes |= static_cast<__uint128_t>(bs[i + 16]) 34 | << (8 * (sizeof(low) - i - 1)); 35 | } 36 | high = highB_bytes; 37 | low = low_bytes; 38 | } 39 | // Addition operator 40 | uint256 operator+(const uint256 &other) const { 41 | __uint128_t newLow = low + other.low; 42 | __uint128_t carry = newLow < low; // Check if there is a carry 43 | newLow = newLow & 44 | ~(carry << (sizeof(__uint128_t) * 8 - 1)); // Clear the carry bit 45 | __uint128_t newHigh = high + other.high + carry; 46 | return uint256(newHigh, newLow); 47 | } 48 | 49 | uint256 operator-(const uint256 &other) const { 50 | __uint128_t newLow = low - other.low; 51 | __uint128_t borrow = newLow > low; // Check if there is a borrow 52 | newLow = newLow & 53 | ~(borrow << (sizeof(__uint128_t) * 8 - 1)); // Clear the borrow bit 54 | __uint128_t newHigh = high - other.high - borrow; 55 | return uint256(newHigh, newLow); 56 | } 57 | 58 | uint256 operator>>(size_t shift) const { 59 | if (shift == 0) { 60 | return *this; 61 | } 62 | if (shift == 128) { 63 | return uint256(0, high); 64 | } 65 | shift = 66 | shift > 256 ? 256 : shift; // Limit the shift to no more than 256 bits 67 | uint256 result; 68 | if (shift > 128) { 69 | // all low shifted 70 | result.low = high >> (shift - 128); 71 | result.high = 0; 72 | } else { 73 | result.high = high >> shift; 74 | result.low = (low >> shift) + (high << (128 - shift)); 75 | } 76 | return result; 77 | } 78 | 79 | uint256 operator<<(size_t shift) const { 80 | if (shift == 0) { 81 | return *this; 82 | } 83 | if (shift == 128) { 84 | return uint256(low, 0); 85 | } 86 | shift = 87 | shift > 256 ? 256 : shift; // Limit the shift to no more than 256 bits 88 | uint256 result; 89 | if (shift > 128) { 90 | // all high shifted 91 | result.high = low << (shift - 128); 92 | result.low = 0; 93 | } else { 94 | result.high = (high << shift) + (low >> (128 - shift)); 95 | result.low = low << shift; 96 | } 97 | return result; 98 | } 99 | 100 | uint256 operator&(const uint256 &rhs) const { 101 | return uint256(high & rhs.high, low & rhs.low); 102 | } 103 | 104 | uint256 operator|(const uint256 &rhs) const { 105 | return uint256(high | rhs.high, low | rhs.low); 106 | } 107 | 108 | uint256 operator^(const uint256 &rhs) const { 109 | return uint256(high ^ rhs.high, low ^ rhs.low); 110 | } 111 | 112 | static uint256 multiply128(const __uint128_t &a, const __uint128_t &b) { 113 | __uint128_t a_high = a >> 64; 114 | __uint128_t a_low = a & 0xFFFFFFFFFFFFFFFFull; 115 | __uint128_t b_high = b >> 64; 116 | __uint128_t b_low = b & 0xFFFFFFFFFFFFFFFFull; 117 | // new low part = a.low * b.low 118 | // new high part = a.high * b.low + b.high * a.low + overflow part from 119 | // (a.low * b.low) overflow high part = a.high * b.high + overflow part from 120 | // (new high part) 121 | __uint128_t new_low = a_low * b_low; 122 | __uint128_t new_high = a_high * b_low + b_high * a_low + (new_low >> 64); 123 | __uint128_t overflow_high = a_high * b_high + (new_high >> 64); 124 | __uint128_t not_overflow_part = ((new_high & 0xFFFFFFFFFFFFFFFFull) << 64) + 125 | (new_low & 0xFFFFFFFFFFFFFFFFull); 126 | return uint256(overflow_high, not_overflow_part); 127 | } 128 | 129 | uint256 operator*(const uint256 &other) const { 130 | __uint128_t a = low, b = other.low; 131 | __uint128_t ah = high, bh = other.high; 132 | // The high part * high part result has already overflowed, so no need to 133 | // consider it new high part = n1.high * n2.low + n2.high * n1.low + 134 | // overflow part (n1.low * n2.low) new low part = n1.low * n2.low 135 | __uint128_t new_high = ah * b + bh * a + multiply128(a, b).high; 136 | __uint128_t new_low = a * b; 137 | return uint256(new_high, new_low); 138 | } 139 | 140 | // TODO: operator / , % 141 | 142 | bool operator==(const uint256 &other) const { 143 | return high == other.high && low == other.low; 144 | } 145 | 146 | bool operator!=(const uint256 &other) const { return !(*this == other); } 147 | 148 | bool operator<(const uint256 &other) const { 149 | if (high < other.high) { 150 | return true; 151 | } else if (high == other.high) { 152 | return low < other.low; 153 | } else { 154 | return false; 155 | } 156 | } 157 | 158 | bool operator<=(const uint256 &other) const { 159 | return (*this == other) || (*this < other); 160 | } 161 | 162 | bool operator>(const uint256 &other) const { 163 | if (high > other.high) { 164 | return true; 165 | } else if (high == other.high) { 166 | return low > other.low; 167 | } else { 168 | return false; 169 | } 170 | } 171 | 172 | bool operator>=(const uint256 &other) const { 173 | return (*this == other) || (*this > other); 174 | } 175 | 176 | uint256 &operator=(const uint256 &other) { 177 | high = other.high; 178 | low = other.low; 179 | return *this; 180 | } 181 | 182 | static uint256 max() { 183 | return uint256((__uint128_t)__int128_t(-1), (__uint128_t)__int128_t(-1)); 184 | } 185 | 186 | bytes32 bytes() const { 187 | // as big endian 188 | bytes32 result; 189 | auto bytes_high = reinterpret_cast(&high); 190 | auto bytes_low = reinterpret_cast(&low); 191 | // big endian byte order 192 | for (size_t i = 0; i < sizeof(high); ++i) { 193 | result[i] = bytes_high[sizeof(high) - i - 1]; 194 | } 195 | for (size_t i = 0; i < sizeof(low); ++i) { 196 | result[i + 16] = bytes_low[sizeof(low) - i - 1]; 197 | } 198 | return result; 199 | } 200 | 201 | __uint128_t to_uint128() const { return low; } 202 | 203 | uint64_t to_uint64() const { return uint64_t(low); } 204 | 205 | uint32_t to_uint32() const { return uint32_t(low); } 206 | 207 | uint16_t to_uint16() const { return uint16_t(low); } 208 | 209 | uint8_t to_uint8() const { return uint8_t(low); } 210 | }; 211 | } // namespace dtvm 212 | -------------------------------------------------------------------------------- /contractlib/v1/storage.inc.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #include "hostio.hpp" 5 | #include "storage_slot.hpp" 6 | #include 7 | 8 | namespace dtvm { 9 | 10 | std::vector decode_bytes_or_string_from_slot(const StorageSlot &slot) { 11 | const auto &cur_slot_bytes = hostio::read_storage(slot.get_slot()); 12 | // Find the right non-zero byte in cur_slot_bytes (if any). If it's odd, the 13 | // length of the bytes to decode 14 | bool longer_than_31_bytes = false; 15 | uint32_t length = 0; 16 | for (int i = 31; i >= 0; --i) { 17 | uint8_t c = cur_slot_bytes[i]; 18 | if (c == 0) { 19 | continue; 20 | } 21 | if (c % 2 != 0) { // Odd number, indicates bytes longer than 31 bytes 22 | longer_than_31_bytes = true; 23 | length = (uint256(cur_slot_bytes).to_uint32() - 1) / 2; 24 | if (length > 2048) { 25 | throw std::runtime_error( 26 | std::string("too large bytes length when decoding: ") + 27 | std::to_string(length)); 28 | } 29 | break; 30 | } else { 31 | // If the length is less than or equal to 31 bytes, the last byte 32 | // indicates the length multiplied by 2 33 | length = uint32_t(c) / 2; 34 | break; 35 | } 36 | } 37 | 38 | if (!longer_than_31_bytes) { 39 | // <= 31 bytes 40 | if (length > 31) { 41 | throw std::runtime_error("invalid bytes length"); 42 | } 43 | std::vector ret(length); 44 | memcpy(ret.data(), cur_slot_bytes.data(), length); 45 | return ret; 46 | } else { 47 | // > 31 bytes 48 | std::vector ret(length); 49 | const auto &content_begin_slot = 50 | uint256(hostio::keccak256(slot.to_bytes32())); 51 | for (size_t i = 0; i < length / 32; i++) { 52 | const auto &item_slot = content_begin_slot + uint256(i); 53 | const auto &item_bytes = hostio::read_storage(item_slot); 54 | memcpy(ret.data() + i * 32, item_bytes.data(), 32); 55 | } 56 | auto remaining_length = length % 32; 57 | if (remaining_length != 0) { 58 | const auto &item_slot = content_begin_slot + uint256(length / 32); 59 | const auto &item_bytes = hostio::read_storage(item_slot); 60 | memcpy(ret.data() + length - remaining_length, item_bytes.data(), 61 | remaining_length); 62 | } 63 | return ret; 64 | } 65 | } 66 | 67 | void encode_and_store_bytes_or_string_in_storage_slot( 68 | const StorageSlot &slot, const std::vector &bytes) { 69 | auto length = bytes.size(); 70 | if (length <= 31) { 71 | bytes32 encoded; 72 | memcpy(encoded.data(), bytes.data(), length); 73 | encoded[length] = uint8_t(length * 2); 74 | if (length < 31) { 75 | memset(encoded.data() + length + 1, 0x0, 31 - length); 76 | } 77 | hostio::write_storage(slot.get_slot(), encoded); 78 | return; 79 | } 80 | // length > 31 81 | bytes32 length_bytes = uint256(length * 2 + 1).bytes(); 82 | hostio::write_storage(slot.get_slot(), length_bytes); 83 | const auto &content_begin_slot = 84 | uint256(hostio::keccak256(slot.to_bytes32())); 85 | for (size_t i = 0; i < length / 32; i++) { 86 | const auto &item_slot = content_begin_slot + uint256(i); 87 | bytes32 item_bytes; 88 | memcpy(item_bytes.data(), bytes.data() + i * 32, 32); 89 | hostio::write_storage(item_slot, item_bytes); 90 | } 91 | if (length % 32 != 0) { 92 | const auto &item_slot = content_begin_slot + uint256(length / 32); 93 | bytes32 item_bytes; 94 | memset(item_bytes.data(), 0x0, 32); 95 | memcpy(item_bytes.data(), bytes.data() + length - length % 32, length % 32); 96 | hostio::write_storage(item_slot, item_bytes); 97 | } 98 | } 99 | 100 | } // namespace dtvm -------------------------------------------------------------------------------- /contractlib/v1/storage_slot.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #pragma once 5 | #include "math.hpp" 6 | #include 7 | #include 8 | 9 | namespace dtvm { 10 | class StorageSlot { 11 | public: 12 | inline StorageSlot() { 13 | slot_ = uint256::max(); 14 | offset_ = 0; 15 | } 16 | inline StorageSlot(int slot, uint32_t offset) { 17 | slot_ = uint256(slot); 18 | offset_ = offset; 19 | } 20 | inline StorageSlot(uint256 slot, uint32_t offset) { 21 | slot_ = slot; 22 | offset_ = offset; 23 | } 24 | inline bool is_valid() { return slot_ != uint256::max(); } 25 | inline uint256 get_slot() const { return slot_; } 26 | inline uint32_t get_offset() const { return offset_; } 27 | inline bytes32 to_bytes32() const { return slot_.bytes(); } 28 | 29 | private: 30 | uint256 slot_; 31 | // Multiple state variables may exist in the same slot, offset_ indicates the 32 | // offset 33 | uint32_t offset_; 34 | }; 35 | } // namespace dtvm -------------------------------------------------------------------------------- /contractlib/v1/types.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #pragma once 5 | #include "math.hpp" 6 | #include "utils.hpp" 7 | #include 8 | #include 9 | 10 | namespace dtvm { 11 | class Address { 12 | public: 13 | Address() { data_ = {0}; } 14 | Address(const bytes32 &bs) { ::memcpy(data_.data(), bs.data() + 12, 20); } 15 | Address(const std::string &hex_str) { 16 | const auto &bs = unhex(hex_str); 17 | ::memcpy(data_.data(), bs.data(), 20); 18 | } 19 | 20 | bytes32 to_bytes32() const { 21 | bytes32 result = {0}; 22 | ::memcpy(result.data() + 12, data_.data(), 20); 23 | return result; 24 | } 25 | const uint8_t *data() const { return data_.data(); } 26 | 27 | bool operator==(const Address &rhs) const { 28 | return ::memcmp(data_.data(), rhs.data(), 20) == 0; 29 | } 30 | 31 | public: 32 | static Address zero() { return Address(); } 33 | 34 | private: 35 | bytes20 data_; 36 | }; 37 | 38 | class Bytes { 39 | public: 40 | Bytes(const std::vector &data) : data_(data) {} 41 | Bytes() {} 42 | 43 | const uint8_t *data() const { return data_.data(); } 44 | 45 | size_t size() const { return data_.size(); } 46 | 47 | const std::vector &bytes() const { return data_; } 48 | 49 | private: 50 | std::vector data_; 51 | }; 52 | } // namespace dtvm 53 | -------------------------------------------------------------------------------- /contractlib/v1/utils.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #pragma once 5 | #include "hostapi.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace dtvm { 14 | inline std::vector unpadded_string(const std::string &str) { 15 | size_t need_pad_zeros = 31 - (str.size() + 31) % 32; 16 | std::vector unpadded_key; 17 | for (size_t i = 0; i < str.size(); i++) { 18 | unpadded_key.push_back((uint8_t)str[i]); 19 | } 20 | for (size_t i = 0; i < need_pad_zeros; i++) { 21 | unpadded_key.push_back('\0'); 22 | } 23 | return unpadded_key; 24 | } 25 | 26 | inline std::array as_bytes32(const std::vector &data) { 27 | std::array ret; 28 | for (size_t i = 0; i < 32; i++) { 29 | if (i < data.size()) { 30 | ret[i] = data[i]; 31 | } else { 32 | ret[i] = 0; 33 | } 34 | } 35 | return ret; 36 | } 37 | 38 | inline std::string hex(const std::vector &bytes) { 39 | std::stringstream ss; 40 | for (const auto &byte : bytes) { 41 | ss << std::hex << std::setw(2) << std::setfill('0') 42 | << static_cast(byte); 43 | } 44 | return ss.str(); 45 | } 46 | 47 | inline std::string hex(const std::array &bytes) { 48 | std::stringstream ss; 49 | for (const auto &byte : bytes) { 50 | ss << std::hex << std::setw(2) << std::setfill('0') 51 | << static_cast(byte); 52 | } 53 | return ss.str(); 54 | } 55 | 56 | inline std::string hex(const std::array &bytes) { 57 | std::stringstream ss; 58 | for (const auto &byte : bytes) { 59 | ss << std::hex << std::setw(2) << std::setfill('0') 60 | << static_cast(byte); 61 | } 62 | return ss.str(); 63 | } 64 | 65 | inline std::vector unhex(const std::string &str) { 66 | std::vector ret; 67 | size_t i = 0; 68 | if (str.size() >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { 69 | i = 2; // ignore 0x/0X prefix 70 | } 71 | if (str.size() % 2 != 0) { 72 | std::string msg("unhex error: odd length string"); 73 | ::revert((int32_t) reinterpret_cast(msg.data()), 74 | (int32_t)msg.size()); 75 | } 76 | for (; i < str.size(); i += 2) { 77 | uint8_t char1 = str[i]; 78 | uint8_t char2 = str[i + 1]; 79 | uint32_t char1_digit, char2_digit; 80 | if (char1 >= '0' && char1 <= '9') { 81 | char1_digit = (int32_t)(char1 - '0'); 82 | } else if (char1 >= 'a' && char1 <= 'f') { 83 | char1_digit = 10 + (int32_t)(char1 - 'a'); 84 | } else if (char1 >= 'A' && char1 <= 'F') { 85 | char1_digit = 10 + (int32_t)(char1 - 'A'); 86 | } else { 87 | std::string msg = std::string("unhex error: invalid hex ") + str; 88 | ::revert((int32_t) reinterpret_cast(msg.data()), 89 | (int32_t)msg.size()); 90 | } 91 | 92 | if (char2 >= '0' && char2 <= '9') { 93 | char2_digit = (int32_t)(char2 - '0'); 94 | } else if (char2 >= 'a' && char2 <= 'f') { 95 | char2_digit = 10 + (int32_t)(char2 - 'a'); 96 | } else if (char2 >= 'A' && char2 <= 'F') { 97 | char2_digit = 10 + (int32_t)(char2 - 'A'); 98 | } else { 99 | std::string msg = std::string("unhex error: invalid hex ") + str; 100 | ::revert((int32_t) reinterpret_cast(msg.data()), 101 | (int32_t)msg.size()); 102 | } 103 | uint8_t cur_byte = (uint8_t)(char1_digit * 16 + char2_digit); 104 | ret.push_back(cur_byte); 105 | } 106 | return ret; 107 | } 108 | } // namespace dtvm 109 | -------------------------------------------------------------------------------- /contractlib/v1/wasi.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #pragma once 5 | #include "hostapi.h" 6 | #include 7 | 8 | #ifdef IN_WASM_ENV 9 | 10 | // implement some wasi in emsdk 11 | extern "C" { 12 | void wasi_proc_exit( 13 | /** 14 | * The exit code returned by the process. 15 | */ 16 | int rval) __attribute__((__import_module__("wasi_snapshot_preview1"), 17 | __import_name__("proc_exit"))); 18 | } 19 | 20 | #endif // IN_WASM_ENV 21 | -------------------------------------------------------------------------------- /contractlib/v1/wasi.inc.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #include "wasi.hpp" 5 | 6 | #ifdef IN_WASM_ENV 7 | // implement some wasi in emsdk 8 | extern "C" { 9 | void wasi_proc_exit( 10 | /** 11 | * The exit code returned by the process. 12 | */ 13 | int rval) __attribute__((__import_module__("wasi_snapshot_preview1"), 14 | __import_name__("proc_exit"))) { 15 | if (rval == 0) { 16 | finish((ADDRESS_UINT) reinterpret_cast(""), 0); 17 | } else { 18 | revert((ADDRESS_UINT) reinterpret_cast("proc_exit"), 19 | 9); // strlen("proc_exit")==9 20 | } 21 | } 22 | } 23 | #endif 24 | -------------------------------------------------------------------------------- /cpp_tests/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /googletest 3 | -------------------------------------------------------------------------------- /cpp_tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | project(DTVM_CppSDK_tests) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | include_directories(../) 7 | 8 | ADD_SUBDIRECTORY (googletest) 9 | enable_testing() 10 | 11 | include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) 12 | 13 | ################################ 14 | # Unit Tests 15 | ################################ 16 | # Add test cpp file 17 | add_executable( runUnitTests 18 | ../contractlib/v1/contractlib.cpp 19 | test_main.cpp test_encoding.cpp 20 | test_math.cpp test_storage.cpp hostapi_mock.cpp) 21 | # Link test executable against gtest & gtest_main 22 | target_link_libraries(runUnitTests gtest gtest_main) 23 | add_test( runUnitTests runUnitTests ) -------------------------------------------------------------------------------- /cpp_tests/test_main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #include 5 | 6 | #include "gtest/gtest.h" 7 | 8 | TEST(HelloTest, BasicAssertions) { 9 | // Expect two strings not to be equal. 10 | EXPECT_STRNE("hello", "world"); 11 | // Expect equality. 12 | EXPECT_EQ(7 * 6, 42); 13 | } 14 | -------------------------------------------------------------------------------- /cpp_tests/test_math.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #include 5 | 6 | #include "utils.hpp" 7 | #include "gtest/gtest.h" 8 | #include 9 | #include 10 | 11 | using namespace dtvm; 12 | 13 | TEST(MathTest, U256Add) { 14 | { 15 | // small int 16 | uint256 a = 1234; 17 | uint256 b = 6788; 18 | uint256 r = a + b; 19 | EXPECT_EQ(r, uint256(8022)); 20 | } 21 | { 22 | // int great than 32bytes 23 | uint256 a = 0x1234; 24 | uint256 b = __uint128_t(1) << 12 * 8; 25 | uint256 r = a + b; 26 | EXPECT_EQ( 27 | bytesToHex(r.bytes()), 28 | "0000000000000000000000000000000000000001000000000000000000001234"); 29 | } 30 | { 31 | // int with u128 max carry 32 | uint256 a = (__uint128_t)__int128_t(-1); 33 | uint256 b = 10; 34 | uint256 r = a + b; 35 | EXPECT_EQ( 36 | bytesToHex(r.bytes()), 37 | "0000000000000000000000000000000100000000000000000000000000000009"); 38 | } 39 | { 40 | // very large u256 41 | uint256 a = __uint128_t(3) << 126; 42 | uint256 b = __uint128_t(1) << 127; 43 | uint256 r = (a + b) + uint256(3); 44 | EXPECT_EQ( 45 | bytesToHex(r.bytes()), 46 | "0000000000000000000000000000000140000000000000000000000000000003"); 47 | } 48 | } 49 | 50 | TEST(MathTest, U256Max) { 51 | uint256 max_value = uint256::max(); 52 | EXPECT_EQ(bytesToHex(max_value.bytes()), 53 | "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); 54 | } 55 | 56 | TEST(MathTest, U256Sub) { 57 | { 58 | // small int 59 | uint256 a = 1234; 60 | uint256 b = 6788; 61 | uint256 r = b - a; 62 | EXPECT_EQ(r, uint256(5554)); 63 | } 64 | { 65 | // int great than 32bytes 66 | uint256 a = 0x1234; 67 | uint256 b = __uint128_t(1) << 12 * 8; 68 | uint256 r = b - a; 69 | EXPECT_EQ( 70 | bytesToHex(r.bytes()), 71 | "0000000000000000000000000000000000000000ffffffffffffffffffffedcc"); 72 | } 73 | { 74 | // int with u128 max carry 75 | uint256 a = (__uint128_t)__int128_t(-1); 76 | uint256 b = 10; 77 | uint256 r = a - b; 78 | EXPECT_EQ( 79 | bytesToHex(r.bytes()), 80 | "00000000000000000000000000000000fffffffffffffffffffffffffffffff5"); 81 | } 82 | } 83 | 84 | TEST(MathTest, U256Mul) { 85 | { 86 | // small int 87 | uint256 a = 1234; 88 | uint256 b = 6788; 89 | uint256 r = a * b; 90 | EXPECT_EQ(r, uint256(8376392)); 91 | } 92 | { 93 | // int great than 32bytes 94 | uint256 a = 0x1234; 95 | uint256 b = __uint128_t(1) << 12 * 8; 96 | uint256 r = a * b; 97 | EXPECT_EQ( 98 | bytesToHex(r.bytes()), 99 | "0000000000000000000000000000000000001234000000000000000000000000"); 100 | } 101 | { 102 | // int with u128 max carry 103 | uint256 a = (__uint128_t)__int128_t(-1); 104 | EXPECT_EQ( 105 | bytesToHex(a.bytes()), 106 | "00000000000000000000000000000000ffffffffffffffffffffffffffffffff"); 107 | uint256 b = 10; 108 | uint256 r = a * b; 109 | EXPECT_EQ( 110 | bytesToHex(r.bytes()), 111 | "00000000000000000000000000000009fffffffffffffffffffffffffffffff6"); 112 | } 113 | } 114 | 115 | TEST(MathTest, U256ShiftLeftRight) { 116 | uint256 a1 = (__uint128_t)__int128_t(-1); 117 | EXPECT_EQ(bytesToHex(a1.bytes()), 118 | "00000000000000000000000000000000ffffffffffffffffffffffffffffffff"); 119 | uint256 a = a1 * 10000; // big number larger than u128 max 120 | { 121 | // small shift 122 | uint256 r = (a << 13) + (a >> 13); 123 | EXPECT_EQ( 124 | bytesToHex(r.bytes()), 125 | "00000000000000000000000004e20001387ffffffffffffffffffffffb1dfffe"); 126 | } 127 | { 128 | // small shift larger than 31 129 | uint256 r = (a << 40) + (a >> 40); 130 | EXPECT_EQ( 131 | bytesToHex(r.bytes()), 132 | "00000000000000000027100000000000000000270fffffffffd8efffffffffff"); 133 | } 134 | { 135 | // small shift larger than 128 136 | uint256 r = (a << 129) + (a >> 129); 137 | EXPECT_EQ( 138 | bytesToHex(r.bytes()), 139 | "ffffffffffffffffffffffffffffb1e000000000000000000000000000001387"); 140 | } 141 | } 142 | 143 | TEST(MathTest, U256AndOrXor) { 144 | uint256 a1 = (__uint128_t)__int128_t(-1); 145 | EXPECT_EQ(bytesToHex(a1.bytes()), 146 | "00000000000000000000000000000000ffffffffffffffffffffffffffffffff"); 147 | uint256 a = a1 * 10000; // big number larger than u128 max 148 | uint256 b = a1 * 999; 149 | { 150 | uint256 r = a & b; 151 | EXPECT_EQ( 152 | bytesToHex(r.bytes()), 153 | "00000000000000000000000000000306ffffffffffffffffffffffffffffd810"); 154 | } 155 | { 156 | uint256 r = a | b; 157 | EXPECT_EQ( 158 | bytesToHex(r.bytes()), 159 | "000000000000000000000000000027effffffffffffffffffffffffffffffcf9"); 160 | } 161 | { 162 | uint256 r = a ^ b; 163 | EXPECT_EQ( 164 | bytesToHex(r.bytes()), 165 | "000000000000000000000000000024e9000000000000000000000000000024e9"); 166 | } 167 | } 168 | 169 | TEST(MathTest, U256Decoding) { 170 | { 171 | uint256 a(as_bytes32(unhex( 172 | "10000000000000000000000000000000fffffffffffffffffffffffffffffff6"))); 173 | EXPECT_EQ( 174 | bytesToHex(a.bytes()), 175 | "10000000000000000000000000000000fffffffffffffffffffffffffffffff6"); 176 | EXPECT_EQ(a.high, __uint128_t(1) << 31 * 4); 177 | EXPECT_EQ(a.low << 64 >> 64, __uint128_t(0xfffffffffffffff6L)); 178 | EXPECT_EQ(uint64_t(a.low >> 64), uint64_t(0xffffffffffffffffL)); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /cpp_tests/test_storage.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #include 5 | 6 | #include "utils.hpp" 7 | #include "gtest/gtest.h" 8 | #include 9 | 10 | using namespace dtvm; 11 | 12 | extern "C" void clear_mock_storage(); 13 | 14 | TEST(StorageTest, BasicTestUint256) { 15 | StorageSlot slot(1, 0); 16 | { 17 | // read empty storage is zeros 18 | uint256 value = read_storage_value(slot); 19 | EXPECT_EQ(value, uint256(0)); 20 | } 21 | // clear between cases 22 | clear_mock_storage(); 23 | { 24 | uint256 value = uint256(__uint128_t(1234), __uint128_t(5678)); 25 | write_storage_value(slot, value); 26 | uint256 read_value = read_storage_value(slot); 27 | EXPECT_EQ(value, read_value); 28 | } 29 | } 30 | 31 | TEST(StorageTest, BasicTestString) { 32 | StorageSlot slot(2, 0); 33 | { 34 | // read empty storage is zeros 35 | const auto &value = read_storage_value(slot); 36 | EXPECT_EQ(value, std::string("")); 37 | } 38 | // clear between cases 39 | clear_mock_storage(); 40 | { 41 | std::string value = std::string("hello"); 42 | write_storage_value(slot, value); 43 | const auto &read_value = read_storage_value(slot); 44 | EXPECT_EQ(value, read_value); 45 | } 46 | // clear between cases 47 | clear_mock_storage(); 48 | { 49 | // test 31 length string 50 | std::string value = std::string(31, 'a'); 51 | write_storage_value(slot, value); 52 | const auto &read_value = read_storage_value(slot); 53 | EXPECT_EQ(value, read_value); 54 | } 55 | // clear between cases 56 | clear_mock_storage(); 57 | { 58 | // test 64(32*n) length string 59 | std::string value = std::string(64, 'a'); 60 | write_storage_value(slot, value); 61 | const auto &read_value = read_storage_value(slot); 62 | EXPECT_EQ(value, read_value); 63 | } 64 | // clear between cases 65 | clear_mock_storage(); 66 | { 67 | // test string with > 31 and not 32*n length 68 | std::string value = std::string(33, 'a'); 69 | write_storage_value(slot, value); 70 | const auto &read_value = read_storage_value(slot); 71 | EXPECT_EQ(value, read_value); 72 | } 73 | } 74 | 75 | TEST(StorageTest, BasicTestBytes) { 76 | StorageSlot slot(3, 0); 77 | { 78 | // read empty storage is zeros 79 | auto value = read_storage_value>(slot); 80 | EXPECT_EQ(value.size(), 0); 81 | } 82 | // clear between cases 83 | clear_mock_storage(); 84 | { 85 | auto value = std::vector(5, 0x13); 86 | write_storage_value(slot, value); 87 | auto read_value = read_storage_value>(slot); 88 | EXPECT_EQ(value, read_value); 89 | } 90 | // clear between cases 91 | clear_mock_storage(); 92 | { 93 | // test 31 length bytes 94 | auto value = std::vector(31, 0x13); 95 | write_storage_value(slot, value); 96 | auto read_value = read_storage_value>(slot); 97 | EXPECT_EQ(value, read_value); 98 | } 99 | // clear between cases 100 | clear_mock_storage(); 101 | { 102 | // test 64(32*n) length bytes 103 | auto value = std::vector(64, 0x13); 104 | write_storage_value(slot, value); 105 | auto read_value = read_storage_value>(slot); 106 | EXPECT_EQ(value, read_value); 107 | } 108 | // clear between cases 109 | clear_mock_storage(); 110 | { 111 | // test bytes with > 31 and not 32*n length 112 | auto value = std::vector(33, 0x13); 113 | write_storage_value(slot, value); 114 | auto read_value = read_storage_value>(slot); 115 | EXPECT_EQ(value, read_value); 116 | } 117 | } 118 | 119 | TEST(StorageTest, BasicTestBool) { 120 | StorageSlot slot(4, 0); 121 | { 122 | // read empty storage is zeros 123 | bool value = read_storage_value(slot); 124 | EXPECT_EQ(value, false); 125 | } 126 | // clear between cases 127 | clear_mock_storage(); 128 | { 129 | bool value = true; 130 | write_storage_value(slot, value); 131 | auto read_value = read_storage_value(slot); 132 | EXPECT_EQ(value, read_value); 133 | } 134 | clear_mock_storage(); 135 | { 136 | bool value = false; 137 | write_storage_value(slot, value); 138 | auto read_value = read_storage_value(slot); 139 | EXPECT_EQ(value, read_value); 140 | } 141 | } 142 | 143 | TEST(StorageTest, BasicTestStorageSlotOffset) { 144 | // storage one value(type uint8, int16, bool) in one storage slot 145 | StorageSlot slot1(5, 0); 146 | StorageSlot slot2(5, 5); 147 | StorageSlot slot3(5, 31); 148 | uint8_t value1 = 0x7a; 149 | int16_t value2 = 0x6b5c; 150 | bool value3 = true; 151 | write_storage_value(slot1, value1); 152 | write_storage_value(slot2, value2); 153 | write_storage_value(slot3, value3); 154 | const auto read_bytes = hostio::read_storage(slot1.get_slot()); 155 | EXPECT_EQ(bytesToHex(read_bytes), 156 | "7a000000006b5c00000000000000000000000000000000000000000000000001"); 157 | uint8_t decoded_value1 = read_storage_value(slot1); 158 | int16_t decoded_value2 = read_storage_value(slot2); 159 | bool decoded_value3 = read_storage_value(slot3); 160 | EXPECT_EQ(value1, decoded_value1); 161 | EXPECT_EQ(value2, decoded_value2); 162 | EXPECT_EQ(value3, decoded_value3); 163 | } 164 | -------------------------------------------------------------------------------- /cpp_tests/utils.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | inline std::string bytesToHex(const std::vector &bytes) { 13 | std::stringstream ss; 14 | for (const auto &byte : bytes) { 15 | ss << std::hex << std::setw(2) << std::setfill('0') 16 | << static_cast(byte); 17 | } 18 | return ss.str(); 19 | } 20 | 21 | inline std::string bytesToHex(const std::array &bytes) { 22 | std::stringstream ss; 23 | for (const auto &byte : bytes) { 24 | ss << std::hex << std::setw(2) << std::setfill('0') 25 | << static_cast(byte); 26 | } 27 | return ss.str(); 28 | } 29 | 30 | inline std::string bytesToHex(const std::array &bytes) { 31 | std::stringstream ss; 32 | for (const auto &byte : bytes) { 33 | ss << std::hex << std::setw(2) << std::setfill('0') 34 | << static_cast(byte); 35 | } 36 | return ss.str(); 37 | } 38 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | RUN sed -i s/archive.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list 3 | RUN sed -i s/security.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list 4 | RUN apt update -y && apt install -y --no-install-recommends build-essential ca-certificates clang-format-15 clang-tidy-15 cmake curl git libzstd-dev ninja-build python3 python3-pip ssh sudo wabt wget zlib1g-dev wget git-lfs zlib1g-dev wget libffi-dev libncurses5-dev libncursesw5-dev libxml2-dev binaryen unzip && rm -rf /var/lib/apt/lists/* 5 | RUN pip3 install cmake-format lit --no-cache-dir 6 | RUN cd /usr/bin/ && ln -s python3 python && ln -s clang-format-15 clang-format && ln -s clang-tidy-15 clang-tidy && ln -s run-clang-tidy-15 run-clang-tidy 7 | RUN useradd -m -u 500 -U -G sudo -s /bin/bash admin && passwd -d admin 8 | RUN mkdir -p /opt 9 | WORKDIR /opt 10 | 11 | COPY install_llvm16.sh /opt/install_llvm16.sh 12 | RUN chmod +x /opt/install_llvm16.sh 13 | # COPY install_llvm15.sh /opt/install_llvm15.sh 14 | # RUN chmod +x /opt/install_llvm15.sh 15 | RUN wget https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.4/clang+llvm-16.0.4-x86_64-linux-gnu-ubuntu-22.04.tar.xz && /opt/install_llvm16.sh 16 | # RUN wget https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.0/clang+llvm-15.0.0-x86_64-linux-gnu-rhel-8.4.tar.xz 17 | 18 | RUN wget -O /opt/solc https://github.com/ethereum/solidity/releases/download/v0.8.29/solc-static-linux 19 | RUN chmod +x /opt/solc 20 | RUN wget -O /opt/googletest-1.15.2.tar.gz https://github.com/google/googletest/releases/download/v1.15.2/googletest-1.15.2.tar.gz 21 | 22 | RUN git clone https://github.com/emscripten-core/emsdk.git 23 | WORKDIR /opt/emsdk 24 | RUN ./emsdk install 3.1.69 25 | RUN ./emsdk activate 3.1.69 26 | USER root 27 | RUN curl -sSf https://mirrors.ustc.edu.cn/misc/rustup-install.sh | sh -s -- -y 28 | RUN bash -c ". /root/.cargo/env" 29 | COPY cargo_config /root/.cargo/config 30 | RUN bash -c ". ~/.cargo/env && rustup install 1.81.0 && rustup default 1.81.0" 31 | WORKDIR /home/admin 32 | USER root 33 | WORKDIR /opt 34 | 35 | RUN echo "export PATH=/opt/llvm16/bin:/opt:\$PATH" >> ~/.bash_profile 36 | RUN echo "export LLVM_SYS_160_PREFIX=/opt/llvm16" >> ~/.bash_profile 37 | ENV PATH=/opt/llvm16/bin:/opt:$PATH 38 | ENV LLVM_SYS_160_PREFIX=/opt/llvm16 39 | 40 | ENV RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static 41 | ENV RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup 42 | COPY install_rust.sh /opt/install_rust.sh 43 | RUN chmod +x /opt/install_rust.sh 44 | # RUN /opt/install_rust.sh 45 | 46 | RUN mkdir -p /root/.cargo && touch /root/.cargo/env 47 | 48 | # install solidity tools 49 | # install foundry 50 | RUN curl -L https://foundry.paradigm.xyz | bash 51 | ENV PATH=~/.foundry/bin:$PATH 52 | RUN bash -c "source ~/.bashrc && foundryup" 53 | 54 | WORKDIR /opt 55 | -------------------------------------------------------------------------------- /docker/cargo_config: -------------------------------------------------------------------------------- 1 | [source.crates-io] 2 | replace-with = 'ustc' 3 | [source.ustc] 4 | registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/" 5 | -------------------------------------------------------------------------------- /docker/docker_build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | if [ "$1" = "prepare" ]; then 5 | echo "done prepare docker build context" 6 | ls 7 | else 8 | cd docker 9 | docker build . --platform linux/x86_64 -f Dockerfile -t dtvmdev1/dtvm-cpp-dev-x64:latest 10 | cd .. 11 | fi 12 | -------------------------------------------------------------------------------- /docker/install_llvm16.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | cd /opt 4 | ls 5 | tar -xvf clang+llvm-16.0.4-x86_64-linux-gnu-ubuntu-22.04.tar.xz 6 | rm -rf clang+llvm-16.0.4-x86_64-linux-gnu-ubuntu-22.04.tar.xz 7 | mv clang+llvm-16.0.4-x86_64-linux-gnu-ubuntu-22.04 llvm16 8 | -------------------------------------------------------------------------------- /docker/install_rust.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | curl -sSf https://mirrors.ustc.edu.cn/misc/rustup-install.sh | sh -s -- -y 4 | . "$HOME/.cargo/env" 5 | rustup install 1.81.0 6 | rustup default 1.81.0 7 | -------------------------------------------------------------------------------- /docs/COMMIT_CONVENTION.md: -------------------------------------------------------------------------------- 1 | # Commit Convention 2 | 3 | To maintain consistency and readability in the codebase, all commits to this project should follow the convention below. These conventions are based on [Conventional Commits](https://www.conventionalcommits.org/). 4 | 5 | ## Commit Format 6 | 7 | ``` 8 | [optional scope]: 9 | 10 | [optional body] 11 | 12 | [optional footer] 13 | ``` 14 | 15 | ### Type 16 | 17 | Must be one of the following: 18 | 19 | - **feat**: A new feature 20 | - **fix**: A bugfix 21 | - **docs**: Documentation only changes 22 | - **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) 23 | - **refactor**: A code change that neither fixes a bug nor adds a feature 24 | - **perf**: A code change that improves performance 25 | - **test**: Adding missing or correcting existing tests 26 | - **build**: Changes that affect the build system or external dependencies (example: make, cmake) 27 | - **ci**: Changes to CI configuration files and scripts 28 | - **chore**: Other changes that don't modify src or test files 29 | 30 | ### Scope 31 | 32 | The scope should specify the area of the change, for example: 33 | 34 | - **core**: Core library code 35 | - **api**: API related 36 | - **contract**: Contract library related 37 | - **solidcpp**: SolidCpp compiler code 38 | - **examples**: Example code 39 | - **docs**: Documentation related 40 | - **tools**: Tool related 41 | - **deps**: Dependency related 42 | - **ci**: CI related 43 | - **test**: Test related 44 | - **other**: Other 45 | 46 | ### Description 47 | 48 | - Use the imperative, present tense: "change" not "changed" or "changes" 49 | - Don't capitalize the first letter 50 | - No period at the end 51 | 52 | ### Body 53 | 54 | - Use the imperative, present tense 55 | - Should include the motivation for the change and contrast this with previous behavior 56 | 57 | ### Footer 58 | 59 | - **BREAKING CHANGE**: Should start with "BREAKING CHANGE:" followed by a space or two newlines, then a description of the change 60 | - References to issues: Can reference related issues, e.g., "Closes #123, #456" 61 | 62 | ## Examples 63 | 64 | ``` 65 | feat(api): add contract event listening capability 66 | 67 | Add support for contract event listening, making it easier for developers to respond to smart contract events. 68 | 69 | BREAKING CHANGE: Event handling API now uses callback functions, previous polling approach is no longer supported. 70 | Closes #123 71 | ``` 72 | 73 | ``` 74 | fix(contract): fix memory access error in template contracts 75 | 76 | Fix segmentation fault that could occur when using template contracts. 77 | 78 | Closes #456 79 | ``` 80 | 81 | ``` 82 | docs: update C++ interface documentation and usage examples 83 | ``` 84 | 85 | ## Automatic Validation 86 | 87 | All commits will be automatically validated using GitHub Actions to ensure they comply with the above conventions. Commits that do not comply with the convention may be automatically rejected. -------------------------------------------------------------------------------- /docs/VERSIONING.md: -------------------------------------------------------------------------------- 1 | # Versioning Guidelines 2 | 3 | DTVM_CppSDK project follows the [Semantic Versioning (SemVer)](https://semver.org/) specification. 4 | 5 | ## Version Format 6 | 7 | Version numbers are formatted as: **X.Y.Z**, where X, Y, and Z are non-negative integers without leading zeros. 8 | 9 | - **X** represents the major version 10 | - **Y** represents the minor version 11 | - **Z** represents the patch version 12 | 13 | Additional labels can be used as extensions for pre-releases and version metadata, formatted as: **X.Y.Z-label+metadata** 14 | 15 | ## Version Increment Rules 16 | 17 | 1. **Major version (X)**: Increment when making incompatible API changes 18 | 2. **Minor version (Y)**: Increment when adding functionality in a backward-compatible manner 19 | 3. **Patch version (Z)**: Increment when making backward-compatible bug fixes 20 | 21 | ## Pre-release Versions 22 | 23 | Pre-release versions can be indicated by adding a hyphen and an identifier after the version number, for example: 24 | 25 | - **1.0.0-alpha.1** 26 | - **1.0.0-beta.2** 27 | - **1.0.0-rc.1** 28 | 29 | Pre-release versions have lower precedence than the associated normal version. 30 | 31 | ## Release Process 32 | 33 | 1. Each significant version should be developed and tested on a `dev` or feature branch before release 34 | 2. Prior to version release, update the version definitions in header files and the `CHANGELOG.md` file to record all changes for that version 35 | 3. Tag versions with Git tags in the format `v1.0.0` 36 | 4. Release versions through GitHub Releases, including the corresponding CHANGELOG 37 | 38 | ## Header File Version Definitions 39 | 40 | The main header file should include the following version definitions: 41 | 42 | ```cpp 43 | // Version information 44 | #define DTVM_CPP_SDK_VERSION_MAJOR 1 45 | #define DTVM_CPP_SDK_VERSION_MINOR 0 46 | #define DTVM_CPP_SDK_VERSION_PATCH 0 47 | #define DTVM_CPP_SDK_VERSION_STRING "1.0.0" 48 | ``` 49 | 50 | ## CHANGELOG Requirements 51 | 52 | The CHANGELOG should list versions from newest to oldest, with each version including the following sections: 53 | 54 | - **Added**: New features 55 | - **Changed**: Changes to existing functionality 56 | - **Deprecated**: Features that are still supported but will be removed in upcoming releases 57 | - **Removed**: Features that have been removed 58 | - **Fixed**: Bug fixes 59 | - **Security**: Security-related fixes 60 | 61 | ## Version Examples 62 | 63 | ### Major Version Upgrade (Incompatible Changes) 64 | ``` 65 | 1.0.0 -> 2.0.0 66 | ``` 67 | 68 | ### Minor Version Upgrade (Backward-compatible New Features) 69 | ``` 70 | 1.0.0 -> 1.1.0 71 | ``` 72 | 73 | ### Patch Version Upgrade (Backward-compatible Bug Fixes) 74 | ``` 75 | 1.1.0 -> 1.1.1 76 | ``` 77 | 78 | ## Tagging Release Versions 79 | 80 | Use Git tags to mark each release version: 81 | 82 | ```bash 83 | git tag -a v1.0.0 -m "Release version 1.0.0" 84 | git push origin v1.0.0 85 | ``` -------------------------------------------------------------------------------- /docs/advanced_features/contract_interaction.md: -------------------------------------------------------------------------------- 1 | Contract Interaction 2 | =============== 3 | 4 | ## Overview 5 | 6 | In WASM smart contract development, interaction between contracts is a crucial functionality. This document will detail how to implement cross-contract calls, message passing, and interactions in this C++ contract SDK. 7 | 8 | ## Contents 9 | 10 | - [Contract Call Basics](#contract-call-basics) 11 | - [Creating Contract Instances](#creating-contract-instances) 12 | - [Regular Call](#regular-call) 13 | - [staticCall](#staticcall) 14 | - [delegateCall](#delegatecall) 15 | - [Error Handling](#error-handling) 16 | - [Best Practices](#best-practices) 17 | 18 | ## Contract Call Basics 19 | 20 | Cross-contract calls allow one contract to execute methods of another contract. In our C++ contract SDK, this is primarily implemented through interface instantiation and method calls. 21 | 22 | ### Interface Definition Example 23 | 24 | ```cpp 25 | SOLIDITY(token_service, R"""( 26 | pragma solidity ^0.8.0; 27 | 28 | interface ITokenService { 29 | function balanceOf(address owner) external returns(uint256); 30 | function transfer(address to, uint256 amount) external; 31 | } 32 | )""") 33 | ``` 34 | 35 | ## Creating Contract Instances 36 | 37 | In the C++ code of a contract, you can create instances of other contracts using the following method: 38 | 39 | ### Through Interface Static Method 40 | 41 | ```cpp 42 | // Create an instance using a known contract address 43 | auto token_contract = ITokenService::from(token_address); 44 | ``` 45 | 46 | ## Regular Call 47 | 48 | Dynamic calls can pass more complex parameters and handle: 49 | 50 | ```cpp 51 | CResult performTokenTransfer( 52 | const Address& token_address, 53 | const Address& recipient, 54 | const uint256& amount 55 | ) { 56 | auto token_contract = ITokenService::from(token_address); 57 | 58 | // Call the transfer method 59 | CResult transfer_result = token_contract->transfer(recipient, amount); 60 | 61 | if (!transfer_result.success()) { 62 | // Transfer successful 63 | return Ok(); 64 | } 65 | 66 | return Revert("Transfer failed"); 67 | } 68 | ``` 69 | 70 | ## staticCall 71 | 72 | Static calls are read-only operations that do not modify the blockchain state: 73 | 74 | ```cpp 75 | CResult checkBalance(const Address& token, const Address& owner) { 76 | auto token_contract = ITokenService::from(token); 77 | 78 | // Directly call methods of other contracts 79 | CResult balance = token_contract->balanceOf(owner); 80 | auto call_info = dtvm::current_call_info(); 81 | std::vector encoded_input = { 1,2,3,4 }; // function selector bytes, insert actual values 82 | std::vector encoded_args = dtvm::abi_encode(std::make_tuple(owner, spender)); 83 | encoded_input.insert(encoded_input.end(), encoded_args.begin(), encoded_args.end()); 84 | CResult balance = dtvm::call_static(token, encoded_input, call_info->value, call_info->gas); 85 | 86 | if (balance.isOk()) { 87 | uint256 token_balance = balance.get(); 88 | // Process the balance 89 | return Ok(token_balance); 90 | } 91 | 92 | return Revert("Balance check failed"); 93 | } 94 | ``` 95 | 96 | 97 | ## delegateCall 98 | 99 | Delegate calls execute using the bytecode of the called contract but in the context of the current contract's state and address (the sender of the delegate call). 100 | 101 | ```cpp 102 | CResult checkBalance(const Address& token, const Address& owner) { 103 | auto token_contract = ITokenService::from(token); 104 | 105 | // Directly call methods of other contracts 106 | CResult balance = token_contract->balanceOf(owner); 107 | auto call_info = dtvm::current_call_info(); 108 | std::vector encoded_input = { 1,2,3,4 }; // function selector bytes, insert actual values 109 | std::vector encoded_args = dtvm::abi_encode(std::make_tuple(owner, spender)); 110 | encoded_input.insert(encoded_input.end(), encoded_args.begin(), encoded_args.end()); 111 | CResult balance = dtvm::call_delegate(token, encoded_input, call_info->value, call_info->gas); 112 | 113 | if (balance.isOk()) { 114 | uint256 token_balance = balance.get(); 115 | // Process the balance 116 | return Ok(token_balance); 117 | } 118 | 119 | return Revert("Balance check failed"); 120 | } 121 | ``` 122 | 123 | ## Error Handling 124 | 125 | Cross-contract calls need careful handling of potential errors: 126 | 127 | ```cpp 128 | CResult safeContractCall(const Address& contract_address) { 129 | auto contract = ITokenService::from(contract_address); 130 | CResult result = contract->someMethod(); 131 | 132 | if (!result.success()) { 133 | // Handle call failure 134 | return Revert(result.data()); 135 | } 136 | 137 | return result; 138 | } 139 | ``` 140 | 141 | ## Best Practices 142 | 143 | 1. Use delegate calls with caution 144 | 2. Use interfaces for type-safe contract calls 145 | 3. Implement appropriate error handling 146 | 4. Be mindful of gas consumption in calls 147 | 5. Avoid circular calls and complex cross-contract dependencies 148 | 6. Be careful to prevent reentrancy attacks 149 | 150 | ## Notes 151 | 152 | - Cross-contract calls may increase gas consumption 153 | - The target contract must be deployed and have a valid address 154 | 155 | -------------------------------------------------------------------------------- /docs/advanced_features/contract_interfaces.md: -------------------------------------------------------------------------------- 1 | Contract Interfaces 2 | ======= 3 | 4 | ## Overview 5 | 6 | In this SDK, contract interface definitions are implemented through the `SOLIDITY` macro. This mechanism allows developers to use Solidity-like syntax to declare contract interfaces, storage variables, events, and methods. 7 | 8 | ## Interface Definition Syntax 9 | 10 | ### SOLIDITY Macro 11 | 12 | ```cpp 13 | SOLIDITY(filename_identifier, R"""( 14 | Solidity code 15 | )""") 16 | ``` 17 | 18 | #### Parameter Description 19 | - First parameter: filename identifier, used to generate the corresponding C++ header file 20 | - Second parameter: Solidity code contained in a raw string literal `R"""(...)"""` 21 | 22 | ## Supported Solidity Syntax Elements 23 | 24 | ### 1. Interface 25 | 26 | ```solidity 27 | interface IMyInterface { 28 | function methodName(parameters) external returns (returnType); 29 | } 30 | ``` 31 | 32 | #### Characteristics 33 | - Only method declarations, no implementations 34 | - Can inherit from other interfaces 35 | - Cannot define constructors 36 | - Methods are default `external` 37 | 38 | ### 2. Abstract Contract 39 | 40 | ```solidity 41 | abstract contract MyContract is IMyInterface { 42 | // Storage variables 43 | mapping(address => uint256) private balances; 44 | 45 | // Events 46 | event Transfer(address from, address to, uint256 amount); 47 | 48 | // Abstract methods 49 | function abstractMethod() public virtual; 50 | } 51 | ``` 52 | 53 | #### Characteristics 54 | - Can include storage variables 55 | - Can define events 56 | - Can only define abstract methods 57 | - Can inherit from interfaces or other abstract contracts 58 | - Cannot define constructors 59 | 60 | ## Supported Data Types 61 | 62 | ### Basic Types 63 | - `uint8`, `uint16`, `uint32`, `uint64`, `uint128`, `uint256` 64 | - `int8`, `int16`, `int32`, `int64`, `int128`, `int256` 65 | - `bool`, `address`, `string` 66 | 67 | ### Composite Types 68 | - Fixed-length arrays: such as `uint32[5]` 69 | - Dynamic arrays: such as `uint32[]` 70 | - Mappings: such as `mapping(address => uint256)` 71 | 72 | ### Special Types 73 | - `bytes` 74 | - `bytes32` 75 | 76 | ## Storage Variable Declarations 77 | 78 | ```solidity 79 | abstract contract MyContract { 80 | // Basic type storage variables 81 | uint256 private tokenAmount; 82 | 83 | // Mapping type storage variables 84 | mapping(address => uint256) private balances; 85 | 86 | // Array type storage variables 87 | uint32[] private transactions; 88 | } 89 | ``` 90 | 91 | ## Event Declarations 92 | 93 | ```solidity 94 | abstract contract MyContract { 95 | // Simple event 96 | event Transfer(address from, address to, uint256 amount); 97 | 98 | // Complex event 99 | event Approval(address indexed owner, address indexed spender, uint256 value); 100 | } 101 | ``` 102 | 103 | ### Event Characteristics 104 | - Can use the `indexed` keyword 105 | - Supports multiple parameters 106 | - Can be triggered in C++ implementation using `emitEventName()` 107 | 108 | ## Method Types 109 | 110 | ### External Methods 111 | ```solidity 112 | function transfer(address to, uint256 amount) external; 113 | ``` 114 | 115 | ### Abstract Methods (Virtual) 116 | ```solidity 117 | function calculateFee() public virtual returns (uint256); 118 | ``` 119 | 120 | ## Notes 121 | 122 | 1. The `SOLIDITY` macro is only used to generate C++ declarations, not directly executed 123 | 2. The generated header file will contain corresponding C++ class declarations 124 | 3. Actual business logic needs to be implemented in the inherited C++ class 125 | 126 | ## Best Practices 127 | 128 | - Keep interface definitions concise and clear 129 | - Use appropriate access modifiers 130 | - Design storage variables and methods properly 131 | - Follow Solidity coding standards 132 | -------------------------------------------------------------------------------- /docs/advanced_features/solidity_integration.md: -------------------------------------------------------------------------------- 1 | Solidity Contract Integration Guide 2 | ======== 3 | 4 | ## Overview 5 | 6 | This document describes how to interact with Solidity contracts in our C++ WASM smart contract SDK. 7 | 8 | ## Compatibility Mechanism 9 | 10 | Our C++ SDK implements seamless interaction with Solidity contracts through unified method selectors and parameter encoding/decoding mechanisms. This means C++ contracts can directly call and be called by Solidity contracts, just as in a native Solidity environment. 11 | 12 | ## Calling Solidity Contracts 13 | 14 | ### Creating Contract Instances 15 | 16 | Use the `from` static method of the interface to create contract instances: 17 | 18 | ```cpp 19 | // Assuming ITokenService interface is already defined 20 | auto token_contract = ITokenService::from(token_address); 21 | ``` 22 | 23 | ### Calling Contract Methods 24 | 25 | Directly call contract methods: 26 | 27 | ```cpp 28 | // Call the balanceOf method 29 | CResult balance = token_contract->balanceOf(owner_address); 30 | ``` 31 | 32 | ## Solidity Contracts Calling C++ Contracts 33 | 34 | Solidity contracts can call C++ contracts just like regular contracts, without any special handling. 35 | 36 | ## Parameter and Return Value Encoding 37 | 38 | The SDK is fully compatible with Solidity's encoding rules: 39 | - Parameters are encoded according to ABI specifications 40 | - Supports basic types: `uint256`, `address`, `bool`, `bytes`, etc. 41 | - Supports arrays and complex types 42 | - Return values follow the same encoding mechanisms 43 | 44 | ## Event Handling 45 | 46 | ### Triggering Events 47 | 48 | Triggering events in C++ contracts: 49 | 50 | ```cpp 51 | // Trigger Solidity-defined events 52 | emitTransfer(from_address, to_address, amount); 53 | ``` 54 | 55 | ## Storage Compatibility 56 | 57 | - Mappings 58 | - Dynamic arrays 59 | - Basic type storage 60 | - Nested structures 61 | 62 | All are fully compatible with Solidity's storage mechanisms. 63 | 64 | ## Important Notes 65 | 66 | 1. Ensure method signatures match exactly 67 | 2. Follow Solidity's ABI encoding rules 68 | 3. Pay attention to precise type matching 69 | -------------------------------------------------------------------------------- /docs/basic_concepts/constructors.md: -------------------------------------------------------------------------------- 1 | Constructor Usage Guide 2 | ============== 3 | 4 | This document provides detailed instructions on how to implement and use constructors in C++ WASM contracts. 5 | 6 | ## Table of Contents 7 | 8 | - [Constructor Declaration](#constructor-declaration) 9 | - [Reading Constructor Parameters](#reading-constructor-parameters) 10 | - [Initialization Operations in Constructors](#initialization-operations-in-constructors) 11 | - [Inheritance and Constructors](#inheritance-and-constructors) 12 | - [Error Handling](#error-handling) 13 | - [Best Practices](#best-practices) 14 | 15 | ## Constructor Declaration 16 | 17 | Since constructors cannot be declared in Solidity abstract classes and interfaces, constructors in C++ contracts need to be implemented in the contract class. Also, because parameters cannot be obtained from the Solidity interface code, parameters must be manually decoded in the C++ contract constructor. 18 | 19 | In C++ contract classes, constructors need to override the `constructor` method of the base class (note that currently C++ contracts without parameters also need to declare an empty constructor): 20 | 21 | ```cpp 22 | class MyToken : public SolidityBase { 23 | protected: 24 | CResult constructor(dtvm::CallInfoPtr call_info, Input &input) override { 25 | // Constructor implementation. Including parameter decoding, contract initialization logic, etc. 26 | return Ok(); 27 | } 28 | }; 29 | ``` 30 | 31 | Constructors have two parameters: 32 | - `call_info`: Contains call context information 33 | - `input`: Used to read constructor parameters 34 | 35 | ## Reading Constructor Parameters 36 | 37 | Since constructors cannot be declared in Solidity abstract classes and interfaces, C++ constructors need to read parameters from the input parameter in sequence: 38 | 39 | ```cpp 40 | class MyToken : public SolidityBase { 41 | protected: 42 | CResult constructor(dtvm::CallInfoPtr call_info, Input &input) override { 43 | // Read parameters in the declared order 44 | std::string name = input.read(); 45 | std::string symbol = input.read(); 46 | uint8_t decimals = input.read(); 47 | uint256 initial_supply = input.read(); 48 | 49 | // Use these parameters for initialization 50 | return Ok(); 51 | } 52 | }; 53 | ``` 54 | 55 | ### Supported Parameter Types 56 | 57 | Constructors support reading all parameter types supported by contract interfaces, including bool, integers, address, strings, etc. Usage examples: 58 | 59 | ```cpp 60 | // Basic types 61 | bool flag = input.read(); 62 | uint8_t u8_value = input.read(); 63 | int16_t i16_value = input.read(); 64 | uint256 u256_value = input.read(); 65 | 66 | // Strings 67 | std::string str = input.read(); 68 | 69 | // Addresses 70 | Address addr = input.read
(); 71 | 72 | ``` 73 | 74 | ## Initialization Operations in Constructors 75 | 76 | Constructors are typically used to initialize the state of the contract: 77 | 78 | ```cpp 79 | class MyToken : public SolidityBase { 80 | protected: 81 | CResult constructor(dtvm::CallInfoPtr call_info, Input &input) override { 82 | // 1. Read parameters 83 | std::string name = input.read(); 84 | std::string symbol = input.read(); 85 | uint256 initial_supply = input.read(); 86 | 87 | // 2. Initialize storage variables 88 | name_->set(name); 89 | symbol_->set(symbol); 90 | 91 | // 3. Initialize balances 92 | Address sender = get_msg_sender(); 93 | balances_->set(sender, initial_supply); 94 | total_supply_->set(initial_supply); 95 | 96 | // 4. Trigger initialization event 97 | emitTransfer(Address::zero(), sender, initial_supply); 98 | 99 | return Ok(); 100 | } 101 | }; 102 | ``` 103 | 104 | ## Inheritance and Constructors 105 | 106 | When a contract inherits from multiple base classes, the constructor implementation method: 107 | 108 | ```cpp 109 | SOLIDITY(base_contracts, R"""( 110 | abstract contract TokenBase { 111 | string private name; 112 | string private symbol; 113 | } 114 | 115 | abstract contract Pausable { 116 | address private admin; 117 | bool private paused; 118 | } 119 | 120 | abstract contract MyToken is TokenBase, Pausable { 121 | mapping(address => uint256) private balances; 122 | } 123 | )""") 124 | 125 | class MyTokenImpl : public MyToken { 126 | protected: 127 | CResult constructor(dtvm::CallInfoPtr call_info, Input &input) override { 128 | // Read parameters needed for all base classes 129 | std::string name = input.read(); 130 | std::string symbol = input.read(); 131 | Address admin = input.read
(); 132 | 133 | // Initialize TokenBase state 134 | name_->set(name); 135 | symbol_->set(symbol); 136 | 137 | // Initialize Pausable state 138 | admin_->set(admin); 139 | paused_->set(false); 140 | 141 | // Initialize the current contract's state 142 | balances_->set(get_msg_sender(), uint256(1000000)); 143 | 144 | return Ok(); 145 | } 146 | }; 147 | ``` 148 | 149 | ## Error Handling 150 | 151 | Error handling in constructors: 152 | 153 | ```cpp 154 | CResult constructor(dtvm::CallInfoPtr call_info, Input &input) override { 155 | try { 156 | std::string name = input.read(); 157 | std::string symbol = input.read(); 158 | 159 | // Parameter validation 160 | if (name.empty() || symbol.empty()) { 161 | return Revert("name and symbol cannot be empty"); 162 | } 163 | 164 | // Permission check 165 | if (get_msg_sender() == Address::zero()) { 166 | return Revert("invalid deployer address"); 167 | } 168 | 169 | // Initialization operations 170 | name_->set(name); 171 | symbol_->set(symbol); 172 | 173 | return Ok(); 174 | } catch (const std::exception& e) { 175 | return Revert(e.what()); 176 | } 177 | } 178 | ``` 179 | 180 | ## Best Practices 181 | 182 | 1. **Strict parameter validation in contract constructors** 183 | ```cpp 184 | CResult constructor(dtvm::CallInfoPtr call_info, Input &input) override { 185 | std::string name = input.read(); 186 | std::string symbol = input.read(); 187 | uint256 initial_supply = input.read(); 188 | 189 | // Validate parameters 190 | if (name.empty()) { 191 | return Revert("name cannot be empty"); 192 | } 193 | if (symbol.empty()) { 194 | return Revert("symbol cannot be empty"); 195 | } 196 | if (initial_supply == uint256(0)) { 197 | return Revert("initial supply cannot be zero"); 198 | } 199 | 200 | // Continue initialization... 201 | return Ok(); 202 | } 203 | ``` 204 | 205 | 2. **Event Logging** 206 | ```cpp 207 | CResult constructor(dtvm::CallInfoPtr call_info, Input &input) override { 208 | std::string name = input.read(); 209 | std::string symbol = input.read(); 210 | uint256 initial_supply = input.read(); 211 | 212 | // Initialization... 213 | 214 | // Record initialization event 215 | emitTokenInitialized( 216 | get_msg_sender(), 217 | name, 218 | symbol, 219 | initial_supply 220 | ); 221 | 222 | return Ok(); 223 | } 224 | ``` 225 | 226 | 3. **Security Considerations** 227 | - Validate all input parameters 228 | - Record privileged addresses to storage variables in the contract constructor when there are permission management requirements 229 | - Ensure correct initialization of critical states 230 | -------------------------------------------------------------------------------- /docs/basic_concepts/events.md: -------------------------------------------------------------------------------- 1 | Contract Events Usage Guide 2 | ================== 3 | 4 | This document details how to declare and use Solidity-style events in C++ WASM contracts. 5 | 6 | ## Contents 7 | 8 | - [Event Declaration](#event-declaration) 9 | - [Event Parameter Types](#event-parameter-types) 10 | - [Triggering Events](#triggering-events) 11 | - [Indexed Parameters](#indexed-parameters) 12 | - [Anonymous Events](#anonymous-events) 13 | - [Best Practices](#best-practices) 14 | 15 | ## Event Declaration 16 | 17 | When declaring contracts using the SOLIDITY macro, you can define various events: 18 | 19 | ```cpp 20 | SOLIDITY(contract_name, R"""( 21 | abstract contract Events { 22 | // Basic event declaration 23 | event Transfer(address from, address to, uint256 amount); 24 | 25 | // Event with indexed parameters 26 | event Approval(address indexed owner, address indexed spender, uint256 value); 27 | 28 | // Event with different parameter types 29 | event ComplexEvent( 30 | bool bool_value, 31 | uint8 uint8_value, 32 | int16 int16_value, 33 | uint256 uint256_value, 34 | string string_value, 35 | bytes bytes_value, 36 | bytes32 bytes32_value 37 | ); 38 | } 39 | )""") 40 | ``` 41 | 42 | ## Event Parameter Types 43 | 44 | Events support the following parameter types: 45 | 46 | - Basic types: `bool`, `uint8`, `int16`, `uint256` and other integer types 47 | - Address type: `address` 48 | - String: `string` 49 | - Byte types: `bytes`, `bytes32` and other byte array types 50 | - Array types: supports both fixed-length and dynamic arrays 51 | 52 | ## Triggering Events 53 | 54 | In C++ code, event triggering methods are automatically generated with the format `emit` + event name. For example: 55 | 56 | ```cpp 57 | class MyContract : public Events { 58 | CResult transfer(const Address& from, const Address& to, const uint256& amount) { 59 | // Business logic... 60 | 61 | // Trigger Transfer event 62 | emitTransfer(from, to, amount); 63 | 64 | return Ok(); 65 | } 66 | 67 | CResult approve(const Address& spender, const uint256& value) { 68 | // Business logic... 69 | 70 | // Trigger Approval event with indexed parameters 71 | emitApproval(get_msg_sender(), spender, value); 72 | 73 | return Ok(); 74 | } 75 | 76 | void receive() override { 77 | // Trigger anonymous Deposit event 78 | emitDeposit(get_msg_sender(), get_msg_value()); 79 | } 80 | 81 | CResult test_complex_event() { 82 | // Trigger event with multiple parameter types 83 | emitComplexEvent( 84 | true, // bool 85 | uint8_t(123), // uint8 86 | int16_t(-100), // int16 87 | uint256(1000000), // uint256 88 | std::string("Hello"), // string 89 | Bytes({1, 2, 3, 4}), // bytes 90 | std::array{0} // bytes32 91 | ); 92 | 93 | return Ok(); 94 | } 95 | }; 96 | ``` 97 | 98 | ## Indexed Parameters 99 | 100 | Indexed parameters are event parameters that can be efficiently indexed and filtered externally. Use the `indexed` keyword when declaring events: 101 | 102 | ```cpp 103 | SOLIDITY(indexed_events, R"""( 104 | abstract contract IndexedEvents { 105 | // Maximum of 3 indexed parameters supported 106 | event Transfer( 107 | address indexed from, // Can be indexed 108 | address indexed to, // Can be indexed 109 | uint256 amount // Normal parameter 110 | ); 111 | 112 | // Support for different types of indexed parameters 113 | event CustomEvent( 114 | uint256 indexed id, 115 | bytes32 indexed hash, 116 | address indexed account, 117 | string data 118 | ); 119 | } 120 | )""") 121 | ``` 122 | 123 | When triggering events with indexed parameters in C++ code, parameter order must match the declaration: 124 | 125 | ```cpp 126 | // Trigger event with indexed parameters 127 | emitTransfer(sender_address, receiver_address, amount); 128 | 129 | // Trigger event with multiple indexed parameters 130 | emitCustomEvent( 131 | uint256(123), // indexed id 132 | bytes32_hash, // indexed hash 133 | some_address, // indexed account 134 | "some data" // normal parameter 135 | ); 136 | ``` 137 | 138 | ## Best Practices 139 | 140 | 1. **Event Naming Conventions** 141 | ```cpp 142 | // Recommended naming patterns 143 | event Transfer(...); // Represents an action 144 | event Approval(...); // Represents a state change 145 | event DepositReceived(...); // Clearly expresses event meaning 146 | ``` 147 | 148 | 2. **Appropriate Use of Indexed Parameters** 149 | - Use for important fields that need external query filtering 150 | - Maximum of 3 indexed parameters per event 151 | - Consider the balance between query performance and gas cost 152 | 153 | ```cpp 154 | // Good use of indexed parameters 155 | event Transfer( 156 | address indexed from, // For querying transfers sent by users 157 | address indexed to, // For querying transfers received by users 158 | uint256 amount // Amount doesn't need indexing 159 | ); 160 | ``` 161 | 162 | 3. **Event Parameter Design** 163 | - Include sufficient information to track state changes 164 | - Avoid including sensitive information 165 | - Consider future compatibility 166 | 167 | ```cpp 168 | // Event with comprehensive information 169 | event TokenMint( 170 | address indexed to, 171 | uint256 amount, 172 | uint256 totalSupply, // Includes updated total supply 173 | uint256 timestamp // Includes timestamp 174 | ); 175 | ``` 176 | 177 | 4. **Documentation Comments** 178 | It's recommended to add comments explaining important events: 179 | 180 | ```cpp 181 | SOLIDITY(documented_events, R"""( 182 | abstract contract DocumentedEvents { 183 | /// @notice Triggered when tokens are transferred 184 | /// @param from Token sender address 185 | /// @param to Token recipient address 186 | /// @param amount Transfer amount 187 | event Transfer(address indexed from, address indexed to, uint256 amount); 188 | } 189 | )""") 190 | ``` 191 | 192 | 5. **Events in Error Handling** 193 | Events can also be triggered when handling errors or special cases: 194 | 195 | ```cpp 196 | CResult transfer(const Address& to, const uint256& amount) { 197 | const auto& sender = get_msg_sender(); 198 | uint256 balance = balances_->get(sender); 199 | 200 | if (balance < amount) { 201 | // Trigger transfer failure event 202 | emitTransferFailed(sender, to, amount, "insufficient balance"); 203 | return Revert("insufficient balance"); 204 | } 205 | 206 | // ... transfer logic ... 207 | 208 | // Trigger success event 209 | emitTransfer(sender, to, amount); 210 | return Ok(); 211 | } 212 | ``` 213 | 214 | 6. **Combining Events with Storage** 215 | - Update storage and trigger events for important state changes 216 | - Use events to record historical change records 217 | - Provide additional context information through events 218 | -------------------------------------------------------------------------------- /docs/basic_concepts/storage.md: -------------------------------------------------------------------------------- 1 | Storage Variables Usage Guide 2 | =============== 3 | 4 | This document details how to use Solidity-style storage variables in C++ WASM contracts. 5 | 6 | ## Contents 7 | 8 | - [Storage Variable Declaration](#storage-variable-declaration) 9 | - [Basic Type Storage](#basic-type-storage) 10 | - [Mapping Type Storage](#mapping-type-storage) 11 | - [Array Type Storage](#array-type-storage) 12 | - [Storage Variable Access Control](#storage-variable-access-control) 13 | - [Best Practices](#best-practices) 14 | 15 | ## Storage Variable Declaration 16 | 17 | When declaring contracts using the SOLIDITY macro, you can define various types of storage variables: 18 | 19 | ```cpp 20 | SOLIDITY(contract_name, R"""( 21 | abstract contract Storage { 22 | // Basic types 23 | bool private bool_value; 24 | uint8 private uint8_value; 25 | int16 private int16_value; 26 | uint256 private uint256_value; 27 | string private string_value; 28 | 29 | // bytes types 30 | bytes private bytes_value; 31 | bytes32 private bytes32_value; 32 | 33 | // mapping types 34 | mapping(address => uint256) private balances; 35 | mapping(address => mapping(address => uint256)) private allowances; 36 | 37 | // array types 38 | uint32[] private uint32_array; 39 | } 40 | )""") 41 | ``` 42 | 43 | For specific mappings between Solidity state variable types and C++ types, please refer to the documentation [types.md](types.md). 44 | 45 | ## Basic Type Storage 46 | 47 | When accessing basic type storage variables in C++, variable names automatically have an underscore suffix `_`. All storage variables are pointer types and need to be accessed using `->`. 48 | 49 | ### Reading Storage Values 50 | 51 | Use the `get()` method to read storage values: 52 | 53 | ```cpp 54 | // Read bool value 55 | bool value = bool_value_->get(); 56 | 57 | // Read numeric values 58 | uint8_t v1 = uint8_value_->get(); 59 | int16_t v2 = int16_value_->get(); 60 | uint256 v3 = uint256_value_->get(); 61 | 62 | // Read string 63 | std::string str = string_value_->get(); 64 | 65 | // Read bytes 66 | Bytes b1 = bytes_value_->get(); 67 | std::array b2 = bytes32_value_->get(); 68 | ``` 69 | 70 | ### Writing Storage Values 71 | 72 | Use the `set()` method to write storage values: 73 | 74 | ```cpp 75 | // Write bool value 76 | bool_value_->set(true); 77 | 78 | // Write numeric values 79 | uint8_value_->set(123); 80 | int16_value_->set(-100); 81 | uint256_value_->set(uint256(1000000)); 82 | 83 | // Write string 84 | string_value_->set("Hello"); 85 | 86 | // Write bytes 87 | bytes_value_->set(Bytes({1, 2, 3, 4})); 88 | std::array b32 = {0}; 89 | bytes32_value_->set(b32); 90 | ``` 91 | 92 | ## Mapping Type Storage 93 | 94 | Mapping types support both single-level and multi-level mappings. When using them in C++, access and modify values through the `get()` and `set()` methods. 95 | 96 | ### Single-level Mapping 97 | 98 | ```cpp 99 | // Read mapping value 100 | Address account = /*...*/; 101 | uint256 balance = balances_->get(account); 102 | 103 | // Modify mapping value 104 | balances_->set(account, uint256(1000)); 105 | ``` 106 | 107 | ### Multi-level Mapping 108 | 109 | The SDK provides convenient access methods for two-level mappings: 110 | ```cpp 111 | // allowances_ has Solidity type mapping(address => mapping(address => uint256)) 112 | // Read two-level mapping 113 | Address owner = /*...*/; 114 | Address spender = /*...*/; 115 | uint256 allowance = allowances_->get(owner, spender); 116 | 117 | // Modify two-level mapping 118 | allowances_->set(owner, spender, uint256(500)); 119 | ``` 120 | 121 | ## Array Type Storage 122 | 123 | Array type storage provides rich operation methods. 124 | 125 | ### Basic Array Operations 126 | 127 | ```cpp 128 | // Get array length 129 | size_t len = uint32_array_->size(); 130 | 131 | // Read value at specific position 132 | uint32_t value = uint32_array_->get(0); 133 | 134 | // Modify value at specific position 135 | uint32_array_->set(0, 123); 136 | 137 | // Append element 138 | uint32_array_->push(456); 139 | 140 | // Pop last element (only modifies array length, doesn't clear the original value. Developers need to be aware of this) 141 | uint32_array_->pop(); 142 | 143 | ``` 144 | 145 | ### Iterating Arrays 146 | 147 | ```cpp 148 | size_t len = uint32_array_->size(); 149 | for(size_t i = 0; i < len; i++) { 150 | uint32_t value = uint32_array_->get(i); 151 | // Process value... 152 | } 153 | ``` 154 | 155 | ## Storage Variable Access Control 156 | 157 | Storage variable access control in Solidity (public, private, internal) affects the generated C++ code: 158 | 159 | - `private`: Can only be accessed within the current contract 160 | - `internal`: Can be accessed within the current contract and its inheriting contracts 161 | - `public`: Automatically generates getter methods, can be accessed externally 162 | 163 | ```cpp 164 | SOLIDITY(storage_access, R"""( 165 | abstract contract StorageAccess { 166 | uint256 private priv_value; // Can only be accessed within the contract 167 | uint256 internal internal_value; // Can be accessed in inheriting contracts 168 | uint256 public pub_value; // Automatically generates getter method 169 | } 170 | )""") 171 | ``` 172 | 173 | Storage variables with different access permissions all generate meaningful storage slots and internal state variables, but public and internal also generate corresponding public contract interfaces and internal C++ access functions. 174 | 175 | ## Best Practices 176 | 177 | 1. **Atomic Operations** 178 | For scenarios requiring atomic operations (such as balance updates), complete reading and writing in a single function call: 179 | 180 | ```cpp 181 | CResult transfer(const Address &to, const uint256& amount) { 182 | const auto& sender = get_msg_sender(); 183 | uint256 sender_bal = balances_->get(sender); 184 | if (sender_bal < amount) { 185 | return Revert("insufficient balance"); 186 | } 187 | balances_->set(sender, sender_bal - amount); 188 | uint256 to_bal = balances_->get(to); 189 | balances_->set(to, to_bal + amount); 190 | return Ok(); 191 | } 192 | ``` 193 | 194 | 2. **Storage Space Optimization** 195 | To save storage space, it's recommended to: 196 | - Use appropriately sized integer types 197 | - Use memory variables instead of storage variables for data that doesn't need on-chain storage 198 | 199 | 3. **Security Considerations** 200 | - Perform necessary permission checks before modifying storage 201 | - Validate user input 202 | - Consider integer overflow issues 203 | 204 | -------------------------------------------------------------------------------- /docs/basic_concepts/types.md: -------------------------------------------------------------------------------- 1 | Type System 2 | ============== 3 | 4 | This document details the type system in the C++ WASM contract SDK, including the mapping relationship between Solidity types and C++ types, as well as how to use each type. 5 | 6 | ## Basic Type Mapping 7 | 8 | The following correspondences exist between Solidity types and C++ types: 9 | 10 | | Solidity Type | C++ Type | Description | 11 | |------------|---------|------| 12 | | address | Address | 20-byte address type | 13 | | uint256 | uint256 | 256-bit unsigned integer | 14 | | uint128 | __uint128_t | 128-bit unsigned integer | 15 | | uint64 | uint64_t | 64-bit unsigned integer | 16 | | uint32 | uint32_t | 32-bit unsigned integer | 17 | | uint8 | uint8_t | 8-bit unsigned integer | 18 | | int256 | int256 | 256-bit signed integer | 19 | | int128 | __int128_t | 128-bit signed integer | 20 | | int64 | int64_t | 64-bit signed integer | 21 | | int32 | int32_t | 32-bit signed integer | 22 | | int8 | int8_t | 8-bit signed integer | 23 | | bool | bool | Boolean type | 24 | | string | std::string | String type | 25 | | T[] | std::vector | Array type | 26 | 27 | ## Example Code 28 | 29 | ```cpp 30 | #include "contractlib/v1/contractlib.hpp" 31 | 32 | SOLIDITY(type_example, R"""( 33 | pragma solidity ^0.8.0; 34 | 35 | contract TypeExample { 36 | // Basic type storage variables 37 | address public owner; 38 | uint256 public value; 39 | uint128 public smallValue; 40 | bool public flag; 41 | string public name; 42 | 43 | // Function parameters and return values using various types 44 | function exampleFunction( 45 | address _addr, 46 | uint256 _value, 47 | uint128 _smallValue, 48 | bool _flag, 49 | string memory _name 50 | ) public returns (uint256); 51 | } 52 | )""") 53 | 54 | class TypeExampleImpl : public TypeExample { 55 | protected: 56 | CResult constructor(dtvm::CallInfoPtr call_info, Input &input) override { 57 | // Using various types in the constructor 58 | owner_->set(get_msg_sender()); // Address type 59 | value_->set(uint256(1000)); // uint256 type 60 | smallValue_->set(__uint128_t(100)); // uint128 type 61 | flag_->set(true); // bool type 62 | name_->set("Example"); // string type 63 | return Ok(); 64 | } 65 | 66 | CResult exampleFunction( 67 | const Address& _addr, // address maps to Address 68 | const uint256& _value, // uint256 maps to uint256 69 | const __uint128_t& _smallValue, // uint128 maps to __uint128_t 70 | bool _flag, // bool maps to bool 71 | const std::string& _name // string maps to std::string 72 | ) override { 73 | // Using passed parameters 74 | owner_->set(_addr); 75 | value_->set(_value); 76 | smallValue_->set(_smallValue); 77 | flag_->set(_flag); 78 | name_->set(_name); 79 | 80 | return Ok(_value); 81 | } 82 | }; 83 | 84 | ENTRYPOINT(TypeExampleImpl) 85 | ``` 86 | 87 | ## Array Type Mapping 88 | 89 | Solidity's array types are mapped in C++ in two ways: 90 | 91 | 1. Arrays in function parameters/return values/event parameters: 92 | - Mapped to `std::vector` 93 | - T is the corresponding C++ basic type 94 | 95 | 2. Arrays in state storage variables: 96 | - Mapped to `StorageArray` 97 | - T is the corresponding C++ basic type 98 | 99 | Example code: 100 | 101 | ```cpp 102 | SOLIDITY(array_example, R"""( 103 | pragma solidity ^0.8.0; 104 | 105 | contract ArrayExample { 106 | // Storage arrays 107 | uint256[] public values; 108 | address[] public addresses; 109 | 110 | // Function parameters using arrays 111 | function processArrays( 112 | uint256[] memory _values, 113 | address[] memory _addresses 114 | ) public returns (uint256[] memory); 115 | } 116 | )""") 117 | 118 | class ArrayExampleImpl : public ArrayExample { 119 | protected: 120 | CResult constructor(dtvm::CallInfoPtr call_info, Input &input) override { 121 | // Using storage arrays 122 | values_->push_back(uint256(100)); 123 | values_->push_back(uint256(200)); 124 | 125 | addresses_->push_back(get_msg_sender()); 126 | return Ok(); 127 | } 128 | 129 | CResult processArrays( 130 | const std::vector& _values, // Array parameter 131 | const std::vector
& _addresses // Array parameter 132 | ) override { 133 | // Return vector 134 | std::vector result; 135 | result.push_back(uint256(1)); 136 | result.push_back(uint256(2)); 137 | return Ok(result); 138 | } 139 | }; 140 | ``` 141 | 142 | ## Mapping Type Mapping 143 | 144 | Solidity's mapping type is mapped in C++ to `StorageMap`: 145 | 146 | ```cpp 147 | SOLIDITY(mapping_example, R"""( 148 | pragma solidity ^0.8.0; 149 | 150 | contract MappingExample { 151 | // mapping storage variables 152 | mapping(address => uint256) public balances; 153 | mapping(uint256 => string) public names; 154 | } 155 | )""") 156 | 157 | class MappingExampleImpl : public MappingExample { 158 | protected: 159 | CResult constructor(dtvm::CallInfoPtr call_info, Input &input) override { 160 | // Using mapping 161 | Address addr = get_msg_sender(); 162 | balances_->set(addr, uint256(1000)); 163 | 164 | names_->set(uint256(1), "Alice"); 165 | names_->set(uint256(2), "Bob"); 166 | 167 | // Reading from mapping 168 | uint256 balance = balances_->get(addr); 169 | std::string name = names_->get(uint256(1)); 170 | 171 | return Ok(); 172 | } 173 | }; 174 | ``` 175 | 176 | ## Important Notes 177 | 178 | 1. When using integer types in C++, special attention needs to be paid to overflow issues, especially when calculating amounts 179 | 180 | 2. When using complex types (string, arrays, etc.) in function parameters, it's recommended to use const references to avoid unnecessary copying 181 | 182 | 3. StorageArray and StorageMap are special storage types that can only be used for state variables, not for local variables 183 | 184 | 4. Operations on storage types (such as set, get, etc.) may incur gas costs, so gas optimization should be considered when using them 185 | 186 | These type mapping relationships allow developers to conveniently handle various Solidity data types in C++ while maintaining type safety and gas efficiency. 187 | -------------------------------------------------------------------------------- /docs/best_practices/coding_standards.md: -------------------------------------------------------------------------------- 1 | # Coding Standards 2 | 3 | This document outlines the coding standards and best practices for developing smart contracts with DTVM_CppSDK. 4 | 5 | ## Code Style 6 | 7 | ### Indentation and Formatting 8 | 9 | - Use **4 spaces** for indentation (not tabs) 10 | - Keep line length under 100 characters 11 | - Use consistent bracing style 12 | - Group related code together 13 | 14 | Example: 15 | ```cpp 16 | class MyContract : public IContract { 17 | protected: 18 | // Good: Consistent indentation with 4 spaces 19 | CResult myFunction(const Address& addr, const uint256& amount) override { 20 | if (amount > 0) { 21 | // Function implementation 22 | return Ok(); 23 | } else { 24 | return Revert("Invalid amount"); 25 | } 26 | } 27 | }; 28 | ``` 29 | 30 | ### Naming Conventions 31 | 32 | - **Classes**: PascalCase (e.g., `TokenContract`) 33 | - **Functions**: camelCase (e.g., `transferTokens`) 34 | - **Variables**: camelCase (e.g., `tokenBalance`) 35 | - **Constants**: UPPER_SNAKE_CASE (e.g., `MAX_SUPPLY`) 36 | - **Private members**: camelCase with underscore suffix (e.g., `balance_`) 37 | 38 | ## Documentation and Comments 39 | 40 | ### File Headers 41 | 42 | Each source file should start with a header comment that includes: 43 | - Brief description of the file 44 | - Author information 45 | - License information 46 | 47 | Example: 48 | ```cpp 49 | /** 50 | * @file MyToken.cpp 51 | * @brief Implementation of a basic ERC20-compatible token contract 52 | * @author Your Name 53 | * @license See LICENSE file 54 | */ 55 | ``` 56 | 57 | ### Class and Interface Documentation 58 | 59 | Document classes and interfaces with a description of their purpose and any important implementation notes: 60 | 61 | ```cpp 62 | /** 63 | * @brief A simple ERC20-compatible token contract 64 | * 65 | * This contract implements the basic functionality of an ERC20 token: 66 | * - Token transfers 67 | * - Balance checking 68 | * - Allowance mechanism 69 | * 70 | * Note: This implementation has not been audited for security. 71 | */ 72 | class MyTokenImpl : public MyToken { 73 | // Implementation... 74 | }; 75 | ``` 76 | 77 | ### Function Documentation 78 | 79 | Document all public and protected functions with: 80 | - Brief description 81 | - Parameter descriptions 82 | - Return value description 83 | - Any side effects or exceptions 84 | 85 | Example: 86 | ```cpp 87 | /** 88 | * @brief Transfer tokens from sender to another address 89 | * 90 | * @param to The address to transfer tokens to 91 | * @param amount The amount of tokens to transfer 92 | * @return CResult Success if transfer completed, Revert with error message otherwise 93 | * 94 | * Emits a Transfer event if successful. 95 | * Reverts if sender has insufficient balance. 96 | */ 97 | CResult transfer(const Address &to, const uint256& amount) override { 98 | // Implementation... 99 | } 100 | ``` 101 | 102 | ### Comments Within Code 103 | 104 | - Use comments to explain **why** code works a certain way, not just what it does 105 | - Avoid unnecessary comments that simply restate the code 106 | - Comment complex algorithms or business logic 107 | 108 | Good: 109 | ```cpp 110 | // Apply 2.5% fee for external transfers 111 | // (0.5% to treasury, 2% to liquidity pool) 112 | uint256 fee = amount * 25 / 1000; 113 | ``` 114 | 115 | Bad: 116 | ```cpp 117 | // Subtract amount from sender 118 | balances_->set(sender, senderBal - amount); // Don't do this 119 | ``` 120 | 121 | ## Contract Structure 122 | 123 | ### Separation of Concerns 124 | 125 | - Separate logical components into different contracts 126 | - Use inheritance effectively, but avoid deep inheritance hierarchies 127 | - Consider using libraries for common functionality 128 | 129 | ### State Variables 130 | 131 | - Declare state variables at the top of the contract 132 | - Group related variables together 133 | - Use appropriate visibility modifiers 134 | 135 | ### Function Organization 136 | 137 | Organize functions in the following order: 138 | 1. Constructor 139 | 2. External/public functions 140 | 3. Internal/protected helper functions 141 | 4. Private functions 142 | 143 | ## Error Handling 144 | 145 | - Use descriptive error messages in `Revert` calls 146 | - Validate all inputs at the beginning of functions 147 | - Handle edge cases explicitly 148 | 149 | Example: 150 | ```cpp 151 | CResult withdraw(const uint256& amount) override { 152 | // Input validation first 153 | if (amount == 0) { 154 | return Revert("Amount must be greater than zero"); 155 | } 156 | 157 | const auto& sender = get_msg_sender(); 158 | uint256 balance = balances_->get(sender); 159 | 160 | if (balance < amount) { 161 | return Revert("Insufficient balance"); 162 | } 163 | 164 | // Implementation after validation passes 165 | // ... 166 | } 167 | ``` 168 | 169 | ## Testing 170 | 171 | - Write tests for all contract functionality 172 | - Test edge cases and failure conditions 173 | - Structure tests logically by function or feature 174 | 175 | ## Security Considerations 176 | 177 | - Be aware of common smart contract vulnerabilities 178 | - Avoid using timestamp for critical logic 179 | - Check for integer overflow/underflow 180 | - Use access control for sensitive functions 181 | - Consider gas costs and optimization 182 | 183 | ## Documentation Maintenance 184 | 185 | - Update documentation when code changes 186 | - Keep examples in documentation up to date 187 | - Document known limitations or issues 188 | -------------------------------------------------------------------------------- /docs/getting_started/installation.md: -------------------------------------------------------------------------------- 1 | # Installation Guide 2 | 3 | This document outlines the steps to install the DTVM_CppSDK toolkit and its dependencies. 4 | 5 | ## System Requirements 6 | 7 | - **Operating System**: Ubuntu 22.04 (recommended) 8 | - **Disk Space**: At least 1GB free space 9 | - **Memory**: 4GB RAM or more recommended 10 | 11 | ## Installing Dependencies 12 | 13 | ### 1. Basic Tools 14 | 15 | Install the required basic tools: 16 | 17 | ```bash 18 | apt update -y 19 | apt install -y git python3 xz-utils 20 | ``` 21 | 22 | ### 2. Emscripten SDK 23 | 24 | Install Emscripten version 3.1.69 (recommended for compatibility): 25 | 26 | ```bash 27 | mkdir -p /opt 28 | cd /opt 29 | git clone https://github.com/emscripten-core/emsdk.git 30 | cd emsdk 31 | ./emsdk install 3.1.69 32 | ./emsdk activate 3.1.69 33 | 34 | # Activate in current shell 35 | source "/opt/emsdk/emsdk_env.sh" 36 | 37 | # Configure for shell startup (optional) 38 | echo 'source "/opt/emsdk/emsdk_env.sh"' >> $HOME/.bash_profile 39 | ``` 40 | 41 | ### 3. DTVM_CppSDK Toolkit 42 | 43 | #### Option 1: From Release Package 44 | 45 | Download and install the toolkit: 46 | 47 | ```bash 48 | # Copy the toolkit package to /opt directory 49 | cp path/to/DTVM_CppSDK.linux.latest.tar.gz /opt 50 | cd /opt 51 | tar -xzvf DTVM_CppSDK.linux.latest.tar.gz 52 | 53 | # Verify the binaries are working 54 | ./solc --version 55 | ./solidcpp --version 56 | 57 | # Add toolkit to PATH (optional) 58 | echo 'export PATH="/opt:$PATH"' >> $HOME/.bash_profile 59 | source $HOME/.bash_profile 60 | ``` 61 | 62 | #### Option 2: From Source (for contributors) 63 | 64 | Clone the repository and build: 65 | 66 | ```bash 67 | git clone https://github.com/DTVMStack/DTVM_CppSDK.git 68 | cd DTVM_CppSDK 69 | bash build_lib.sh 70 | ``` 71 | 72 | ## Docker Installation (Alternative) 73 | 74 | If you prefer using Docker, we provide a Docker image with all dependencies pre-installed: 75 | 76 | ```bash 77 | # Pull the Docker image 78 | docker pull yourorg/dtvmcppcompiler:latest 79 | 80 | # Run a container 81 | docker run -it --name dtvmcpp-dev yourorg/dtvmcppcompiler:latest 82 | 83 | # For development with volume mounting 84 | docker run -it -v $(pwd):/workspace --name dtvmcpp-dev yourorg/dtvmcppcompiler:latest 85 | ``` 86 | 87 | ## Verifying Installation 88 | 89 | To verify that DTVM_CppSDK is correctly installed, try building one of the example contracts: 90 | 91 | ```bash 92 | cd examples/example1 93 | bash build_wasm.sh 94 | ``` 95 | 96 | If the build completes successfully, you should see a file named `my_token.wasm` in the current directory. 97 | 98 | ## Next Steps 99 | 100 | After installation, refer to the [Quick Start Guide](quick_start.md) to begin developing your first smart contract with DTVM_CppSDK. 101 | -------------------------------------------------------------------------------- /docs/getting_started/quick_start.md: -------------------------------------------------------------------------------- 1 | # Quick Start Guide 2 | 3 | This guide will help you quickly develop and test your first smart contract using DTVM_CppSDK. 4 | 5 | ## Prerequisites 6 | 7 | - DTVM_CppSDK installed (see [Installation Guide](installation.md)) 8 | - Basic knowledge of C++ programming 9 | - Familiarity with blockchain concepts 10 | 11 | ## Creating Your First Smart Contract 12 | 13 | ### 1. Create a New Project Directory 14 | 15 | ```bash 16 | mkdir -p my_first_contract 17 | cd my_first_contract 18 | ``` 19 | 20 | ### 2. Copy the Example Files 21 | 22 | The fastest way to get started is to use one of the existing examples as a template: 23 | 24 | ```bash 25 | # Assuming DTVM_CppSDK is installed in /opt 26 | cp -r /opt/examples/example1/* . 27 | ``` 28 | 29 | ### 3. Understand the Contract Structure 30 | 31 | Open the main contract file: 32 | 33 | ```bash 34 | # Open the file in your preferred editor 35 | nano my_token.cpp 36 | ``` 37 | 38 | Key components to understand: 39 | 40 | - **SOLIDITY** macro: Defines the contract interface in Solidity syntax 41 | - **Contract implementation class**: C++ class that implements the Solidity interface 42 | - **ENTRYPOINT** macro: Generates entry points for the contract 43 | 44 | ### 4. Modify the Contract 45 | 46 | Let's make a simple modification to the token contract: 47 | 48 | 1. Change the initial token supply 49 | 2. Modify the token name 50 | 51 | Example changes: 52 | 53 | ```cpp 54 | // In the constructor method 55 | this->balances_->set(get_msg_sender(), uint256(5000000)); // Changed from 10000000 56 | this->token_symbol_->set("MYT"); // Changed to "MYT" 57 | ``` 58 | 59 | ### 5. Build the Contract 60 | 61 | Build your contract to WebAssembly: 62 | 63 | ```bash 64 | # Build the contract 65 | ./build_wasm.sh 66 | ``` 67 | 68 | This will generate: 69 | - `my_token.wasm`: The WebAssembly bytecode 70 | - `my_token.wat`: A text representation of the WebAssembly bytecode 71 | 72 | ### 6. Test the Contract 73 | 74 | Test your contract using the mock CLI: 75 | 76 | ```bash 77 | # Run the test script 78 | ./test_in_mock_cli.sh 79 | ``` 80 | 81 | ## Contract Development Workflow 82 | 83 | 1. **Design**: Plan your contract functionality and state variables 84 | 2. **Implement**: Write your contract in C++ following the Solidity interface 85 | 3. **Build**: Compile to WebAssembly using the build tools 86 | 4. **Test**: Test locally using the mock CLI 87 | 5. **Deploy**: Deploy to a test network or mainnet 88 | 89 | ## Next Steps 90 | 91 | - Explore the [examples directory](../../examples/) for more complex contracts 92 | - Learn about [contract storage](../basic_concepts/storage.md) 93 | - Understand [contract events](../basic_concepts/events.md) 94 | - Study [contract interfaces](../advanced_features/contract_interfaces.md) 95 | 96 | ## Troubleshooting 97 | 98 | ### Common Issues 99 | 100 | 1. **Build Failures** 101 | - Check that Emscripten is correctly installed and activated 102 | - Ensure your C++ code is valid and follows C++17 standard 103 | 104 | 2. **Runtime Errors** 105 | - Check your contract's logic for issues 106 | - Verify storage variable types match their usage 107 | 108 | 3. **Testing Problems** 109 | - Ensure the mock CLI is correctly installed 110 | - Check the input format for contract calls 111 | 112 | For more detailed troubleshooting, see the [Testing Guide](../best_practices/testing.md). 113 | 114 | ## Example: Complete Token Contract 115 | 116 | Here's a minimal ERC20-like token contract example: 117 | 118 | ```cpp 119 | #include "contractlib/v1/contractlib.hpp" 120 | 121 | SOLIDITY(minimal_token, R"""( 122 | pragma solidity ^0.8.0; 123 | 124 | interface IMinimalToken { 125 | function balanceOf(address owner) external returns(uint256); 126 | function transfer(address to, uint256 amount) external; 127 | } 128 | 129 | abstract contract MinimalToken is IMinimalToken { 130 | mapping(address => uint256) private balances; 131 | string private name; 132 | 133 | event Transfer(address from, address to, uint256 amount); 134 | } 135 | )""") 136 | 137 | #include "generated/minimal_token_decl.hpp" 138 | 139 | using namespace dtvm; 140 | 141 | class MinimalTokenImpl : public MinimalToken { 142 | protected: 143 | CResult constructor(dtvm::CallInfoPtr call_info, Input &input) override { 144 | std::string token_name = input.read(); 145 | this->name_->set(token_name); 146 | balances_->set(get_msg_sender(), uint256(1000000)); 147 | return Ok(); 148 | } 149 | 150 | CResult balanceOf(const Address &owner) override { 151 | return Ok(this->balances_->get(owner)); 152 | } 153 | 154 | CResult transfer(const Address &to, const uint256& amount) override { 155 | const auto& sender = get_msg_sender(); 156 | uint256 sender_bal = this->balances_->get(sender); 157 | if (sender_bal < amount) { 158 | return Revert("insufficient balance"); 159 | } 160 | this->balances_->set(sender, sender_bal - amount); 161 | uint256 to_bal = this->balances_->get(to); 162 | this->balances_->set(to, to_bal + amount); 163 | emitTransfer(sender, to, amount); 164 | return Ok(); 165 | } 166 | }; 167 | 168 | ENTRYPOINT(MinimalTokenImpl) 169 | ``` 170 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # DTVM_CppSDK Documentation 2 | 3 | > Note: This toolkit currently supports Ubuntu 22.04 environment. Support for additional environments will be added after further testing. 4 | 5 | ## Documentation Index 6 | 7 | ### Getting Started 8 | * [Installation Guide](getting_started/installation.md) - Guide to install the toolkit 9 | * [Quick Start](getting_started/quick_start.md) - Get up and running quickly 10 | 11 | ### Usage 12 | * [Usage Guide](usage.md) - Dependency installation and usage instructions 13 | * [Tutorial](tutorial.md) - Basic guide to C++ contract development 14 | 15 | ### Basic Concepts 16 | * [Types](basic_concepts/types.md) - Supported contract data types 17 | * [Storage](basic_concepts/storage.md) - Contract storage mechanisms 18 | * [Events](basic_concepts/events.md) - Working with contract events 19 | * [Constructors](basic_concepts/constructors.md) - Contract constructor implementation 20 | 21 | ### Advanced Features 22 | * [Contract Interfaces](advanced_features/contract_interfaces.md) - Defining contract interfaces 23 | * [Contract Interaction](advanced_features/contract_interaction.md) - Interaction between contracts 24 | * [Solidity Integration](advanced_features/solidity_integration.md) - Integrating with Solidity contracts 25 | 26 | ### Best Practices 27 | * [Coding Standards](best_practices/coding_standards.md) - Recommended coding standards 28 | * [Testing](best_practices/testing.md) - Testing your contracts 29 | 30 | ## Example Contracts 31 | 32 | For practical examples, see the [examples directory](../examples/) in the repository. 33 | -------------------------------------------------------------------------------- /docs/tutorial.md: -------------------------------------------------------------------------------- 1 | Tutorial 2 | ======== 3 | 4 | # Tutorial for Writing WASM Contracts in DTVM_CppSDK 5 | 6 | This tutorial introduces how to use DTVM_CppSDK to write WASM contracts in C++. The main feature of this library is the ability to declare Solidity interfaces, events, and storage in C++ contracts using the `SOLIDITY` macro. You can then write C++ classes that inherit these Solidity interfaces and implement business logic in C++. In the C++ code, you can read and write Solidity storage, emit events, call other contracts, and more. 7 | 8 | Next, I will walk you through a complete example code to explain step by step how to use this library to write a simple contract. 9 | 10 | ## Table of Contents 11 | 12 | - [Environment Setup](#environment-setup) 13 | - [Using the SOLIDITY Macro](#using-the-solidity-macro) 14 | - [Generating C++ Declaration Files](#generating-c-declaration-files) 15 | - [Implementing Contract Logic](#implementing-contract-logic) 16 | - [Constructor](#constructor) 17 | - [Reading Storage Data](#reading-storage-data) 18 | - [Calling Other Contracts](#calling-other-contracts) 19 | - [Implementing Interface Methods](#implementing-interface-methods) 20 | - [Receiving Native Tokens](#receiving-native-tokens) 21 | - [Defining Entry Points](#defining-entry-points) 22 | - [Complete Code Example](#complete-code-example) 23 | 24 | ## Environment Setup 25 | 26 | Before starting, make sure you have installed this toolkit and its dependencies as described in the [usage.md](usage.md) document. 27 | 28 | ## Using the SOLIDITY Macro 29 | 30 | In C++ code, we use the `SOLIDITY` macro to embed Solidity code. This macro does not actually perform any operations, but our toolchain uses it to generate corresponding C++ declaration files. 31 | 32 | 33 | ```cpp 34 | #include "contractlib/v1/contractlib.hpp" 35 | 36 | SOLIDITY(my_token, R"""( 37 | pragma solidity ^0.8.0; 38 | 39 | interface ITokenService { 40 | function balanceOf(address owner) external returns(uint256); 41 | function transfer(address to, uint256 amount) external; 42 | } 43 | 44 | abstract contract MyToken is ITokenService { 45 | mapping(address => uint256) private balances; 46 | string private token_symbol; 47 | bool private bool_value; 48 | uint8 private uint8_value; 49 | int16 private int16_value; 50 | uint256 private uint256_value; 51 | bytes private bytes_value; 52 | bytes32 private bytes32_value; 53 | uint32[] private uint32_array; 54 | uint128 private uint128_value; 55 | int128 private int128_value; 56 | 57 | function callOtherBalance(address token, address owner) public virtual returns (uint256); 58 | 59 | event Transfer(address from, address to, uint256 amount); 60 | } 61 | )""") 62 | ``` 63 | 64 | In the code above: 65 | 66 | - The first parameter of the `SOLIDITY` macro is the file name identifier `my_token`, which is used to generate an intermediate C++ header file. This generated C++ header file will need to be included later in the code. 67 | The second parameter is the Solidity code, enclosed in a C++ raw string literal `R"""(...)"""`. 68 | 69 | This Solidity code defines: 70 | 71 | - An interface `ITokenService` with the methods `balanceOf` and `transfer`. 72 | - An abstract contract `MyToken` that inherits from `ITokenService`, adds storage variables and events, and includes an abstract method `callOtherBalance`. 73 | 74 | ## Include the Generated C++ Header File 75 | 76 | After writing the Solidity code, the toolkit will automatically generate a C++ header file based on this Solidity code during compilation. The file name prefix is the first parameter of the `SOLIDITY` macro, which in this case is `my_token`, and the suffix is `_decl.hpp`. 77 | 78 | ```cpp 79 | #include "generated/my_token_decl.hpp" 80 | ``` 81 | 82 | ## Implementing Contract Logic 83 | 84 | Now, we can inherit `MyToken` in C++ and implement its business logic. 85 | 86 | ```cpp 87 | using namespace dtvm; 88 | 89 | class MyTokenImpl : public MyToken { 90 | // Implement business logic 91 | }; 92 | ``` 93 | 94 | ### Constructor 95 | 96 | In the constructor, we can initialize storage variables. 97 | 98 | ```cpp 99 | protected: 100 | 101 | CResult constructor(dtvm::CallInfoPtr call_info, Input &input) override { 102 | std::string symbol = input.read(); 103 | this->token_symbol_->set(symbol); 104 | balances_->set(get_msg_sender(), uint256(10000000)); 105 | 106 | this->uint128_value_->set(123); 107 | this->bytes_value_->set(Bytes ({ 1, 2, 3, 4 })); 108 | return Ok(); 109 | } 110 | ``` 111 | 112 | - Read the `symbol` symbol passed during contract deployment and store it in `token_symbol`. 113 | - Set the initial token balance for the deployer. 114 | - Initialize other storage variables. 115 | - This example contract mainly demonstrates supported syntax and does not have practical use. For meaningful examples, refer to the examples/erc20 contract. 116 | - Since constructors cannot be declared in Solidity abstract classes and interfaces, the constructor in the C++ class must read parameters from the input argument. 117 | 118 | ### Reading Storage Data 119 | 120 | Example methods demonstrating how to read storage variables. 121 | 122 | ```cpp 123 | CResult test_read_bytes() { 124 | return Ok(this->bytes_value_->get()); 125 | } 126 | 127 | CResult test_read_u128() { 128 | return Ok(this->uint128_value_->get()); 129 | } 130 | ``` 131 | 132 | ### Calling Other Contracts 133 | 134 | Implement a method to call other contracts. 135 | 136 | ```cpp 137 | CResult callOtherBalance(const Address& token, const Address& owner) override { 138 | // Call another contract via the Solidity interface 139 | auto token_contract = ITokenService::from(token); 140 | return token_contract->balanceOf(owner); 141 | } 142 | ``` 143 | 144 | - Use `ITokenService::from` to create an instance of another contract. 145 | - Call its `balanceOf` method. 146 | 147 | ### Implementing Interface Methods 148 | 149 | Implement the `balanceOf` and `transfer` methods defined in the interface. 150 | 151 | ```cpp 152 | CResult balanceOf(const Address &owner) override { 153 | uint256 bal = this->balances_->get(owner); 154 | // Example of triggering a Solidity event log 155 | emitTransfer(owner, owner, bal); 156 | return Ok(bal); 157 | } 158 | 159 | CResult transfer(const Address &to, const uint256& amount) override { 160 | const auto& sender = get_msg_sender(); 161 | uint256 sender_bal = this->balances_->get(sender); 162 | if (sender_bal < amount) { 163 | return Revert("balance not enough"); 164 | } 165 | this->balances_->set(sender, sender_bal - amount); 166 | uint256 to_bal = this->balances_->get(to); 167 | this->balances_->set(to, to_bal + amount); 168 | emitTransfer(sender, to, amount); 169 | return Ok(); 170 | } 171 | ``` 172 | 173 | - `balanceOf` returns the balance of a specified address and triggers a `Transfer` event (from self to self, indicating a query operation). 174 | - `transfer` implements token transfer logic and reverts if the balance is insufficient. 175 | 176 | ### Receiving Native Tokens 177 | 178 | Override the `receive` method to handle the case of directly receiving native tokens (without calldata). 179 | 180 | ```cpp 181 | public: 182 | void receive() override { 183 | // Called when directly receiving native tokens (without calldata) 184 | emitTransfer(Address::zero(), get_msg_sender(), get_msg_value()); 185 | } 186 | ``` 187 | 188 | ## Defining The contract's EntryPoint (Important) 189 | 190 | Use the `ENTRYPOINT` macro to define the contract's entry point, generating the dispatch functions for calls and deployment. This step is mandatory because the C++ source code may contain multiple classes, and we need to specify which class to export as the contract class using the `ENTRYPOINT` macro. 191 | 192 | ```cpp 193 | ENTRYPOINT(MyTokenImpl) 194 | ``` 195 | 196 | ## Complete Code Example 197 | 198 | The complete code example can be found in this project, in file `examples/example1/my_token.cpp`. 199 | 200 | ## Summary 201 | 202 | Through this tutorial, you have learned how to: 203 | 204 | - Use the `SOLIDITY` macro to declare Solidity interfaces and contracts in C++. 205 | - Use this tool to convert Solidity code into C++ header files. 206 | - Inherit Solidity contracts in C++ and implement business logic. 207 | - Read and write Solidity storage variables. 208 | - Trigger events and call other contracts. 209 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | How to use 2 | ============ 3 | 4 | # Environment Setup 5 | 6 | Currently, it is recommended to use Ubuntu 22.04 environment, or you can use Docker. 7 | 8 | # Docker image 9 | 10 | The fastest way to set up the compilation environment is to use a Docker image or build it based on docker/Dockerfile. 11 | 12 | ``` 13 | docker pull dtvmdev1/dtvm-cpp-dev-x64:main 14 | ``` 15 | 16 | # Installing Dependencies 17 | 18 | * Install git, python3, xz 19 | 20 | ``` 21 | apt update -y 22 | apt install -y git python3 xz-utils 23 | ``` 24 | 25 | * Install emsdk 3.1.69 (clang-based C++ to WASM compiler), it is recommended to use this specific version 26 | 27 | ``` 28 | mkdir -p /opt 29 | cd /opt 30 | git clone https://github.com/emscripten-core/emsdk.git 31 | cd emsdk 32 | ./emsdk install 3.1.69 33 | ./emsdk activate 3.1.69 34 | # activate in current bash 35 | source "/opt/emsdk/emsdk_env.sh" 36 | # configure when startup bash 37 | echo 'source "/opt/emsdk/emsdk_env.sh"' >> $HOME/.bash_profile 38 | ``` 39 | 40 | * Download the toolkit to the /opt directory 41 | 42 | ``` 43 | cp path/to/DTVM_CppSDK.tar.gz /opt 44 | cd /opt 45 | tar -xzvf DTVM_CppSDK.tar.gz 46 | # Check if all binary programs are available 47 | ./solc --version 48 | ./solidcpp --version 49 | 50 | # Add the toolkit to the PATH environment variable 51 | echo 'export PATH="/opt:$PATH"' >> $HOME/.bash_profile 52 | source $HOME/.bash_profile 53 | ``` 54 | 55 | # Developing Contracts 56 | 57 | Refer to [tutorial.md](tutorial.md) for contract development. The downloaded toolkit also includes several simple example contracts. Here we use the example contracts to illustrate how to compile contracts with this toolkit. 58 | 59 | # Compiling Contracts 60 | 61 | Here we use the ERC20 contract in the toolkit as an example 62 | 63 | ``` 64 | # Navigate to the contract directory, you should navigate to your own contract directory 65 | cd /opt/examples/erc20 66 | 67 | # Compile C++ contract source files to WASM bytecode files. If you have multiple contract source files, you can specify multiple input parameters 68 | solidcpp build --input my_erc20.cpp --contractlib-dir=../.. --generated-dir ./generated --output my_erc20.wasm 69 | 70 | # You can view the generated contract bytecode 71 | ll my_erc20.wasm 72 | 73 | ``` 74 | 75 | # Testing Contracts 76 | 77 | It is recommended to test the deployment and invocation of contract interfaces and functions on a test chain after contract development to verify the correctness of the contract. 78 | 79 | After your contracts deployed to the testnet, You can use web3 sdks or wallets to test the deployed contract. 80 | -------------------------------------------------------------------------------- /examples/c_erc20/.gitignore: -------------------------------------------------------------------------------- 1 | /*.wasm 2 | /*.o 3 | /*.wat 4 | /*.cbin 5 | /*.cbin.hex 6 | /test.db 7 | -------------------------------------------------------------------------------- /examples/c_erc20/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | export LLVM_DIR=/opt/llvm15/lib/cmake/llvm 5 | export LLVM_SYS_150_PREFIX=/opt/llvm15 6 | export PATH=/opt/llvm15/bin:$PATH 7 | # cdt is clang with wasm-sysroot 8 | clang --target=wasm32 -nostdlib -O2 -nostdlib -c erc20_c.c -I /opt/cdt/wasm-sysroot/include 9 | wasm-ld erc20_c.o -o erc20_c.wasm --no-entry --strip-all --allow-undefined --export "deploy" --export "call" /opt/cdt/wasm-sysroot/lib/libclang_rt.builtins.a 10 | 11 | wasm2wat -o erc20_c.wat erc20_c.wasm 12 | -------------------------------------------------------------------------------- /examples/c_erc20/hostapi.h: -------------------------------------------------------------------------------- 1 | #ifndef __HOSTAPI_H__ 2 | #define __HOSTAPI_H__ 3 | 4 | #include 5 | 6 | // Distinguish between uint32_t and uint64_t for representing pointer integers based on the environment, 7 | // making it easier to test in 64-bit environments 8 | 9 | #if INTPTR_MAX == INT64_MAX 10 | #define ADDRESS_UINT uint64_t 11 | #else 12 | #define ADDRESS_UINT uint32_t 13 | #define IN_WASM_ENV 14 | #endif 15 | 16 | __attribute__((import_module("env"), import_name("getAddress"))) 17 | void getAddress(ADDRESS_UINT result_offset); 18 | 19 | __attribute__((import_module("env"), import_name("getCaller"))) 20 | void getCaller(ADDRESS_UINT result_offset); 21 | 22 | __attribute__((import_module("env"), import_name("getCallValue"))) 23 | void getCallValue(ADDRESS_UINT result_offset); 24 | 25 | __attribute__((import_module("env"), import_name("getCallDataSize"))) int32_t 26 | getCallDataSize(); 27 | 28 | __attribute__((import_module("env"), import_name("callDataCopy"))) void 29 | callDataCopy(ADDRESS_UINT target, ADDRESS_UINT offset, int32_t len); 30 | 31 | __attribute__((import_module("env"), import_name("getBlockHash"))) int32_t 32 | getBlockHash(int64_t number, ADDRESS_UINT result_offset); 33 | 34 | __attribute__((import_module("env"), import_name("getBlockCoinbase"))) 35 | void getBlockCoinbase(ADDRESS_UINT result_offset); 36 | 37 | __attribute__((import_module("env"), import_name("getBlockPrevRandao"))) void getBlockPrevRandao(ADDRESS_UINT result_offset); 38 | 39 | __attribute__((import_module("env"), import_name("getBlockGasLimit"))) int64_t getBlockGasLimit(); 40 | 41 | __attribute__((import_module("env"), import_name("getBlockTimestamp"))) int64_t getBlockTimestamp(); 42 | 43 | __attribute__((import_module("env"), import_name("getGasLeft"))) int64_t getGasLeft(); 44 | 45 | __attribute__((import_module("env"), import_name("getBlockNumber"))) int64_t getBlockNumber(); 46 | 47 | __attribute__((import_module("env"), import_name("getTxGasPrice"))) void getTxGasPrice(ADDRESS_UINT value_offset); 48 | 49 | __attribute__((import_module("env"), import_name("getTxOrigin"))) void getTxOrigin(ADDRESS_UINT result_offset); 50 | 51 | __attribute__((import_module("env"), import_name("getBaseFee"))) void getBaseFee(ADDRESS_UINT result_offset); 52 | 53 | __attribute__((import_module("env"), import_name("getBlobBaseFee"))) void getBlobBaseFee(ADDRESS_UINT result_offset); 54 | 55 | __attribute__((import_module("env"), import_name("getChainId"))) void getChainId(ADDRESS_UINT result_offset); 56 | 57 | 58 | __attribute__((import_module("env"), import_name("getExternalBalance"))) 59 | void getExternalBalance(ADDRESS_UINT address_offset, ADDRESS_UINT result_offset); 60 | 61 | __attribute__((import_module("env"), import_name("getExternalCodeHash"))) 62 | void getExternalCodeHash(ADDRESS_UINT address_offset, ADDRESS_UINT result_offset); 63 | 64 | __attribute__((import_module("env"), import_name("storageLoad"))) void storageLoad( 65 | ADDRESS_UINT key_offset, 66 | ADDRESS_UINT result_offset); 67 | 68 | __attribute__((import_module("env"), import_name("storageStore"))) void storageStore( 69 | ADDRESS_UINT key_offset, 70 | ADDRESS_UINT value_offset); 71 | 72 | __attribute__((import_module("env"), import_name("transientStore"))) 73 | void transientStore(ADDRESS_UINT key_offset, ADDRESS_UINT value_offset); 74 | 75 | __attribute__((import_module("env"), import_name("transientLoad"))) 76 | void transientLoad(ADDRESS_UINT key_offset, ADDRESS_UINT result_offset); 77 | 78 | __attribute__((import_module("env"), import_name("codeCopy"))) 79 | void codeCopy(ADDRESS_UINT result_offset, int32_t code_offset, int32_t length); 80 | 81 | __attribute__((import_module("env"), import_name("getCodeSize"))) 82 | int32_t getCodeSize(); 83 | 84 | __attribute__((import_module("env"), import_name("externalCodeCopy"))) 85 | void externalCodeCopy(ADDRESS_UINT address_offset, ADDRESS_UINT result_offset, int32_t code_offset, int32_t length); 86 | 87 | __attribute__((import_module("env"), import_name("getExternalCodeSize"))) 88 | int32_t getExternalCodeSize(ADDRESS_UINT address_offset); 89 | 90 | __attribute__((import_module("env"), import_name("callContract"))) 91 | int32_t callContract(int64_t gas, ADDRESS_UINT addressOffset, ADDRESS_UINT valueOffset, ADDRESS_UINT dataOffset, int32_t dataLength); 92 | 93 | __attribute__((import_module("env"), import_name("callCode"))) 94 | int32_t callCode(int64_t gas, ADDRESS_UINT addressOffset, ADDRESS_UINT valueOffset, ADDRESS_UINT dataOffset, int32_t dataLength); 95 | 96 | __attribute__((import_module("env"), import_name("callDelegate"))) 97 | int32_t callDelegate(int64_t gas, ADDRESS_UINT addressOffset, ADDRESS_UINT dataOffset, int32_t dataLength); 98 | 99 | __attribute__((import_module("env"), import_name("callStatic"))) 100 | int32_t callStatic(int64_t gas, ADDRESS_UINT addressOffset, ADDRESS_UINT dataOffset, int32_t dataLength); 101 | 102 | __attribute__((import_module("env"), import_name("createContract"))) 103 | int32_t createContract(ADDRESS_UINT valueOffset, ADDRESS_UINT codeOffset, int32_t codeLength, 104 | ADDRESS_UINT dataOffset, int32_t dataLength, ADDRESS_UINT saltOffset, int32_t is_create2, ADDRESS_UINT resultOffset); 105 | 106 | __attribute__((import_module("env"), import_name("finish"))) void finish(ADDRESS_UINT data_offset, 107 | int32_t length); 108 | 109 | __attribute__((import_module("env"), import_name("revert"))) void revert(ADDRESS_UINT data_offset, 110 | int32_t length); 111 | 112 | __attribute__((import_module("env"), import_name("emitLogEvent"))) void emitLogEvent( 113 | ADDRESS_UINT data_offset, 114 | int32_t length, 115 | int32_t number_of_topics, 116 | ADDRESS_UINT topic1, 117 | ADDRESS_UINT topic2, 118 | ADDRESS_UINT topic3, 119 | ADDRESS_UINT topic4); 120 | 121 | __attribute__((import_module("env"), import_name("getReturnDataSize"))) 122 | int32_t getReturnDataSize(); 123 | 124 | __attribute__((import_module("env"), import_name("returnDataCopy"))) 125 | void returnDataCopy(ADDRESS_UINT resultOffset, int32_t dataOffset, int32_t length); 126 | 127 | __attribute__((import_module("env"), import_name("selfDestruct"))) 128 | void selfDestruct(ADDRESS_UINT addressOffset); 129 | 130 | __attribute__((import_module("env"), import_name("keccak256"))) 131 | void keccak256(ADDRESS_UINT inputOffset, int32_t inputLength, ADDRESS_UINT resultOffset); 132 | 133 | __attribute__((import_module("env"), import_name("sha256"))) 134 | void sha256(ADDRESS_UINT inputOffset, int32_t inputLength, ADDRESS_UINT resultOffset); 135 | 136 | #endif // __HOSTAPI_H__ 137 | -------------------------------------------------------------------------------- /examples/c_erc20/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | rm -f test.db 5 | 6 | # Read the input ERC20 wasm file and execute similar test cases as in test_my_token.sh 7 | wasm_file=$1 8 | 9 | # If no wasm file path is provided or file does not exist, show error 10 | if [ -z "$wasm_file" ] || [ ! -f "$wasm_file" ]; then 11 | echo "Usage: $0 " 12 | exit 1 13 | fi 14 | 15 | 16 | function run_cmd_and_grep() { 17 | # run command, echo result, and grep $grep. if exit, not run continue 18 | local exit_code=$? 19 | local output="$1" 20 | local grep_pattern="$2" 21 | 22 | # Echo the output 23 | echo "$output" 24 | 25 | # Check if the command was successful 26 | if [ $exit_code -ne 0 ]; then 27 | echo "Command failed with exit code $exit_code" 28 | exit $exit_code 29 | fi 30 | 31 | # Check if the output matches the grep pattern 32 | # echo "$output" | grep -E -zo "$grep_pattern" 33 | echo "matching pattern: $grep_pattern" 34 | echo "$output" | awk -v pattern="$grep_pattern" 'BEGIN { RS="\0" } $0 ~ pattern { found=1 } END { if (!found) exit 1 }' 35 | echo "grep pattern matched" 36 | } 37 | 38 | echo 'test deploy ERC20 contract $wasm_file' 39 | # deploy contract (arg total supply(uint256)) 40 | /opt/chain_mockcli -f $wasm_file --action deploy -s 0x9988776655443322119900112233445566778899 -i 0x68656c6c6f000000000000000000000000000000000000000000000000000000 41 | # total supply is optional here 42 | # /opt/chain_mockcli -f $wasm_file --action deploy -i 0x 43 | echo 'test totalSupply after deploy erc20' 44 | # query totalSupply() 45 | output=$(/opt/chain_mockcli -f $wasm_file --action call -i 0x18160ddd) 46 | run_cmd_and_grep "$output" 'evm finish with result hex: 68656c6c6f000000000000000000000000000000000000000000000000000000' 47 | 48 | echo 'test mint' 49 | # mint(owner,amount) 50 | output=$(/opt/chain_mockcli -f $wasm_file --action call -s 0x9988776655443322119900112233445566778899 -i 0x40c10f1900000000000000000000000000112233445566778899001122334455667788990000000000000000000000000000000000000000000000000000000000000007) 51 | run_cmd_and_grep "$output" 'evm finish with result hex: \ngas used' 52 | 53 | echo 'test balanceOf after mint' 54 | # balanceOf(address) after mint 68656c6c6f000000000000000000000000000000000000000000000000000007 when has total_supply big 55 | output=$(/opt/chain_mockcli -f $wasm_file --action call -i 0x70a082310000000000000000000000000011223344556677889900112233445566778899) 56 | run_cmd_and_grep "$output" 'evm finish with result hex: 0000000000000000000000000000000000000000000000000000000000000007' 57 | 58 | echo 'test transfer from owner to user2' 59 | # transfer from owner to user2 60 | output=$(/opt/chain_mockcli -f $wasm_file --action call -i 0xa9059cbb0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000005) 61 | run_cmd_and_grep "$output" 'evm finish with result hex:' 62 | 63 | echo 'test query balanceOf after transfer' 64 | # balanceOf(address) 65 | output=$(/opt/chain_mockcli -f $wasm_file --action call -i 0x70a082310000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4) 66 | run_cmd_and_grep "$output" 'evm finish with result hex: 0000000000000000000000000000000000000000000000000000000000000005' 67 | -------------------------------------------------------------------------------- /examples/erc20/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /cmake-build-debug 3 | /cmake-build-release 4 | /logs 5 | /build 6 | *.log 7 | /solc-static-linux 8 | /solc-windows 9 | /solc-macos 10 | /generated 11 | /*.wasm 12 | /*.wat 13 | /*.cbin 14 | /*.cbin.hex 15 | /*.o 16 | /test.db 17 | /sol.output 18 | /*.bin 19 | -------------------------------------------------------------------------------- /examples/erc20/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | project(DTVM_CppSDK_demo) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | include_directories(../..) 7 | 8 | add_executable(DTVM_CppSDK_demo 9 | ../../contractlib/v1/contractlib.cpp 10 | my_token.cpp) 11 | -------------------------------------------------------------------------------- /examples/erc20/build_wasm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CUR_DIR=`pwd` 4 | cd ../.. 5 | bash build_lib.sh 6 | cd $CUR_DIR 7 | 8 | /opt/solidcpp build --input my_erc20.cpp --contractlib-dir=../.. --generated-dir ./generated --output my_erc20.wasm 9 | wasm2wat -o my_erc20.wat my_erc20.wasm 10 | -------------------------------------------------------------------------------- /examples/erc20/build_wasm_steps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | CUR_DIR=`pwd` 5 | cd ../.. 6 | bash build_lib.sh 7 | cd $CUR_DIR 8 | 9 | /opt/solidcpp fetch-cpp-sol --input my_erc20.cpp --output ./generated 10 | 11 | # use solc to generate meta and storage 12 | /opt/solc --output-dir ./generated --metadata --storage-layout --overwrite ./generated/my_erc20.sol 13 | 14 | # generate decl.hpp by solidcpp 15 | /opt/solidcpp generate-hpp --input "generated/*_meta.json" --input "generated/*_storage.json" \ 16 | --output generated/my_erc20_decl.hpp 17 | 18 | # -s INITIAL_HEAP=1048576 to initial heap 1MB 19 | # you can add -D NDEBUG to disable debug print 20 | # must use emscripten at leat 3.1.69(tested) 21 | em++ -std=c++17 -o my_erc20.wasm -O3 my_erc20.cpp ../../contractlib/v1/contractlib.o -I ../.. -s 'EXPORTED_FUNCTIONS=["_call","_deploy"]' --no-entry -Wl,--allow-undefined -s ERROR_ON_UNDEFINED_SYMBOLS=0 \ 22 | -s WASM=1 -s STANDALONE_WASM=0 -s PURE_WASI=0 -s INITIAL_MEMORY=1048576 -s TOTAL_STACK=307200 23 | wasm2wat -o my_erc20.wat my_erc20.wasm 24 | -------------------------------------------------------------------------------- /examples/erc20/my_erc20.cpp: -------------------------------------------------------------------------------- 1 | #include "contractlib/v1/contractlib.hpp" 2 | 3 | // Macro to declare, actually does nothing. 4 | // solc+our repository's solidcpp tool is used to convert it into JSON and .h header files 5 | SOLIDITY(my_erc20, R"""( 6 | 7 | pragma solidity ^0.8.0; 8 | 9 | interface IERC20 { 10 | function totalSupply() external view returns (uint256); 11 | function balanceOf(address account) external view returns (uint256); 12 | function transfer(address recipient, uint256 amount) 13 | external 14 | returns (bool); 15 | 16 | function allowance(address owner, address spender) 17 | external 18 | view 19 | returns (uint256); 20 | function approve(address spender, uint256 amount) external returns (bool); 21 | function transferFrom(address sender, address recipient, uint256 amount) 22 | external 23 | returns (bool); 24 | } 25 | 26 | abstract contract ERC20 is IERC20 { 27 | event Transfer(address indexed from, address indexed to, uint256 value); 28 | event Approval( 29 | address indexed owner, address indexed spender, uint256 value 30 | ); 31 | 32 | uint256 private totalSupply; 33 | mapping(address => uint256) private balances; 34 | 35 | mapping(address => mapping(address => uint256)) private allowance; 36 | 37 | string public name; 38 | string public symbol; 39 | uint8 public decimals; 40 | 41 | function mint(address owner, uint256 amount) public virtual; 42 | } 43 | 44 | )""") 45 | 46 | #include "generated/my_erc20_decl.hpp" 47 | 48 | using namespace dtvm; 49 | 50 | class MyErc20Impl : public ERC20 { 51 | 52 | protected: 53 | 54 | CResult constructor(dtvm::CallInfoPtr call_info, Input &input) override { 55 | this->name_->set("TestToken"); 56 | this->symbol_->set("TT"); 57 | this->decimals_->set(uint8_t(4)); 58 | if (input.eof()) { 59 | this->totalSupply_->set(uint256(0)); 60 | } else { 61 | uint256 total_supply = input.read(); 62 | this->totalSupply_->set(total_supply); 63 | 64 | // mint inital supply to owner 65 | balances_->set(get_msg_sender(), total_supply); 66 | emitTransfer(Address::zero(), get_msg_sender(), total_supply); 67 | } 68 | return Ok(); 69 | } 70 | 71 | CResult totalSupply() override { 72 | return Ok(this->totalSupply_->get()); 73 | } 74 | 75 | CResult decimals() override { 76 | return Ok(this->decimals_->get()); 77 | } 78 | 79 | CResult name() override { 80 | return Ok(this->name_->get()); 81 | } 82 | 83 | CResult symbol() override { 84 | return Ok(this->symbol_->get()); 85 | } 86 | 87 | CResult balanceOf(const Address &owner) override { 88 | uint256 bal = this->balances_->get(owner); 89 | return Ok(bal); 90 | } 91 | 92 | CResult mint(const Address& owner, const uint256& amount) override { 93 | uint256 total_supply = this->totalSupply_->get(); 94 | this->totalSupply_->set(total_supply + amount); 95 | uint256 owner_bal = this->balances_->get(owner); 96 | this->balances_->set(owner, owner_bal + amount); 97 | emitTransfer(Address::zero(), owner, amount); 98 | return Ok(); 99 | } 100 | 101 | CResult transfer(const Address &to, const uint256& amount) override { 102 | const auto& sender = get_msg_sender(); 103 | uint256 sender_bal = this->balances_->get(sender); 104 | if (sender_bal < amount) { 105 | return Revert("balance not enough"); 106 | } 107 | this->balances_->set(sender, sender_bal - amount); 108 | uint256 to_bal = this->balances_->get(to); 109 | this->balances_->set(to, to_bal + amount); 110 | emitTransfer(sender, to, amount); 111 | return Ok(); 112 | } 113 | 114 | CResult approve(const Address &spender, const uint256& amount) override { 115 | const auto& owner = get_msg_sender(); 116 | uint256 allowance = this->allowance_->get(owner, spender); 117 | this->allowance_->set(owner, spender, allowance + amount); 118 | emitApproval(owner, spender, amount); 119 | return Ok(); 120 | } 121 | 122 | CResult transferFrom(const Address &from, const Address &to, const uint256& amount) override { 123 | const auto& spender = get_msg_sender(); 124 | uint256 allowance = this->allowance_->get(from, spender); 125 | if (allowance < amount) { 126 | return Revert("allowance not enough"); 127 | } 128 | this->allowance_->set(from, spender, allowance - amount); 129 | 130 | uint256 from_bal = this->balances_->get(from); 131 | if (from_bal < amount) { 132 | return Revert("balance not enough"); 133 | } 134 | this->balances_->set(from, from_bal - amount); 135 | uint256 to_bal = this->balances_->get(to); 136 | this->balances_->set(to, to_bal + amount); 137 | emitTransfer(from, to, amount); 138 | return Ok(); 139 | } 140 | 141 | CResult allowance(const Address &owner, const Address &spender) override { 142 | return Ok(this->allowance_->get(owner, spender)); 143 | } 144 | }; 145 | 146 | // Generate call and deploy dispatch functions 147 | ENTRYPOINT(MyErc20Impl) 148 | -------------------------------------------------------------------------------- /examples/erc20/test_in_mock_cli.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | rm -f test.db 5 | 6 | echo 'test deploy contract' 7 | # deploy contract (arg total supply(uint256)) 8 | /opt/chain_mockcli -f my_erc20.wasm --action deploy -i 0x68656c6c6f000000000000000000000000000000000000000000000000000000 9 | # Provide optional total supply here 10 | # /opt/chain_mockcli -f my_erc20.wasm --action deploy -i 0x 11 | 12 | echo 'test mint' 13 | # mint(owner,amount) 14 | /opt/chain_mockcli -f my_erc20.wasm --action call -i 0x40c10f1900000000000000000000000000112233445566778899001122334455667788990000000000000000000000000000000000000000000000000000000000000007 15 | 16 | echo 'test balanceOf after mint' 17 | # balanceOf(address) after mint 18 | /opt/chain_mockcli -f my_erc20.wasm --action call -i 0x70a082310000000000000000000000000011223344556677889900112233445566778899 | grep 'evm finish with result hex: 68656c6c6f000000000000000000000000000000000000000000000000000007' 19 | 20 | echo 'test transfer from owner to user2' 21 | # transfer from owner to user2 22 | /opt/chain_mockcli -f my_erc20.wasm --action call -i 0xa9059cbb0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000005 | grep 'evm finish with result hex:' 23 | 24 | echo 'test query balanceOf after transfer' 25 | # balanceOf(address) 26 | /opt/chain_mockcli -f my_erc20.wasm --action call -i 0x70a082310000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4 | grep 'evm finish with result hex: 0000000000000000000000000000000000000000000000000000000000000005' 27 | 28 | # test approve, allowance, transferFrom 29 | echo 'test approve, allowance, transferFrom' 30 | 31 | # approve to user2 32 | /opt/chain_mockcli -f my_erc20.wasm --action call -i 0x095ea7b30000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000001 | grep 'evm finish with result hex:' 33 | 34 | # query allowance to user2 (sender is 0x0011223344556677889900112233445566778899) 35 | /opt/chain_mockcli -f my_erc20.wasm --action call -i 0xdd62ed3e00000000000000000000000000112233445566778899001122334455667788990000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4 | grep 'evm finish with result hex: 0000000000000000000000000000000000000000000000000000000000000001' 36 | 37 | # transferFrom from 0x0011223344556677889900112233445566778899 to user3 (send by user2) 38 | /opt/chain_mockcli -f my_erc20.wasm --action call --sender-address-hex 0x5b38da6a701c568545dcfcb03fcb875f56beddc4 -i 0x23b872dd00000000000000000000000000112233445566778899001122334455667788990000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc50000000000000000000000000000000000000000000000000000000000000001 | grep 'evm finish with result hex:' 39 | 40 | # query balanceOf user3 41 | /opt/chain_mockcli -f my_erc20.wasm --action call -i 0x70a082310000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc5 | grep 'evm finish with result hex: 0000000000000000000000000000000000000000000000000000000000000001' 42 | 43 | echo 'all tests success' 44 | -------------------------------------------------------------------------------- /examples/example1/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /cmake-build-debug 3 | /cmake-build-release 4 | /logs 5 | /build 6 | *.log 7 | /solc-static-linux 8 | /solc-windows 9 | /solc-macos 10 | /generated 11 | /*.wasm 12 | /*.wat 13 | /*.cbin 14 | /*.cbin.hex 15 | /*.o 16 | /test.db 17 | /sol.output 18 | -------------------------------------------------------------------------------- /examples/example1/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | project(DTVM_CppSDK_demo) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | include_directories(../..) 7 | 8 | add_executable(DTVM_CppSDK_demo 9 | ../../contractlib/v1/contractlib.cpp 10 | my_token.cpp) 11 | -------------------------------------------------------------------------------- /examples/example1/build_wasm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CUR_DIR=`pwd` 4 | cd ../.. 5 | bash build_lib.sh 6 | cd $CUR_DIR 7 | 8 | /opt/solidcpp build --input my_token.cpp --contractlib-dir=../.. --generated-dir ./generated --output my_token.wasm 9 | wasm2wat -o my_token.wat my_token.wasm 10 | -------------------------------------------------------------------------------- /examples/example1/build_wasm_steps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | CUR_DIR=`pwd` 5 | cd ../.. 6 | bash build_lib.sh 7 | cd $CUR_DIR 8 | 9 | /opt/solidcpp fetch-cpp-sol --input my_token.cpp --output ./generated 10 | 11 | # use solc to generate meta and storage, and fetch json from output 12 | /opt/solc --output-dir ./generated --metadata --storage-layout --overwrite ./generated/my_token.sol 13 | 14 | # generate decl.hpp by solidcpp 15 | /opt/solidcpp generate-hpp --input "generated/*_meta.json" --input "generated/*_storage.json" \ 16 | --output generated/my_token_decl.hpp 17 | 18 | # -s INITIAL_HEAP=1048576 to initial heap 1MB 19 | # you can add -D NDEBUG to disable debug print 20 | # must use emscripten at leat 3.1.69(tested) 21 | em++ -std=c++17 -o my_token.wasm -O3 my_token.cpp ../../contractlib/v1/contractlib.o -I ../.. -s 'EXPORTED_FUNCTIONS=["_call","_deploy"]' --no-entry -Wl,--allow-undefined -s ERROR_ON_UNDEFINED_SYMBOLS=0 \ 22 | -s WASM=1 -s STANDALONE_WASM=0 -s PURE_WASI=0 -s INITIAL_MEMORY=1048576 -s TOTAL_STACK=307200 23 | wasm2wat -o my_token.wat my_token.wasm 24 | -------------------------------------------------------------------------------- /examples/example1/generate_meta.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # output of metadata can be used by solidcpp fetch-sol-json 3 | /opt/solc --metadata --storage-layout ./generated/my_token.sol 4 | 5 | -------------------------------------------------------------------------------- /examples/example1/manual_generated/.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | -------------------------------------------------------------------------------- /examples/example1/manual_generated/my_token_decl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "contractlib/contractlib.hpp" 3 | 4 | class ITokenService : public dtvm::Contract { 5 | public: 6 | virtual dtvm::CResult balanceOf(const dtvm::Address &owner) = 0; 7 | virtual dtvm::CResult balanceOf(dtvm::CallInfoPtr call_info, const dtvm::Address &owner) { 8 | return balanceOf(owner); 9 | } 10 | 11 | static std::shared_ptr from(const dtvm::Address& addr); 12 | 13 | protected: 14 | inline dtvm::CResult interface_balanceOf(dtvm::CallInfoPtr call_info, dtvm::Input &input) { 15 | dtvm::require(dtvm::get_msg_value() == 0, "not payable method"); 16 | dtvm::Address arg0 = input.read_address(); 17 | return balanceOf(call_info, arg0); 18 | } 19 | }; 20 | 21 | // generate a proxy subclass for each solidity interface 22 | class ITokenServiceProxy : public ITokenService { 23 | public: 24 | inline ITokenServiceProxy(const dtvm::Address& addr) { 25 | addr_ = addr; 26 | } 27 | inline dtvm::CResult balanceOf(dtvm::CallInfoPtr call_info, const dtvm::Address& owner) override { 28 | std::vector encoded_input = { 112, 160, 130, 49 }; // 1889567281, function selector bytes, balanceOf(address) 29 | std::vector encoded_args = dtvm::abi_encode(std::make_tuple(owner)); 30 | encoded_input.insert(encoded_input.end(), encoded_args.begin(), encoded_args.end()); 31 | return dtvm::call(addr_, encoded_input, call_info->value, call_info->gas); 32 | } 33 | virtual dtvm::CResult balanceOf(const dtvm::Address &owner) { 34 | return balanceOf(dtvm::default_call_info(), owner); 35 | } 36 | protected: 37 | dtvm::CResult constructor(dtvm::CallInfoPtr call_info, dtvm::Input &input) override { 38 | // no need to execute contract constructor since it's just a proxy 39 | // do nothing 40 | return dtvm::Revert("unreachable"); 41 | } 42 | private: 43 | dtvm::Address addr_; 44 | }; 45 | 46 | std::shared_ptr ITokenService::from(const dtvm::Address& addr) { 47 | return std::make_shared(addr); 48 | } 49 | 50 | // Since the solidity meta json does not contain inheritance relationships, we cannot directly inherit from interfaces like ITokenService. 51 | // Instead, we implement all interface function signatures based on the Meta json. 52 | class MyToken : public dtvm::Contract { 53 | protected: 54 | dtvm::StorageSlot balances_slot; 55 | std::shared_ptr> balances_; 56 | 57 | dtvm::StorageSlot token_symbol_slot; 58 | std::shared_ptr> token_symbol_; 59 | public: 60 | inline MyToken() { 61 | balances_slot = dtvm::StorageSlot(1, 0); 62 | balances_ = std::make_shared>(balances_slot); 63 | 64 | token_symbol_slot = dtvm::StorageSlot(2, 0); 65 | token_symbol_ = std::make_shared>(token_symbol_slot); 66 | } 67 | 68 | // Use public virtual for ordinary non-external interfaces, so they can be called by other functions in derived classes. 69 | public: 70 | virtual dtvm::CResult callOtherBalance(const dtvm::Address& token, const dtvm::Address& owner) = 0; 71 | virtual dtvm::CResult callOtherBalance(dtvm::CallInfoPtr call_info, const dtvm::Address& token, const dtvm::Address& owner) { 72 | return callOtherBalance(token, owner); 73 | } 74 | virtual dtvm::CResult balanceOf(const dtvm::Address &owner) = 0; 75 | virtual dtvm::CResult balanceOf(dtvm::CallInfoPtr call_info, const dtvm::Address &owner) { 76 | return balanceOf(owner); 77 | } 78 | 79 | protected: 80 | inline void emitTransfer(dtvm::Address from, dtvm::Address to, dtvm::uint256 amount) { 81 | const std::vector topic1 = { 221, 242, 82, 173 }; // 3723645613, Transfer(address,address,uint256) 82 | const std::vector data = dtvm::abi_encode(std::make_tuple(from, to, amount)); 83 | dtvm::hostio::emit_log({topic1}, data); 84 | } 85 | 86 | // interface_XXX needs to be protected so that subcontracts can call it as entrypoint 87 | protected: 88 | // inline dtvm::CResult interface_constructor(dtvm::CallInfoPtr call_info, dtvm::Input &input) { 89 | // // payable, support transfer, no need to check transfer amount 90 | // std::string arg0 = input.read_string(); 91 | // return constructor(call_info, arg0); 92 | // } 93 | 94 | inline dtvm::CResult interface_callOtherBalance(dtvm::CallInfoPtr call_info, dtvm::Input &input) { 95 | dtvm::require(dtvm::get_msg_value() == 0, "not payable method"); 96 | dtvm::Address arg0 = input.read_address(); 97 | dtvm::Address arg1 = input.read_address(); 98 | return callOtherBalance(call_info, arg0, arg1); 99 | } 100 | 101 | inline dtvm::CResult interface_balanceOf(dtvm::CallInfoPtr call_info, dtvm::Input &input) { 102 | dtvm::require(dtvm::get_msg_value() == 0, "not payable method"); 103 | dtvm::Address arg0 = input.read_address(); 104 | return balanceOf(call_info, arg0); 105 | } 106 | 107 | public: 108 | dtvm::CResult dispatch(dtvm::CallInfoPtr call_info, dtvm::Input &input_with_selector) { 109 | const uint32_t selector = input_with_selector.read_selector(); 110 | // ignore first 4 bytes selector, read the rest as contract abi input 111 | dtvm::Input input(input_with_selector.data() + 4, input_with_selector.size() - 4); 112 | switch (selector) { 113 | 114 | case 2326892680: { // selector of callOtherBalance 115 | return interface_callOtherBalance(call_info, input); 116 | } 117 | break; 118 | 119 | case 1889567281: { // selector of balanceOf 120 | return interface_balanceOf(call_info, input); 121 | } 122 | break; 123 | 124 | default: { 125 | fallback(); 126 | return dtvm::Ok(); 127 | } 128 | } 129 | } 130 | 131 | dtvm::CResult dispatch_constructor(dtvm::CallInfoPtr call_info, dtvm::Input &input) { 132 | return constructor(call_info, input); 133 | } 134 | }; 135 | -------------------------------------------------------------------------------- /examples/example1/my_token.cpp: -------------------------------------------------------------------------------- 1 | #include "contractlib/v1/contractlib.hpp" 2 | 3 | // This macro is a declaration that doesn't do anything by itself. 4 | // The solc compiler and the solidcpp tool in our repository convert it to json and .h header files 5 | SOLIDITY(my_token, R"""( 6 | 7 | pragma solidity ^0.8.0; 8 | 9 | interface ITokenService { 10 | function balanceOf(address owner) external returns(uint256); 11 | function transfer(address to, uint256 amount) external; 12 | } 13 | 14 | abstract contract MyToken is ITokenService { 15 | mapping(address => uint256) private balances; 16 | string private token_symbol; 17 | bool private bool_value; 18 | uint8 private uint8_value; 19 | int16 private int16_value; 20 | uint256 private uint256_value; 21 | bytes private bytes_value; 22 | bytes32 private bytes32_value; 23 | uint32[] private uint32_array; 24 | uint128 private uint128_value; 25 | int128 private int128_value; 26 | 27 | function callOtherBalance(address token, address owner) public virtual returns (uint256); 28 | 29 | function testGetValue(uint256 value) public virtual payable; 30 | 31 | event Transfer(address indexed from, address to, uint256 amount); 32 | } 33 | 34 | )""") 35 | 36 | // #include "manual_generated/my_token_decl.hpp" 37 | #include "generated/my_token_decl.hpp" 38 | 39 | using namespace dtvm; 40 | 41 | class MyTokenImpl : public MyToken { 42 | 43 | protected: 44 | 45 | CResult constructor(dtvm::CallInfoPtr call_info, Input &input) override { 46 | std::string symbol = input.read(); 47 | this->token_symbol_->set(symbol); 48 | balances_->set(get_msg_sender(), uint256(10000000)); 49 | 50 | this->uint128_value_->set(123); 51 | this->bytes_value_->set(Bytes ({ 1, 2, 3, 4 })); 52 | return Ok(); 53 | } 54 | 55 | CResult test_read_bytes() { 56 | return Ok(this->bytes_value_->get()); 57 | } 58 | 59 | CResult test_read_u128() { 60 | return Ok(this->uint128_value_->get()); 61 | } 62 | 63 | CResult callOtherBalance(const Address& token, const Address& owner) override { 64 | // dtvm::call method can pass gas, value, encoded input, addr etc 65 | auto token_contract = ITokenService::from(token); 66 | return token_contract->balanceOf(owner); 67 | } 68 | 69 | CResult testGetValue(const uint256& value) override { 70 | const auto& msg_value = get_msg_value(); 71 | if (msg_value != value) { 72 | return Revert("value not equal"); 73 | } 74 | return Ok(); 75 | } 76 | 77 | CResult balanceOf(const Address &owner) override { 78 | uint256 bal = this->balances_->get(owner); 79 | // emit event demo 80 | emitTransfer(owner, owner, bal); 81 | return Ok(bal); 82 | } 83 | 84 | CResult transfer(const Address &to, const uint256& amount) override { 85 | const auto& sender = get_msg_sender(); 86 | uint256 sender_bal = this->balances_->get(sender); 87 | if (sender_bal < amount) { 88 | return Revert("balance not enough"); 89 | } 90 | this->balances_->set(sender, sender_bal - amount); 91 | uint256 to_bal = this->balances_->get(to); 92 | this->balances_->set(to, to_bal + amount); 93 | emitTransfer(sender, to, amount); 94 | return Ok(); 95 | } 96 | 97 | public: 98 | void receive() override { 99 | // override when receive native token directly(no calldata) 100 | emitTransfer(Address::zero(), get_msg_sender(), get_msg_value()); 101 | } 102 | }; 103 | 104 | // generate call and deploy dispatch functions 105 | ENTRYPOINT(MyTokenImpl) 106 | -------------------------------------------------------------------------------- /examples/example1/test_in_mock_cli.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | rm -f test.db 4 | 5 | echo 'testing deploy contract with argument' 6 | # deploy contract (arg 'hello') 7 | /opt/chain_mockcli -f my_token.wasm --action deploy -i 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000568656c6c6f000000000000000000000000000000000000000000000000000000 8 | 9 | echo 'testing transfer from owner to user2' 10 | # transfer from owner to user2 11 | /opt/chain_mockcli -f my_token.wasm --action call -i 0xa9059cbb0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000001 | grep 'evm finish with result hex: ' 12 | 13 | echo 'testing balanceOf' 14 | # balanceOf(address) 15 | /opt/chain_mockcli -f my_token.wasm --action call -i 0x70a082310000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4 | grep 'evm finish with result hex: 0000000000000000000000000000000000000000000000000000000000000001' 16 | 17 | echo 'testing msg.value' 18 | # test msg.value testGetValue(uint256) 0xb9aa1f48 with valid value 7 19 | /opt/chain_mockcli -f my_token.wasm --action call -v 7 -i 0xb9aa1f480000000000000000000000000000000000000000000000000000000000000007 | grep 'evm finish with result hex: ' 20 | 21 | # test msg.value testGetValue(uint256) 0xb9aa1f48 with invalid value 22 | /opt/chain_mockcli -f my_token.wasm --action call -v 7 -i 0xb9aa1f480000000000000000000000000000000000000000000000000000000000000008 | grep 'evm revert with result hex: 000000000000000000000000000000000000000000000000000000000000000f76616c7565206e6f7420657175616c0000000000000000000000000000000000' 23 | 24 | echo 'tests success' 25 | -------------------------------------------------------------------------------- /examples/fibonacci/.gitignore: -------------------------------------------------------------------------------- 1 | /*.wasm 2 | /*.o 3 | /*.wat 4 | /test.db 5 | /*.cbin 6 | /*.cbin.hex 7 | -------------------------------------------------------------------------------- /examples/fibonacci/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | export LLVM_DIR=/opt/llvm15/lib/cmake/llvm 5 | export LLVM_SYS_150_PREFIX=/opt/llvm15 6 | export PATH=/opt/llvm15/bin:$PATH 7 | # cdt is clang with wasm-sysroot 8 | clang --target=wasm32 -nostdlib -O2 -nostdlib -c fib.c -I /opt/cdt/wasm-sysroot/include 9 | wasm-ld fib.o -o fib.wasm --no-entry --strip-all --allow-undefined --export "deploy" --export "call" /opt/cdt/wasm-sysroot/lib/libclang_rt.builtins.a 10 | 11 | wasm2wat -o fib.wat fib.wasm 12 | 13 | # build recur version 14 | clang --target=wasm32 -nostdlib -O2 -nostdlib -c fib_recur.c -I /opt/cdt/wasm-sysroot/include 15 | wasm-ld fib_recur.o -o fib_recur.c.wasm --no-entry --strip-all --allow-undefined --export "call" --export "deploy" /opt/cdt/wasm-sysroot/lib/libclang_rt.builtins.a 16 | 17 | wasm2wat -o fib_recur.c.wat fib_recur.c.wasm 18 | 19 | # build counter.c 20 | clang --target=wasm32 -nostdlib -O2 -nostdlib -c counter.c -I /opt/cdt/wasm-sysroot/include 21 | wasm-ld counter.o -o counter.c.wasm --no-entry --strip-all --allow-undefined --export "call" --export "deploy" /opt/cdt/wasm-sysroot/lib/libclang_rt.builtins.a 22 | 23 | wasm2wat -o counter.c.wat counter.c.wasm 24 | -------------------------------------------------------------------------------- /examples/fibonacci/build_fib_no_contract.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | export LLVM_DIR=/opt/llvm15/lib/cmake/llvm 5 | export LLVM_SYS_150_PREFIX=/opt/llvm15 6 | export PATH=/opt/llvm15/bin:$PATH 7 | # cdt is clang with wasm-sysroot 8 | clang --target=wasm32 -nostdlib -O2 -nostdlib -c fib_no_contract.c -I /opt/cdt/wasm-sysroot/include 9 | wasm-ld fib_no_contract.o -o fib_no_contract.c.wasm --no-entry --strip-all --allow-undefined --export "fibonacci" --export "fibonacciTailOptimized" /opt/cdt/wasm-sysroot/lib/libclang_rt.builtins.a 10 | 11 | wasm2wat -o fib_no_contract.c.wat fib_no_contract.c.wasm 12 | -------------------------------------------------------------------------------- /examples/fibonacci/counter.c: -------------------------------------------------------------------------------- 1 | #include "hostapi.h" 2 | 3 | static uint8_t selector[4]; 4 | 5 | static void uint32_to_big_endian_bytes(uint32_t value, uint8_t* bytes) { 6 | int64_t *p = (int64_t *)bytes; 7 | p[0] = 0; 8 | p[1] = 0; 9 | p[2] = 0; 10 | int32_t *p2 = (int32_t *) (bytes + 24); 11 | p2[0] = 0; 12 | bytes[28+0] = (value >> 24) & 0xFF; 13 | bytes[28+1] = (value >> 16) & 0xFF; 14 | bytes[28+2] = (value >> 8) & 0xFF; 15 | bytes[28+3] = value & 0xFF; 16 | } 17 | 18 | static uint32_t uint32_from_big_endian_bytes(uint8_t* bytes) { 19 | uint32_t value = 0; 20 | value |= ((uint32_t)bytes[28+0] << 24); 21 | value |= ((uint32_t)bytes[28+1] << 16); 22 | value |= ((uint32_t)bytes[28+2] << 8); 23 | value |= ((uint32_t)bytes[28+3]); 24 | return value; 25 | } 26 | 27 | static uint8_t n_bytes32[32] = {0}; 28 | static uint8_t tmp_result[32] = {0}; 29 | 30 | static uint8_t counter_slot[32] = {0}; 31 | 32 | void call() { 33 | callDataCopy((int32_t) &selector, 0, 4); 34 | if (selector[0] == 0xe8) { // increase: 0xe8927fbc 35 | 36 | storageLoad((int32_t) &counter_slot, (int32_t) &n_bytes32); 37 | uint32_t n = uint32_from_big_endian_bytes(&n_bytes32); 38 | n++; 39 | uint32_to_big_endian_bytes(n, tmp_result); 40 | storageStore((int32_t) &counter_slot, (int32_t) &tmp_result); 41 | finish(0, 0); 42 | } else if (selector[0] == 0x06) { // count: 0x06661abd 43 | storageLoad((int32_t) &counter_slot, (int32_t) &n_bytes32); 44 | finish((int32_t) &n_bytes32, 32); 45 | } else if (selector[0] == 0xd7) { // decrease: 0xd732d955 46 | storageLoad((int32_t) &counter_slot, (int32_t) &n_bytes32); 47 | uint32_t n = uint32_from_big_endian_bytes(&n_bytes32); 48 | n--; 49 | uint32_to_big_endian_bytes(n, tmp_result); 50 | storageStore((int32_t) &counter_slot, (int32_t) &tmp_result); 51 | finish(0, 0); 52 | } 53 | } 54 | 55 | void deploy() { 56 | } -------------------------------------------------------------------------------- /examples/fibonacci/fib.c: -------------------------------------------------------------------------------- 1 | #include "hostapi.h" 2 | 3 | // Standard Fibonacci implementation 4 | unsigned int fibonacci(unsigned int n) { 5 | if (n == 0) return 0; 6 | if (n == 1) return 1; 7 | 8 | unsigned int a = 0; 9 | unsigned int b = 1; 10 | unsigned int result; 11 | 12 | for (unsigned int i = 2; i <= n; i++) { 13 | result = a + b; 14 | a = b; 15 | b = result; 16 | } 17 | 18 | return result; 19 | } 20 | 21 | // Tail-optimized Fibonacci implementation 22 | unsigned int fibonacciTailOptimized(unsigned int n) { 23 | if (n == 0) return 0; 24 | 25 | unsigned int a = 0; 26 | unsigned int b = 1; 27 | 28 | while (n > 1) { 29 | unsigned int temp = b; 30 | b = a + b; 31 | a = temp; 32 | n--; 33 | } 34 | 35 | return b; 36 | } 37 | 38 | static uint8_t selector[4]; 39 | 40 | static void uint32_to_big_endian_bytes(uint32_t value, uint8_t* bytes) { 41 | // clear first 28bytes by 3*i64 +i32 pointer 42 | int64_t *p = (int64_t *)bytes; 43 | p[0] = 0; 44 | p[1] = 0; 45 | p[2] = 0; 46 | int32_t *p2 = (int32_t *) (bytes + 24); 47 | p2[0] = 0; 48 | // write last 4 bytes 49 | bytes[28+0] = (value >> 24) & 0xFF; 50 | bytes[28+1] = (value >> 16) & 0xFF; 51 | bytes[28+2] = (value >> 8) & 0xFF; 52 | bytes[28+3] = value & 0xFF; 53 | } 54 | 55 | static uint8_t n_bytes32[32] = {0}; 56 | static uint8_t tmp_result[32] = {0}; 57 | 58 | void call() { 59 | // Read 4 bytes selector from calldata 60 | callDataCopy((int32_t) &selector, 0, 4); 61 | if (selector[0] == 0x61) { 62 | // fibonacci(uint256) == 0x61047ff4 63 | callDataCopy((int32_t) &n_bytes32, 4, 32); 64 | uint32_t n = (uint32_t) n_bytes32[31]; 65 | uint32_t result = fibonacci(n); 66 | uint32_to_big_endian_bytes(result, tmp_result); 67 | finish((int32_t) &tmp_result, 32); 68 | } else if (selector[0] == 0xac) { 69 | // fibonacciTailOptimized(uint256) == 0xac8da0ab 70 | callDataCopy((int32_t) &n_bytes32, 4, 32); 71 | uint32_t n = (uint32_t) n_bytes32[31]; 72 | uint32_t result = fibonacciTailOptimized(n); 73 | uint32_to_big_endian_bytes(result, tmp_result); 74 | finish((int32_t) &tmp_result, 32); 75 | } 76 | } 77 | 78 | void deploy() { 79 | } 80 | -------------------------------------------------------------------------------- /examples/fibonacci/fib_no_contract.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Standard Fibonacci implementation 4 | unsigned int fibonacci(unsigned int n) { 5 | if (n == 0) return 0; 6 | if (n == 1) return 1; 7 | 8 | unsigned int a = 0; 9 | unsigned int b = 1; 10 | unsigned int result; 11 | 12 | for (unsigned int i = 2; i <= n; i++) { 13 | result = a + b; 14 | a = b; 15 | b = result; 16 | } 17 | 18 | return result; 19 | } 20 | 21 | // Tail-optimized Fibonacci implementation 22 | unsigned int fibonacciTailOptimized(unsigned int n) { 23 | if (n == 0) return 0; 24 | 25 | unsigned int a = 0; 26 | unsigned int b = 1; 27 | 28 | while (n > 1) { 29 | unsigned int temp = b; 30 | b = a + b; 31 | a = temp; 32 | n--; 33 | } 34 | 35 | return b; 36 | } 37 | -------------------------------------------------------------------------------- /examples/fibonacci/fib_recur.c: -------------------------------------------------------------------------------- 1 | #include "hostapi.h" 2 | 3 | // Recursive Fibonacci implementation 4 | unsigned int fibonacci(unsigned int n) { 5 | if (n == 0) return 0; 6 | if (n == 1) return 1; 7 | return fibonacci(n - 1) + fibonacci(n - 2); 8 | } 9 | 10 | // Tail-recursive Fibonacci helper function 11 | unsigned int fibonacciTailRecursive(unsigned int n, unsigned int a, unsigned int b) { 12 | if (n == 0) return a; 13 | if (n == 1) return b; 14 | return fibonacciTailRecursive(n - 1, b, a + b); 15 | } 16 | 17 | // Tail-optimized Fibonacci implementation using recursion 18 | unsigned int fibonacciTailOptimized(unsigned int n) { 19 | return fibonacciTailRecursive(n, 0, 1); 20 | } 21 | 22 | static uint8_t selector[4]; 23 | 24 | static void uint32_to_big_endian_bytes(uint32_t value, uint8_t* bytes) { 25 | int64_t *p = (int64_t *)bytes; 26 | p[0] = 0; 27 | p[1] = 0; 28 | p[2] = 0; 29 | int32_t *p2 = (int32_t *) (bytes + 24); 30 | p2[0] = 0; 31 | bytes[28+0] = (value >> 24) & 0xFF; 32 | bytes[28+1] = (value >> 16) & 0xFF; 33 | bytes[28+2] = (value >> 8) & 0xFF; 34 | bytes[28+3] = value & 0xFF; 35 | } 36 | 37 | static uint8_t n_bytes32[32] = {0}; 38 | static uint8_t tmp_result[32] = {0}; 39 | 40 | void call() { 41 | callDataCopy((int32_t) &selector, 0, 4); 42 | if (selector[0] == 0x61) { 43 | callDataCopy((int32_t) &n_bytes32, 4, 32); 44 | uint32_t n = (uint32_t) n_bytes32[31]; 45 | uint32_t result = fibonacci(n); 46 | uint32_to_big_endian_bytes(result, tmp_result); 47 | finish((int32_t) &tmp_result, 32); 48 | } else if (selector[0] == 0xac) { 49 | callDataCopy((int32_t) &n_bytes32, 4, 32); 50 | uint32_t n = (uint32_t) n_bytes32[31]; 51 | uint32_t result = fibonacciTailOptimized(n); 52 | uint32_to_big_endian_bytes(result, tmp_result); 53 | finish((int32_t) &tmp_result, 32); 54 | } 55 | } 56 | 57 | void deploy() { 58 | } -------------------------------------------------------------------------------- /examples/fibonacci/hostapi.h: -------------------------------------------------------------------------------- 1 | #ifndef __HOSTAPI_H__ 2 | #define __HOSTAPI_H__ 3 | 4 | #include 5 | 6 | // Use uint32_t or uint64_t for pointer integers based on the environment, making it easier to test in 64-bit environments 7 | 8 | #if INTPTR_MAX == INT64_MAX 9 | #define ADDRESS_UINT uint64_t 10 | #else 11 | #define ADDRESS_UINT uint32_t 12 | #define IN_WASM_ENV 13 | #endif 14 | 15 | __attribute__((import_module("env"), import_name("getAddress"))) 16 | void getAddress(ADDRESS_UINT result_offset); 17 | 18 | __attribute__((import_module("env"), import_name("getCaller"))) 19 | void getCaller(ADDRESS_UINT result_offset); 20 | 21 | __attribute__((import_module("env"), import_name("getCallValue"))) 22 | void getCallValue(ADDRESS_UINT result_offset); 23 | 24 | __attribute__((import_module("env"), import_name("getCallDataSize"))) int32_t 25 | getCallDataSize(); 26 | 27 | __attribute__((import_module("env"), import_name("callDataCopy"))) void 28 | callDataCopy(ADDRESS_UINT target, ADDRESS_UINT offset, int32_t len); 29 | 30 | __attribute__((import_module("env"), import_name("getBlockHash"))) int32_t 31 | getBlockHash(int64_t number, ADDRESS_UINT result_offset); 32 | 33 | __attribute__((import_module("env"), import_name("getBlockCoinbase"))) 34 | void getBlockCoinbase(ADDRESS_UINT result_offset); 35 | 36 | __attribute__((import_module("env"), import_name("getBlockPrevRandao"))) void getBlockPrevRandao(ADDRESS_UINT result_offset); 37 | 38 | __attribute__((import_module("env"), import_name("getBlockGasLimit"))) int64_t getBlockGasLimit(); 39 | 40 | __attribute__((import_module("env"), import_name("getBlockTimestamp"))) int64_t getBlockTimestamp(); 41 | 42 | __attribute__((import_module("env"), import_name("getGasLeft"))) int64_t getGasLeft(); 43 | 44 | __attribute__((import_module("env"), import_name("getBlockNumber"))) int64_t getBlockNumber(); 45 | 46 | __attribute__((import_module("env"), import_name("getTxGasPrice"))) void getTxGasPrice(ADDRESS_UINT value_offset); 47 | 48 | __attribute__((import_module("env"), import_name("getTxOrigin"))) void getTxOrigin(ADDRESS_UINT result_offset); 49 | 50 | __attribute__((import_module("env"), import_name("getBaseFee"))) void getBaseFee(ADDRESS_UINT result_offset); 51 | 52 | __attribute__((import_module("env"), import_name("getBlobBaseFee"))) void getBlobBaseFee(ADDRESS_UINT result_offset); 53 | 54 | __attribute__((import_module("env"), import_name("getChainId"))) void getChainId(ADDRESS_UINT result_offset); 55 | 56 | 57 | __attribute__((import_module("env"), import_name("getExternalBalance"))) 58 | void getExternalBalance(ADDRESS_UINT address_offset, ADDRESS_UINT result_offset); 59 | 60 | __attribute__((import_module("env"), import_name("getExternalCodeHash"))) 61 | void getExternalCodeHash(ADDRESS_UINT address_offset, ADDRESS_UINT result_offset); 62 | 63 | __attribute__((import_module("env"), import_name("storageLoad"))) void storageLoad( 64 | ADDRESS_UINT key_offset, 65 | ADDRESS_UINT result_offset); 66 | 67 | __attribute__((import_module("env"), import_name("storageStore"))) void storageStore( 68 | ADDRESS_UINT key_offset, 69 | ADDRESS_UINT value_offset); 70 | 71 | __attribute__((import_module("env"), import_name("transientStore"))) 72 | void transientStore(ADDRESS_UINT key_offset, ADDRESS_UINT value_offset); 73 | 74 | __attribute__((import_module("env"), import_name("transientLoad"))) 75 | void transientLoad(ADDRESS_UINT key_offset, ADDRESS_UINT result_offset); 76 | 77 | __attribute__((import_module("env"), import_name("codeCopy"))) 78 | void codeCopy(ADDRESS_UINT result_offset, int32_t code_offset, int32_t length); 79 | 80 | __attribute__((import_module("env"), import_name("getCodeSize"))) 81 | int32_t getCodeSize(); 82 | 83 | __attribute__((import_module("env"), import_name("externalCodeCopy"))) 84 | void externalCodeCopy(ADDRESS_UINT address_offset, ADDRESS_UINT result_offset, int32_t code_offset, int32_t length); 85 | 86 | __attribute__((import_module("env"), import_name("getExternalCodeSize"))) 87 | int32_t getExternalCodeSize(ADDRESS_UINT address_offset); 88 | 89 | __attribute__((import_module("env"), import_name("callContract"))) 90 | int32_t callContract(int64_t gas, ADDRESS_UINT addressOffset, ADDRESS_UINT valueOffset, ADDRESS_UINT dataOffset, int32_t dataLength); 91 | 92 | __attribute__((import_module("env"), import_name("callCode"))) 93 | int32_t callCode(int64_t gas, ADDRESS_UINT addressOffset, ADDRESS_UINT valueOffset, ADDRESS_UINT dataOffset, int32_t dataLength); 94 | 95 | __attribute__((import_module("env"), import_name("callDelegate"))) 96 | int32_t callDelegate(int64_t gas, ADDRESS_UINT addressOffset, ADDRESS_UINT dataOffset, int32_t dataLength); 97 | 98 | __attribute__((import_module("env"), import_name("callStatic"))) 99 | int32_t callStatic(int64_t gas, ADDRESS_UINT addressOffset, ADDRESS_UINT dataOffset, int32_t dataLength); 100 | 101 | __attribute__((import_module("env"), import_name("createContract"))) 102 | int32_t createContract(ADDRESS_UINT valueOffset, ADDRESS_UINT codeOffset, int32_t codeLength, 103 | ADDRESS_UINT dataOffset, int32_t dataLength, ADDRESS_UINT saltOffset, int32_t is_create2, ADDRESS_UINT resultOffset); 104 | 105 | __attribute__((import_module("env"), import_name("finish"))) void finish(ADDRESS_UINT data_offset, 106 | int32_t length); 107 | 108 | __attribute__((import_module("env"), import_name("revert"))) void revert(ADDRESS_UINT data_offset, 109 | int32_t length); 110 | 111 | __attribute__((import_module("env"), import_name("emitLogEvent"))) void emitLogEvent( 112 | ADDRESS_UINT data_offset, 113 | int32_t length, 114 | int32_t number_of_topics, 115 | ADDRESS_UINT topic1, 116 | ADDRESS_UINT topic2, 117 | ADDRESS_UINT topic3, 118 | ADDRESS_UINT topic4); 119 | 120 | __attribute__((import_module("env"), import_name("getReturnDataSize"))) 121 | int32_t getReturnDataSize(); 122 | 123 | __attribute__((import_module("env"), import_name("returnDataCopy"))) 124 | void returnDataCopy(ADDRESS_UINT resultOffset, int32_t dataOffset, int32_t length); 125 | 126 | __attribute__((import_module("env"), import_name("selfDestruct"))) 127 | void selfDestruct(ADDRESS_UINT addressOffset); 128 | 129 | __attribute__((import_module("env"), import_name("keccak256"))) 130 | void keccak256(ADDRESS_UINT inputOffset, int32_t inputLength, ADDRESS_UINT resultOffset); 131 | 132 | __attribute__((import_module("env"), import_name("sha256"))) 133 | void sha256(ADDRESS_UINT inputOffset, int32_t inputLength, ADDRESS_UINT resultOffset); 134 | 135 | #endif // __HOSTAPI_H__ 136 | -------------------------------------------------------------------------------- /examples/fibonacci/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | rm -f test.db 5 | 6 | wasm_file=$1 7 | 8 | if [ -z "$wasm_file" ] || [ ! -f "$wasm_file" ]; then 9 | echo "Usage: $0 " 10 | exit 1 11 | fi 12 | 13 | 14 | function run_cmd_and_grep() { 15 | # run command, echo result, and grep $grep. if exit, not run continue 16 | local exit_code=$? 17 | local output="$1" 18 | local grep_pattern="$2" 19 | 20 | # Echo the output 21 | echo "$output" 22 | 23 | # Check if the command was successful 24 | if [ $exit_code -ne 0 ]; then 25 | echo "Command failed with exit code $exit_code" 26 | exit $exit_code 27 | fi 28 | 29 | # Check if the output matches the grep pattern 30 | # echo "$output" | grep -E -zo "$grep_pattern" 31 | echo "matching pattern: $grep_pattern" 32 | echo "$output" | awk -v pattern="$grep_pattern" 'BEGIN { RS="\0" } $0 ~ pattern { found=1 } END { if (!found) exit 1 }' 33 | echo "grep pattern matched" 34 | } 35 | 36 | echo 'test deploy contract $wasm_file' 37 | # deploy contract 38 | /opt/chain_mockcli -f $wasm_file --action deploy -s 0x9988776655443322119900112233445566778899 -i 0x 39 | 40 | echo 'test fib(10)' 41 | 42 | output=$(/opt/chain_mockcli -f $wasm_file --action call -i 0x61047ff4000000000000000000000000000000000000000000000000000000000000000a) 43 | run_cmd_and_grep "$output" 'evm finish with result hex: 0000000000000000000000000000000000000000000000000000000000000037' 44 | -------------------------------------------------------------------------------- /solidcpp/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /solidcpp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solidcpp" 3 | version = "0.1.2" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | hashbrown = "0.15.1" 8 | byteorder = "1.5.0" 9 | clap = { version = "4.0.3", features = ["derive"] } 10 | ethereum-types = "0.15.1" 11 | globwalk = "0.9.1" 12 | hex = "0.4.3" 13 | regex = "1.11.0" 14 | serde_json = "1.0" 15 | sha3 = "0.10.8" 16 | -------------------------------------------------------------------------------- /solidcpp/src/commands/fetch_cpp_sol.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// Extract Solidity code strings from C++ contract source code 5 | /// that start with SOLIDITY(contractName, R"""( and end with )""") 6 | use regex::Regex; 7 | 8 | #[derive(Debug)] 9 | pub struct SolidityFromCpp { 10 | pub contract_name: String, 11 | pub solidity_code: String, 12 | } 13 | 14 | pub fn extract_solidity_from_cpp(s: &str) -> Vec { 15 | let re_str = r#"SOLIDITY\(([\w_]+),\s*R"""\(((.|\n)*)\)"""\)"#; 16 | let re = Regex::new(re_str).unwrap(); 17 | re.captures_iter(s) 18 | .map(|cap| SolidityFromCpp { 19 | contract_name: cap.get(1).map_or("", |m| m.as_str()).to_string(), 20 | solidity_code: cap.get(2).map_or("", |m| m.as_str()).to_string(), 21 | }) 22 | .collect() 23 | } 24 | 25 | #[test] 26 | fn test_extract_solidity_from_cpp() { 27 | let s = r#" 28 | SOLIDITY(ContractName, R"""( 29 | pragma solidity ^0.8.0; 30 | 31 | contract ContractName { 32 | // ... 33 | } 34 | )""") 35 | 36 | void main() { 37 | printf("Hello, world!"); 38 | } 39 | "#; 40 | let sols = extract_solidity_from_cpp(s); 41 | println!("extracted: {sols:?}"); 42 | assert_eq!(sols.len(), 1); 43 | assert_eq!(sols[0].contract_name, "ContractName"); 44 | assert!(sols[0].solidity_code.contains("contract ContractName {")) 45 | } 46 | -------------------------------------------------------------------------------- /solidcpp/src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | pub mod fetch_cpp_sol; 5 | pub mod generate_hpp; 6 | pub mod sol_types_utils; 7 | pub mod utils; 8 | -------------------------------------------------------------------------------- /solidcpp/src/commands/sol_types_utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use std::collections::HashMap; 5 | 6 | fn parse_type(solidity_type: &str) -> String { 7 | let storage_base_type_in_cpp_mapping: HashMap = [ 8 | ("t_string_storage".to_string(), "std::string".to_string()), 9 | ("t_bytes32".to_string(), "dtvm::bytes32".to_string()), 10 | ("t_bytes_storage".to_string(), "dtvm::Bytes".to_string()), 11 | ("t_uint128".to_string(), "__uint128_t".to_string()), 12 | ("t_int128".to_string(), "__int128_t".to_string()), 13 | ("t_uint256".to_string(), "dtvm::uint256".to_string()), 14 | ("t_int256".to_string(), "dtvm::int256".to_string()), 15 | ("t_uint64".to_string(), "uint64_t".to_string()), 16 | ("t_int64".to_string(), "int64_t".to_string()), 17 | ("t_uint32".to_string(), "uint32_t".to_string()), 18 | ("t_int32".to_string(), "int32_t".to_string()), 19 | ("t_uint16".to_string(), "uint16_t".to_string()), 20 | ("t_int16".to_string(), "int16_t".to_string()), 21 | ("t_uint8".to_string(), "uint8_t".to_string()), 22 | ("t_int8".to_string(), "int8_t".to_string()), 23 | ("t_bool".to_string(), "bool".to_string()), 24 | ("t_address_storage".to_string(), "dtvm::Address".to_string()), 25 | ("t_address".to_string(), "dtvm::Address".to_string()), 26 | ] 27 | .into_iter() 28 | .collect(); 29 | if let Some(cpp_type) = storage_base_type_in_cpp_mapping.get(solidity_type) { 30 | return cpp_type.to_string(); 31 | } 32 | if let Some((key_type, value_type)) = parse_mapping_type(solidity_type) { 33 | return format!( 34 | "dtvm::StorageMap<{}, {}>", 35 | parse_type(key_type), 36 | parse_type(value_type) 37 | ); 38 | } 39 | if let Some(element_type) = parse_array_type(solidity_type) { 40 | return format!("dtvm::StorageArray<{}>", parse_type(element_type)); 41 | } 42 | panic!("unknown solidity type: {}", solidity_type); 43 | } 44 | 45 | fn parse_mapping_type(solidity_type: &str) -> Option<(&str, &str)> { 46 | if !solidity_type.starts_with("t_mapping(") || !solidity_type.ends_with(")") { 47 | return None; 48 | } 49 | // key type will have no comma, so we can split by the first comma 50 | let first_comma_pos = solidity_type.find(',').unwrap(); 51 | Some(( 52 | &solidity_type[("t_mapping(".len())..first_comma_pos], 53 | &solidity_type[first_comma_pos + 1..(solidity_type.len() - 1)], 54 | )) 55 | } 56 | 57 | fn parse_array_type(solidity_type: &str) -> Option<&str> { 58 | if !solidity_type.starts_with("t_array(") || !solidity_type.ends_with(")dyn_storage") { 59 | return None; 60 | } 61 | Some(&solidity_type[("t_array(".len())..solidity_type.len() - ")dyn_storage".len()]) 62 | } 63 | 64 | pub fn transform_solidity_storage_type_to_cpp(solidity_type: &str) -> String { 65 | parse_type(solidity_type) 66 | } 67 | 68 | #[test] 69 | fn test_nested_mapping_transform() { 70 | let input = "t_mapping(t_address,t_mapping(t_address,t_uint256))"; 71 | let output = transform_solidity_storage_type_to_cpp(input); 72 | assert_eq!( 73 | output, 74 | "dtvm::StorageMap>" 75 | ); 76 | } 77 | 78 | #[test] 79 | fn test_simple_type_transform() { 80 | { 81 | let solidity_type = "t_address"; 82 | let output = transform_solidity_storage_type_to_cpp(solidity_type); 83 | assert_eq!(output, "dtvm::Address"); 84 | } 85 | { 86 | let solidity_type = "t_uint256"; 87 | let output = transform_solidity_storage_type_to_cpp(solidity_type); 88 | assert_eq!(output, "dtvm::uint256"); 89 | } 90 | { 91 | let solidity_type = "t_string_storage"; 92 | let output = transform_solidity_storage_type_to_cpp(solidity_type); 93 | assert_eq!(output, "std::string"); 94 | } 95 | { 96 | let solidity_type = "t_mapping(t_address,t_uint256)"; 97 | let output = transform_solidity_storage_type_to_cpp(solidity_type); 98 | assert_eq!(output, "dtvm::StorageMap"); 99 | } 100 | { 101 | let solidity_type = "t_array(t_uint32)dyn_storage"; 102 | let output = transform_solidity_storage_type_to_cpp(solidity_type); 103 | assert_eq!(output, "dtvm::StorageArray"); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /solidcpp/src/commands/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | pub fn with_prefix_comma_if_not_empty(s: &str) -> String { 5 | if !s.is_empty() { 6 | format!(", {s}") 7 | } else { 8 | s.to_string() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /solidcpp/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024-2025 the DTVM authors. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | pub mod commands; 5 | -------------------------------------------------------------------------------- /solidcpp/test_example1_fetch_sol_from_cpp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cargo run -- fetch-cpp-sol --input ../examples/example1/my_token.cpp --output ../examples/example1/generated 3 | -------------------------------------------------------------------------------- /solidcpp/test_example1_fetch_sol_json.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cargo run -- fetch-sol-json --input ../examples/example1/sol.output --output ../examples/example1/generated 3 | -------------------------------------------------------------------------------- /solidcpp/test_example1_generate_hpp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cargo run -- generate-hpp --input ../examples/example1/generated/ITokenService_meta.json --input ../examples/example1/generated/MyToken_meta.json \ 3 | --input ../examples/example1/generated/ITokenService_storage.json --input ../examples/example1/generated/MyToken_storage.json \ 4 | --output ../examples/example1/generated/my_token_decl.hpp 5 | 6 | -------------------------------------------------------------------------------- /tools/chain_mockcli/linux_x86/chain_mockcli-linux-ubuntu22.04-0.1.0.zip: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:6f14988987a7b7e844bd8584e71f4efdb1ffd6b94f74edae27815501f017498f 3 | size 2546024 4 | -------------------------------------------------------------------------------- /tools/chain_mockcli/mac_arm/chain_mockcli-mac-arm-0.1.0.zip: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:0ced5a41efdbb790f29467a8acdfd2810bb51b3438780a823840ad3f60a8a2fe 3 | size 2421184 4 | -------------------------------------------------------------------------------- /tools/format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Get the directory where the script is located 6 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 7 | PROJECT_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )" 8 | 9 | # Function to perform formatting 10 | format() { 11 | echo "Running solidcpp cargo fmt..." 12 | cd "$PROJECT_ROOT/solidcpp" 13 | cargo fmt 14 | echo "Running solidcpp cargo clippy --fix..." 15 | cargo clippy --fix --allow-dirty 16 | cd "$PROJECT_ROOT" 17 | 18 | 19 | echo "Running contractlib clang-format..." 20 | cd "$PROJECT_ROOT/contractlib" 21 | clang-format -i v1/*.h v1/*.hpp v1/*.cpp 22 | cd "$PROJECT_ROOT" 23 | 24 | echo "Running cpp_tests clang-format..." 25 | cd "$PROJECT_ROOT/cpp_tests" 26 | clang-format -i *.cpp *.hpp 27 | cd "$PROJECT_ROOT" 28 | } 29 | 30 | # Function to perform format checking 31 | check() { 32 | echo "Running solidcpp cargo fmt --check..." 33 | cd "$PROJECT_ROOT/solidcpp" 34 | cargo fmt --all -- --check 35 | echo "Running solidcpp cargo clippy check..." 36 | cargo clippy --all-targets --all-features -- -D warnings 37 | cd "$PROJECT_ROOT" 38 | 39 | echo "Running contractlib make fmt_check..." 40 | cd "$PROJECT_ROOT/contractlib" 41 | clang-format --dry-run --Werror v1/*.h v1/*.hpp v1/*.cpp 42 | cd "$PROJECT_ROOT" 43 | 44 | echo "Running cpp_tests make fmt_check..." 45 | cd "$PROJECT_ROOT/cpp_tests" 46 | clang-format --dry-run --Werror *.cpp *.hpp 47 | cd "$PROJECT_ROOT" 48 | } 49 | 50 | # Main script logic 51 | case "$1" in 52 | "format") 53 | format 54 | ;; 55 | "check") 56 | check 57 | ;; 58 | *) 59 | echo "Usage: $0 {format|check}" 60 | echo " format: Run formatting tools" 61 | echo " check: Run format checking tools" 62 | exit 1 63 | ;; 64 | esac 65 | --------------------------------------------------------------------------------