diff --git a/.devcontainer/Dockerfile.dev b/.devcontainer/Dockerfile.dev index 8f7c104f..f45f2ff8 100644 --- a/.devcontainer/Dockerfile.dev +++ b/.devcontainer/Dockerfile.dev @@ -8,7 +8,7 @@ RUN apt-get update && apt-get upgrade -y RUN apt-get install -y build-essential cmake tmux clang-tidy autoconf libtool pkg-config libabsl-dev libboost-all-dev libc-ares-dev libcrypto++-dev libgrpc-dev libgrpc++-dev librocksdb-dev libscrypt-dev libsnappy-dev libssl-dev zlib1g-dev openssl protobuf-compiler protobuf-compiler-grpc nano vim unison git gdb ninja-build # Set the working directory in the Docker container -WORKDIR /orbitersdk-cpp +WORKDIR /bdk-cpp # Copy Unison configuration file COPY sync.prf /root/.unison/sync.prf \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 541a19a2..e28f3de5 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,5 +1,5 @@ { - "name": "OrbiterSDK C++ Dev Container", + "name": "BDK C++ Dev Container", "build": { "dockerfile": "Dockerfile.dev" }, @@ -8,11 +8,11 @@ "terminal.integrated.shell.linux": "/bin/bash" }, "mounts": [ - "source=${localWorkspaceFolder},target=/orbitersdk-cpp,type=bind,consistency=cached" + "source=${localWorkspaceFolder},target=/bdk-cpp,type=bind,consistency=cached" ], "runArgs": ["-it", "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"], "extensions": ["ms-vscode.cpptools", "ms-vscode.cmake-tools"], - "postCreateCommand": "mkdir /orbitersdk-data && nohup unison -repeat 1 /orbitersdk-cpp /orbitersdk-data -auto -batch \ + "postCreateCommand": "mkdir /bdk-data && nohup unison -repeat 1 /bdk-cpp /bdk-data -auto -batch \ -ignore 'Name {build}' \ -ignore 'Name {build_local_testnet}' \ -ignore 'Name {.vscode}' \ @@ -38,5 +38,5 @@ -ignore 'Name {kateproject}' \ -ignore 'Name {*.o}' \ -ignore 'Name {*.gch}' \ - > /dev/null 2>&1 && cp -r /orbitersdk-cpp/* /orbitersdk-data" + > /dev/null 2>&1 && cp -r /bdk-cpp/* /bdk-data" } diff --git a/.devcontainer/sync.prf b/.devcontainer/sync.prf index c02b36b1..f1da84ef 100644 --- a/.devcontainer/sync.prf +++ b/.devcontainer/sync.prf @@ -1,6 +1,6 @@ # Unison synchronization profile -root = /orbitersdk-cpp -root = /orbitersdk-data +root = /bdk-cpp +root = /bdk-data # Specify synchronization options auto = true diff --git a/.dockerignore b/.dockerignore index b6a7208b..5da5368f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,3 @@ -build/ *.o *.gch proto/metrics.pb.cc @@ -18,6 +17,7 @@ depends/x86_64-pc-linux-gnu/ scripts/AIO-setup.log compile_commands.json .cache/ +.backup/ #IDEA's IDE .idea/ @@ -27,6 +27,9 @@ cmake-build-release/ # Files generated by CMake src/utils/options.h +# build directory +build/ + # Files generated by automated scripts local_testnet/ -build_local_testnet/ \ No newline at end of file +build_local_testnet/ diff --git a/.github/workflows/PULL_REQUEST_TEMPLATE.md b/.github/workflows/PULL_REQUEST_TEMPLATE.md index 99f3c736..a1819943 100644 --- a/.github/workflows/PULL_REQUEST_TEMPLATE.md +++ b/.github/workflows/PULL_REQUEST_TEMPLATE.md @@ -40,7 +40,7 @@ https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-git ## Added to documentation? - [ ] 📜 README.md -- [ ] 📓 [Sparq Docs](https://github.com/SparqNet/sparq-docs) +- [ ] 📓 [Sparq Docs](https://github.com/AppLayer/sparq-docs) - [ ] 🙅 no documentation needed ## [optional] Are there any post-deployment tasks we need to perform? diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 11b72672..3fb8fd31 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -11,96 +11,69 @@ on: - development jobs: - setup: - runs-on: ubuntu-latest - - container: - image: debian:bookworm - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Update apt-get - run: apt-get update - - - name: Install project dependencies - run: DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential cmake tmux clang-tidy autoconf libtool pkg-config libabsl-dev libboost-all-dev libc-ares-dev libcrypto++-dev libgrpc-dev libgrpc++-dev librocksdb-dev libscrypt-dev libsnappy-dev libssl-dev zlib1g-dev openssl protobuf-compiler protobuf-compiler-grpc libprotobuf-dev git doxygen curl unzip - - - name: Print GCC version - run: gcc --version - - - name: Install CA certificates - run: apt-get install -y ca-certificates - build_test_and_analyse: - needs: setup - runs-on: ubuntu-latest - - container: - image: debian:bookworm + runs-on: [self-hosted, linux, x64] env: # https://docs.sonarqube.org/latest/analysis/scan/sonarscanner/ - BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed + # Directory where build-wrapper output will be placed + BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + TARGET_DIR: "/home/actions/actions-runner/_work/bdk-cpp/bdk-cpp" + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Update apt-get - run: apt-get update - - - name: Install project dependencies - run: DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential cmake tmux clang-tidy autoconf libtool pkg-config libabsl-dev libboost-all-dev libc-ares-dev libcrypto++-dev libgrpc-dev libgrpc++-dev librocksdb-dev libscrypt-dev libsnappy-dev libssl-dev zlib1g-dev openssl protobuf-compiler protobuf-compiler-grpc libprotobuf-dev git curl unzip gcovr - - - name: Install sonar-scanner and build-wrapper - uses: SonarSource/sonarcloud-github-c-cpp@v2 - - - name: Configure CMake - run: cmake -S . -B build -DSONARQUBE_ANALYSIS=ON -DDEBUG=OFF - + - name: Clone the repository + run: git clone https://github.com/${{github.repository}} ${{env.TARGET_DIR}} || true + - name: Sync with remote repository + run: git -C ${{env.TARGET_DIR}} fetch + - name: Checkout to current branch + run: git -C ${{env.TARGET_DIR}} checkout ${{env.BRANCH_NAME}} + - name: Set user.email + run: git -C ${{env.TARGET_DIR}} config --global user.email "github-actions[bot]@users.noreply.github.com" + - name: Set user.name + run: git -C ${{env.TARGET_DIR}} config --global user.name "github-actions" + - name: Update local repository + run: git -C ${{env.TARGET_DIR}} pull + - name: Build the container + run: ./scripts/auto.sh -s bdk build + - name: Stop the container + run: ./scripts/auto.sh -s bdk stop + - name: Restart the container + run: ./scripts/auto.sh -s bdk up + - name: Clean previous build (if there is one) + run: ./scripts/auto.sh -s bdk exec 'make -C build clean' || true + - name: Configure MOLD linker + run: ./scripts/auto.sh -s bdk exec 'cmake -S . -B build + -DSONARQUBE_ANALYSIS=ON -DDEBUG=OFF + -DCMAKE_EXE_LINKER_FLAGS=\"-fuse-ld=mold\" + -DCMAKE_SHARED_LINKER_FLAGS=\"-fuse-ld=mold\"' - name: Build with SonarQube BuildWrapper + CMake - run: build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build build/ --config Release -- -j $(nproc) - - - name: Give execute permissions - run: chmod +x ./build/orbitersdkd-tests - - - name: Run Catch2 Tests - run: ./build/orbitersdkd-tests -d yes - + run: ./scripts/auto.sh -s bdk exec \ + 'build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} + cmake --build build --config Release -- -j $(nproc)' + - name: Run Tests + run: ./scripts/auto.sh -s bdk exec \ + './build/src/bins/bdkd-tests/bdkd-tests -d yes' + - name: Delete coverage XML report (if any) + run: ./scripts/auto.sh -s bdk exec 'rm coverage.xml || true' - name: Collect coverage into one XML report - run: | - gcovr --gcov-ignore-parse-errors --sonarqube > coverage.xml - + run: ./scripts/auto.sh -s bdk exec \ + 'gcovr -d --gcov-ignore-parse-errors --exclude-throw-branches --sonarqube -o coverage.xml' - name: Run SonarQube Scanner - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} - run: | - sonar-scanner --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" --define sonar.coverageReportPaths=coverage.xml + run: ./scripts/auto.sh -s bdk exec \ + 'env SONAR_TOKEN=${{ env.SONAR_TOKEN }} + SONAR_HOST_URL=${{ env.SONAR_HOST_URL }} + sonar-scanner + --define sonar.cfamily.build-wrapper-output=${{ env.BUILD_WRAPPER_OUT_DIR }} + --define sonar.coverageReportPaths=coverage.xml' documentation: + runs-on: [self-hosted, linux, x64] needs: build_test_and_analyse - runs-on: ubuntu-latest - - container: - image: debian:bookworm - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name : Update apt-get - run: apt-get update - - - name : Install Doxygen - run: apt-get install -y doxygen - - name: Generate Doxygen Documentation - run: | - mkdir docs - doxygen Doxyfile - + run: ./scripts/auto.sh -s bdk exec 'doxygen Doxyfile' - name: Publish Documentation uses: actions/upload-artifact@v4 with: name: Documentation - path: docs \ No newline at end of file + path: docs diff --git a/.gitignore b/.gitignore index c3c34df9..f8858807 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,11 @@ compile_commands.json cmake-build-debug/ cmake-build-release/ +# emacs +TAGS +.dir-locals.el +.backup + # VILARINHO's ignored files liner.sh prepare-lib.sh @@ -48,4 +53,5 @@ build_local_testnet/ # Files generated by Doxygen docs/html/ +# Documentation sparq-docs diff --git a/.kateproject b/.kateproject index 79fd0844..02cbdb86 100644 --- a/.kateproject +++ b/.kateproject @@ -1,4 +1,4 @@ { - "name": "orbitersdk", + "name": "bdk", "files": [ { "git": 1 } ] } diff --git a/CMakeLists.txt b/CMakeLists.txt index 175f68b4..dc0adf93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,24 +16,39 @@ if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") cmake_policy(SET CMP0135 NEW) endif() +# TODO: avoid FindBoost deprecation message in CMake 3.30+ (cmake --help-policy CMP0167) + # Project data -project(orbitersdk VERSION 0.2.0 DESCRIPTION "Sparq subnet") +project(bdk VERSION 0.2.0 DESCRIPTION "AppLayer Blockchain Development Kit") set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) set(CMAKE_CXX_EXTENSIONS OFF) SET(DEBUG ON CACHE BOOL "Debug mode") +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") # Always look for static libraries - "ZLIB_USE_STATIC_LIBS" was added in 3.24 +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # For clang-tidy + +# Set compiler flags +# TODO: -Wno-c++26-extensions is included because zpp_libs uses name-independent declarations (vars named "_"). +# This should be resolved at a later date, but was turned off for now because it doesn't affect us if(DEBUG) - set(CMAKE_CXX_FLAGS "-O0 -g -fsanitize=address -fno-inline -fno-eliminate-unused-debug-types -fstack-protector -Werror=unused-variable") # Provides faster compile time. + set(CMAKE_CXX_FLAGS "-O0 -g -Wno-c++26-extensions -fsanitize=address -fno-inline -fno-eliminate-unused-debug-types -fstack-protector") # Provides faster compile time. elseif(SONARQUBE_ANALYSIS) - set(CMAKE_CXX_FLAGS "-O0 -g --coverage") + set(CMAKE_CXX_FLAGS "-O0 -g -Wno-c++26-extensions --coverage") else() - set(CMAKE_CXX_FLAGS "-O2 -Werror=unused-variable") + set(CMAKE_CXX_FLAGS "-O2 -Wno-c++26-extensions -Werror=unused-variable") +endif() +find_program(MOLD "mold") # Use mold by default if it is installed +if(MOLD) + message(STATUS "Using mold as linker") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=mold") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=mold") endif() -set(CMAKE_POSITION_INDEPENDENT_CODE ON) -set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") # Always look for static libraries - "ZLIB_USE_STATIC_LIBS" was added in 3.24 -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # For clang-tidy -# Set project version inside the code +# Set project version inside the code (forcefully so changes in the .in file are always reflected correctly to the compiler) +# if (EXISTS ${CMAKE_SOURCE_DIR}/src/utils/options.h) +# file(REMOVE ${CMAKE_SOURCE_DIR}/src/utils/options.h) +# endif() configure_file( ${CMAKE_SOURCE_DIR}/src/utils/options.h.in ${CMAKE_SOURCE_DIR}/src/utils/options.h @@ -43,13 +58,20 @@ configure_file( # External project data set(BUILD_TESTS ON CACHE BOOL "Build helper unit testing program") set(BUILD_DISCOVERY ON CACHE BOOL "Build helper discovery node program") -set(BUILD_AVALANCHEGO OFF CACHE BOOL "Build with AvalancheGo wrapping") set(BUILD_TOOLS OFF CACHE BOOL "Build tools related to subnet") +set(BUILD_TESTNET OFF CACHE BOOL "Build the project for testnet") +set(BUILD_BENCHMARK OFF CACHE BOOL "Build with the benchmark tests") +set(BUILD_BTVSERVER OFF CACHE BOOL "Build the BTV (Build the Void) websocket server") +set(BUILD_VARIABLES_TESTS ON CACHE BOOL "Build tests for SafeVar (Contract variables)") set(USE_LINT OFF CACHE BOOL "Run linter on compile (clang-tidy)") if(USE_LINT) set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-header-filter=.;-checks=-*,abseil-*,boost-*,bugprone-*,cert-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,hicpp-*,misc-*,modernize-*,performance-*,portability-*,readability-*") endif() +if(BUILD_TESTNET) + add_definitions(-DBUILD_TESTNET) +endif() + # Echo CMake vars during config message(STATUS "C++ standard: ${CMAKE_CXX_STANDARD}") message(STATUS "C++ standard is required: ${CMAKE_CXX_STANDARD_REQUIRED}") @@ -59,11 +81,13 @@ message(STATUS "Using PIC: ${CMAKE_POSITION_INDEPENDENT_CODE}") message(STATUS "Find libs with suffix: ${CMAKE_FIND_LIBRARY_SUFFIXES}") message("Building tests: ${BUILD_TESTS}") message("Building Discovery Node: ${BUILD_DISCOVERY}") -message("Building AvalancheGo support: ${BUILD_AVALANCHEGO}") message("Building tools: ${BUILD_TOOLS}") +message("Building testnet: ${BUILD_TESTNET}") +message("Building benchmark tests: ${BUILD_BENCHMARK}") +message("Building SafeVar tests: ${BUILD_VARIABLES_TESTS}") message("Using lint: ${USE_LINT}") -cable_add_buildinfo_library(PROJECT_NAME orbitersdk) +cable_add_buildinfo_library(PROJECT_NAME bdk) # System package configs (built-in) set(Boost_USE_STATIC_LIBS ON) @@ -71,20 +95,23 @@ set(OPENSSL_USE_STATIC_LIBS ON) set(Protobuf_USE_STATIC_LIBS ON) # Find system packages (built-in) -find_package(Threads) -find_package(Boost 1.74.0 REQUIRED COMPONENTS chrono filesystem program_options system thread nowide) +find_package(Boost 1.83.0 REQUIRED COMPONENTS chrono filesystem program_options system thread nowide) find_package(OpenSSL 1.1.1 REQUIRED) -find_package(ZLIB REQUIRED) +find_package(Protobuf REQUIRED) # TODO: not used yet but will be, keep it for now +find_package(Threads) # Find system packages (custom) +find_package(Cares REQUIRED) # TODO: not used yet but will be, keep it for now find_package(CryptoPP 8.2.0 REQUIRED) +find_package(Ethash REQUIRED) +find_package(Evmc REQUIRED) +find_package(Evmone REQUIRED) +find_package(GRPC REQUIRED) # TODO: not used yet but will be, keep it for now +find_package(Keccak REQUIRED) find_package(Scrypt REQUIRED) - -# Add external modules -include(cmake/ProjectBoostCertify.cmake) # Boost Certify -include(cmake/ProjectEthash.cmake) # Ethash -include(cmake/ProjectSecp256k1.cmake) # Bitcoin core fast implementation -include(cmake/ProjectSpeedb.cmake) # Speedb (Level/RocksDB drop-in replacement) +find_package(Secp256k1 REQUIRED) +find_package(Speedb REQUIRED) +find_package(SQLiteCpp REQUIRED) # Add catch2 as a library add_library(catch2 @@ -92,18 +119,11 @@ add_library(catch2 ${CMAKE_SOURCE_DIR}/src/libs/catch2/catch_amalgamated.cpp ) target_include_directories(catch2 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/catch2) +target_compile_definitions(catch2 PRIVATE CATCH_AMALGAMATED_CUSTOM_MAIN) # Check compiler variable sizes include(cmake/CheckSizes.cmake) -# Add AvalancheGo wrapper dependencies if compiling it -if(BUILD_AVALANCHEGO) - find_package(Absl REQUIRED) # Built-in is hardcoded to SHARED, this one to STATIC - find_package(Cares REQUIRED) - find_package(Protobuf 3.12 REQUIRED) - find_package(GRPC REQUIRED) -endif() - # Include directories for headers and libs include_directories( "${CMAKE_SOURCE_DIR}" @@ -121,364 +141,29 @@ link_directories( "${CMAKE_SOURCE_DIR}/build/deps/lib" ) -# Organize, compile and link orbitersdk libs +# Organize, compile and link bdk libs add_subdirectory(src/contract) add_subdirectory(src/core) add_subdirectory(src/net) add_subdirectory(src/utils) add_subdirectory(tests) -# Generate gRPC files if building with support for AvalancheGo. -# Headers/sources are always cleaned at configure so they can be regenerated at build -if(BUILD_AVALANCHEGO) - file(REMOVE - "${CMAKE_SOURCE_DIR}/proto/vm.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/aliasreader.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/appsender.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/keystore.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/messenger.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/sharedmemory.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/rpcdb.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/vm.grpc.pb.h" - "${CMAKE_SOURCE_DIR}/proto/aliasreader.grpc.pb.h" - "${CMAKE_SOURCE_DIR}/proto/appsender.grpc.pb.h" - "${CMAKE_SOURCE_DIR}/proto/keystore.grpc.pb.h" - "${CMAKE_SOURCE_DIR}/proto/messenger.grpc.pb.h" - "${CMAKE_SOURCE_DIR}/proto/sharedmemory.grpc.pb.h" - "${CMAKE_SOURCE_DIR}/proto/rpcdb.grpc.pb.h" - "${CMAKE_SOURCE_DIR}/proto/vm.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/aliasreader.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/appsender.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/keystore.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/messenger.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/sharedmemory.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/rpcdb.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/vm.pb.h" - "${CMAKE_SOURCE_DIR}/proto/aliasreader.pb.h" - "${CMAKE_SOURCE_DIR}/proto/appsender.pb.h" - "${CMAKE_SOURCE_DIR}/proto/keystore.pb.h" - "${CMAKE_SOURCE_DIR}/proto/messenger.pb.h" - "${CMAKE_SOURCE_DIR}/proto/sharedmemory.pb.h" - "${CMAKE_SOURCE_DIR}/proto/rpcdb.pb.h" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/aliasreader.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/aliasreader.grpc.pb.h" - COMMAND "protoc" - ARGS --grpc_out="${CMAKE_SOURCE_DIR}/proto" - --plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN}" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - "${CMAKE_SOURCE_DIR}/proto/aliasreader.proto" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/appsender.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/appsender.grpc.pb.h" - COMMAND "protoc" - ARGS --grpc_out="${CMAKE_SOURCE_DIR}/proto" - --plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN}" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - "${CMAKE_SOURCE_DIR}/proto/appsender.proto" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/keystore.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/keystore.grpc.pb.h" - COMMAND "protoc" - ARGS --grpc_out="${CMAKE_SOURCE_DIR}/proto" - --plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN}" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - "${CMAKE_SOURCE_DIR}/proto/keystore.proto" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/messenger.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/messenger.grpc.pb.h" - COMMAND "protoc" - ARGS --grpc_out="${CMAKE_SOURCE_DIR}/proto" - --plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN}" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - "${CMAKE_SOURCE_DIR}/proto/messenger.proto" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/metrics.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/metrics.grpc.pb.h" - COMMAND "protoc" - ARGS --grpc_out="${CMAKE_SOURCE_DIR}/proto" - --plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN}" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - "${CMAKE_SOURCE_DIR}/proto/metrics.proto" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/sharedmemory.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/sharedmemory.grpc.pb.h" - COMMAND "protoc" - ARGS --grpc_out="${CMAKE_SOURCE_DIR}/proto" - --plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN}" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - "${CMAKE_SOURCE_DIR}/proto/sharedmemory.proto" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/rpcdb.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/rpcdb.grpc.pb.h" - COMMAND "protoc" - ARGS --grpc_out="${CMAKE_SOURCE_DIR}/proto" - --plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN}" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - "${CMAKE_SOURCE_DIR}/proto/rpcdb.proto" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/vm.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/vm.grpc.pb.h" - COMMAND "protoc" - ARGS --grpc_out="${CMAKE_SOURCE_DIR}/proto" - --plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN}" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - --experimental_allow_proto3_optional - "${CMAKE_SOURCE_DIR}/proto/vm.proto" - ) - - # Protobuf PROTOBUF_GENERATE_CPP does NOT work with --experimental_allow_proto3_optional - # requiring us to go over protobuf files with add_custom_command - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/aliasreader.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/aliasreader.pb.h" - COMMAND "protoc" - ARGS --cpp_out="${CMAKE_SOURCE_DIR}/proto" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - "${CMAKE_SOURCE_DIR}/proto/aliasreader.proto" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/appsender.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/appsender.pb.h" - COMMAND "protoc" - ARGS --cpp_out="${CMAKE_SOURCE_DIR}/proto" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - "${CMAKE_SOURCE_DIR}/proto/appsender.proto" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/keystore.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/keystore.pb.h" - COMMAND "protoc" - ARGS --cpp_out="${CMAKE_SOURCE_DIR}/proto" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - "${CMAKE_SOURCE_DIR}/proto/keystore.proto" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/messenger.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/messenger.pb.h" - COMMAND "protoc" - ARGS --cpp_out="${CMAKE_SOURCE_DIR}/proto" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - "${CMAKE_SOURCE_DIR}/proto/messenger.proto" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/metrics.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/metrics.pb.h" - COMMAND "protoc" - ARGS --cpp_out="${CMAKE_SOURCE_DIR}/proto" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - "${CMAKE_SOURCE_DIR}/proto/metrics.proto" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/sharedmemory.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/sharedmemory.pb.h" - COMMAND "protoc" - ARGS --cpp_out="${CMAKE_SOURCE_DIR}/proto" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - "${CMAKE_SOURCE_DIR}/proto/sharedmemory.proto" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/rpcdb.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/rpcdb.pb.h" - COMMAND "protoc" - ARGS --cpp_out="${CMAKE_SOURCE_DIR}/proto" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - "${CMAKE_SOURCE_DIR}/proto/rpcdb.proto" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/metrics.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/metrics.pb.h" - COMMAND "protoc" - ARGS --cpp_out="${CMAKE_SOURCE_DIR}/proto" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - "${CMAKE_SOURCE_DIR}/proto/metrics.proto" - ) - - add_custom_command( - OUTPUT "${CMAKE_SOURCE_DIR}/proto/vm.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/vm.pb.h" - COMMAND "protoc" - ARGS --cpp_out="${CMAKE_SOURCE_DIR}/proto" - --proto_path="${CMAKE_SOURCE_DIR}/proto" - --experimental_allow_proto3_optional - "${CMAKE_SOURCE_DIR}/proto/vm.proto" - ) - - add_library(ProtoFiles - "${CMAKE_SOURCE_DIR}/proto/vm.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/aliasreader.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/appsender.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/keystore.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/messenger.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/sharedmemory.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/rpcdb.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/metrics.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/vm.pb.h" - "${CMAKE_SOURCE_DIR}/proto/aliasreader.pb.h" - "${CMAKE_SOURCE_DIR}/proto/appsender.pb.h" - "${CMAKE_SOURCE_DIR}/proto/keystore.pb.h" - "${CMAKE_SOURCE_DIR}/proto/messenger.pb.h" - "${CMAKE_SOURCE_DIR}/proto/sharedmemory.pb.h" - "${CMAKE_SOURCE_DIR}/proto/rpcdb.pb.h" - "${CMAKE_SOURCE_DIR}/proto/metrics.pb.h" - ) - - # You HAVE to set the file names - add_library (gen-grpc - "${CMAKE_SOURCE_DIR}/proto/vm.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/aliasreader.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/appsender.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/keystore.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/messenger.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/sharedmemory.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/rpcdb.grpc.pb.cc" - "${CMAKE_SOURCE_DIR}/proto/vm.grpc.pb.h" - "${CMAKE_SOURCE_DIR}/proto/aliasreader.grpc.pb.h" - "${CMAKE_SOURCE_DIR}/proto/appsender.grpc.pb.h" - "${CMAKE_SOURCE_DIR}/proto/keystore.grpc.pb.h" - "${CMAKE_SOURCE_DIR}/proto/messenger.grpc.pb.h" - "${CMAKE_SOURCE_DIR}/proto/sharedmemory.grpc.pb.h" - "${CMAKE_SOURCE_DIR}/proto/rpcdb.grpc.pb.h" - ${ProtoFiles} - ) - - target_link_libraries(gen-grpc PUBLIC ${Protobuf_LIBRARIES} ${GRPC_LIBRARIES} ${CARES_LIBRARY} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} absl::flags) - - add_library(orbitersdk_lib STATIC - ${UTILS_HEADERS} - ${UTILS_SOURCES} - ${CONTRACT_HEADERS} - ${CONTRACT_SOURCES} - ${CORE_HEADERS} - ${CORE_SOURCES} - ${NET_HEADERS} - ${NET_SOURCES} - ) - - add_dependencies(orbitersdk_lib gen-grpc ProtoFiles) - - target_include_directories(orbitersdk_lib PUBLIC ${CMAKE_SOURCE_DIR}/include ${OPENSSL_INCLUDE_DIR}) - - target_link_libraries(orbitersdk_lib PRIVATE - ${CRYPTOPP_LIBRARIES} ${SCRYPT_LIBRARY} Secp256k1 Ethash ${ETHASH_BYPRODUCTS} ${Protobuf_LIBRARIES} - ${GRPC_LIBRARIES} ${CARES_LIBRARY} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} absl::flags - Speedb ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} - ) - - set_target_properties(orbitersdk_lib PROPERTIES COMPILE_FLAGS "-DAVALANCHEGO_COMPATIBLE=1") - - # Compile and link the executable - add_executable(orbitersdkd "${CMAKE_SOURCE_DIR}/src/main.cpp") - - add_dependencies(orbitersdkd orbitersdk_lib gen-grpc ProtoFiles) - target_include_directories(orbitersdkd PRIVATE orbitersdk_lib ${OPENSSL_INCLUDE_DIR}) - target_link_libraries(orbitersdkd - orbitersdk_lib - ${Protobuf_LIBRARIES} ${GRPC_LIBRARIES} ${CARES_LIBRARY} Speedb - ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} - absl::flags Secp256k1 Ethash ${ETHASH_BYPRODUCTS} - ) - - # Compile and link the ABI generator executable - add_executable(contractabigenerator "${CMAKE_SOURCE_DIR}/src/main-contract-abi.cpp") - - add_dependencies(contractabigenerator orbitersdk_lib) - target_include_directories(contractabigenerator PRIVATE orbitersdk_lib ${OPENSSL_INCLUDE_DIR}) - target_link_libraries(contractabigenerator - orbitersdk_lib Speedb ${SNAPPY_LIBRARY} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} Secp256k1 Ethash ${ETHASH_BYPRODUCTS} - ) - - # TODO: Implement tests for AvalancheGo compilation. -else() - add_library(orbitersdk_lib STATIC - ${UTILS_HEADERS} - ${UTILS_SOURCES} - ${CONTRACT_HEADERS} - ${CONTRACT_SOURCES} - ${CORE_HEADERS} - ${CORE_SOURCES} - ${NET_HEADERS} - ${NET_SOURCES} - ) - - target_include_directories(orbitersdk_lib PRIVATE ${CMAKE_SOURCE_DIR}/include ${OPENSSL_INCLUDE_DIR}) - - target_link_libraries(orbitersdk_lib PRIVATE - ${CRYPTOPP_LIBRARIES} ${SCRYPT_LIBRARY} Secp256k1 Ethash ${ETHASH_BYPRODUCTS} - Speedb ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} - ) - - set_target_properties(orbitersdk_lib PROPERTIES COMPILE_FLAGS "-DAVALANCHEGO_COMPATIBLE=0") - - # Compile and link the executable - add_executable(orbitersdkd "${CMAKE_SOURCE_DIR}/src/main.cpp") - - add_dependencies(orbitersdkd orbitersdk_lib) - target_include_directories(orbitersdkd PRIVATE orbitersdk_lib ${OPENSSL_INCLUDE_DIR}) - target_link_libraries(orbitersdkd - orbitersdk_lib Speedb ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} Secp256k1 Ethash ${ETHASH_BYPRODUCTS} - ) - - # Compile and link the ABI generator executable - add_executable(contractabigenerator "${CMAKE_SOURCE_DIR}/src/main-contract-abi.cpp") - - add_dependencies(contractabigenerator orbitersdk_lib) - target_include_directories(contractabigenerator PRIVATE orbitersdk_lib ${OPENSSL_INCLUDE_DIR}) - target_link_libraries(contractabigenerator - orbitersdk_lib Speedb ${SNAPPY_LIBRARY} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} Secp256k1 Ethash ${ETHASH_BYPRODUCTS} - ) - - # Compile and link the ABI generator executable - add_executable(networkdeployer "${CMAKE_SOURCE_DIR}/src/networkdeployer.cpp") +add_library(bdk_lib STATIC + ${UTILS_HEADERS} ${UTILS_SOURCES} ${CONTRACT_HEADERS} ${CONTRACT_SOURCES} + ${CORE_HEADERS} ${CORE_SOURCES} ${NET_HEADERS} ${NET_SOURCES} +) - add_dependencies(networkdeployer orbitersdk_lib) - target_include_directories(networkdeployer PRIVATE orbitersdk_lib ${OPENSSL_INCLUDE_DIR}) - target_link_libraries(networkdeployer - orbitersdk_lib Speedb ${SNAPPY_LIBRARY} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} Secp256k1 Ethash ${ETHASH_BYPRODUCTS} - ) -endif() +target_include_directories(bdk_lib PRIVATE + ${CMAKE_SOURCE_DIR}/include ${OPENSSL_INCLUDE_DIR} ${ETHASH_INCLUDE_DIR} ${KECCAK_INCLUDE_DIR} + ${EVMC_INCLUDE_DIR} ${EVMONE_INCLUDE_DIR} ${SPEEDB_INCLUDE_DIR} ${SECP256K1_INCLUDE_DIR} +) -# Compile and link the test executable if set to build it -if (BUILD_TESTS) - add_executable(orbitersdkd-tests ${TESTS_HEADERS} ${TESTS_SOURCES}) - add_dependencies(orbitersdkd-tests orbitersdk_lib) - target_include_directories(orbitersdkd-tests PRIVATE orbitersdk_lib ${OPENSSL_INCLUDE_DIR}) - target_link_libraries(orbitersdkd-tests - orbitersdk_lib Speedb ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} Secp256k1 catch2 Ethash ${ETHASH_BYPRODUCTS} - ) -endif() +target_link_libraries(bdk_lib PRIVATE + ${EVMC_INSTRUCTIONS_LIBRARY} ${EVMC_LOADER_LIBRARY} ${EVMONE_LIBRARY} + ${CRYPTOPP_LIBRARIES} ${SCRYPT_LIBRARY} ${SECP256K1_LIBRARY} + ${ETHASH_LIBRARY} ${KECCAK_LIBRARY} ${SPEEDB_LIBRARY} + ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} -l:liblz4.a SQLiteCpp +) -# Compile and link the Discovery Node test executable if set to build it -if (BUILD_DISCOVERY) - add_executable(orbitersdkd-discovery "${CMAKE_SOURCE_DIR}/src/main-discovery.cpp") - add_dependencies(orbitersdkd-discovery orbitersdk_lib) - target_include_directories(orbitersdkd-discovery PRIVATE orbitersdk_lib ${OPENSSL_INCLUDE_DIR}) - target_link_libraries(orbitersdkd-discovery - orbitersdk_lib Speedb ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} Secp256k1 Ethash ${ETHASH_BYPRODUCTS} - ) -endif() +add_subdirectory(src/bins) diff --git a/CMakePresets.json b/CMakePresets.json index 2ffffa4f..4d8c159e 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -5,7 +5,7 @@ "name": "linux-release", "displayName": "Linux Release", "generator": "Ninja", - "binaryDir": "/orbitersdk-data/build_local_testnet", + "binaryDir": "/bdk-data/build_local_testnet", "installDir": "${sourceDir}/out/install/${presetName}", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" @@ -25,7 +25,7 @@ "name": "linux-debug", "displayName": "Linux Debug", "generator": "Ninja", - "binaryDir": "/orbitersdk-data/build_local_testnet", + "binaryDir": "/bdk-data/build_local_testnet", "installDir": "${sourceDir}/out/install/${presetName}", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" diff --git a/Dockerfile b/Dockerfile index b47ebff6..781642cf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,30 +1,36 @@ -# Copyright (c) [2023-2024] [Sparq Network] +# Copyright (c) [2023-2024] [AppLayer Developers] # This software is distributed under the MIT License. # See the LICENSE.txt file in the project root for more information. # Start from a base Debian image -FROM debian:bookworm +FROM debian:trixie + +# Set shell to Bash because Docker standards are stupid +SHELL ["/bin/bash", "-c"] # Update the system RUN apt-get update && apt-get upgrade -y -# Install dependencies -RUN apt-get install -y build-essential cmake tmux clang-tidy autoconf libtool pkg-config libabsl-dev libboost-all-dev libc-ares-dev libcrypto++-dev libgrpc-dev libgrpc++-dev librocksdb-dev libscrypt-dev libsnappy-dev libssl-dev zlib1g-dev openssl protobuf-compiler protobuf-compiler-grpc nano vim unison git - # Set the working directory in the Docker container -WORKDIR /orbitersdk-cpp +WORKDIR /bdk-cpp # Copy the local folder to the container -COPY . /orbitersdk-cpp +COPY . /bdk-cpp + +# Install Docker-specific dependencies +RUN apt-get -y install nano vim unison curl jq unzip + +# Install dependencies +RUN bash /bdk-cpp/scripts/deps.sh --install # Create the synchronized directory -RUN mkdir /orbitersdk-volume +RUN mkdir /bdk-volume # Copy Unison configuration file COPY sync.prf /root/.unison/sync.prf # Start Unison in the background, ignoring files that should not be synced -CMD nohup unison -repeat 1 /orbitersdk-volume /orbitersdk-cpp -auto -batch \ +CMD nohup unison -repeat 1 /bdk-volume /bdk-cpp -auto -batch \ -ignore 'Name {build}' \ -ignore 'Name {build_local_testnet}' \ -ignore 'Name {.vscode}' \ diff --git a/Doxyfile b/Doxyfile index dc3d7d8c..3f4ceba1 100644 --- a/Doxyfile +++ b/Doxyfile @@ -41,7 +41,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "OrbiterSDK" +PROJECT_NAME = "BDK" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -53,7 +53,7 @@ PROJECT_NUMBER = "1" # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = "Subnet from Sparq Labs" +PROJECT_BRIEF = "Blockchain Development Kit" # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 @@ -410,7 +410,7 @@ IDL_PROPERTY_SUPPORT = YES # all members of a group must be documented explicitly. # The default value is: NO. -DISTRIBUTE_GROUP_DOC = NO +DISTRIBUTE_GROUP_DOC = YES # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option diff --git a/README.md b/README.md index 2cb8e0b0..253f6060 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# orbitersdk +# Blockchain Development Kit (BDK)

- - - - - - build status + + + + + + build status chat on Discord @@ -20,7 +20,7 @@ alt="chat on Telegram">

-Sparq subnet source code. [See the docs](https://github.com/SparqNet/sparq-docs) for a more thorough look at the project. +AppLayer's BDK source code. [See the docs](https://docs.applayer.com) for a more thorough look at the project. If you are a developer, fill this form out for free support and additional incentives: https://forms.gle/m83ceG3XoJY3fpwU9 @@ -32,41 +32,70 @@ The project has a Dockerfile at the root of the repository that will build the p * [Docker for Windows](https://docs.docker.com/docker-for-windows/install/) * [Docker for Mac](https://docs.docker.com/docker-for-mac/install/) * [Docker for Linux](https://docs.docker.com/desktop/install/linux-install/) -* Build the image locally with `docker build -t bdk-cpp-dev:latest ` (if using Linux or Mac, run as `sudo`) +* Build the image locally with `docker build -t bdk-cpp-dev:latest .` * This will build the image and tag it as `bdk-cpp-dev:latest` - you can change the tag to whatever you want, but remember to change it on the next step * Run the container (you will be logged in as root): - * **For Linux/Mac**: `sudo docker run -it -v $(pwd):/orbitersdk-volume -p 8080-8099:8080-8099 -p 8110-8111:8110-8111 orbitersdk-cpp-dev:latest` - * **For Windows**: `docker run -it -v %cd%:/orbitersdk-volume -p 8080-8099:8080-8099 -p 8110-8111:8110-8111 orbitersdk-cpp-dev:latest` + * **For Linux/Mac**: `docker run -it --name bdk-cpp -v $(pwd):/bdk-volume -p 8080-8099:8080-8099 -p 8110-8111:8110-8111 bdk-cpp-dev:latest` + * **For Windows**: `docker run -it --name bdk-cpp -v %cd%:/bdk-volume -p 8080-8099:8080-8099 -p 8110-8111:8110-8111 bdk-cpp-dev:latest` -Remember that we are using our local SDK repo as a volume, so every change in the local folder will be reflected to the container in real time, and vice-versa. +Remember that we are using our local repo as a volume, so every change in the local folder will be reflected to the container in real time, and vice-versa. Also, you can integrate the container with your favorite IDE or editor, e.g. [VSCode + Docker extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker). ## Developing manually -Install the following dependencies on your system: - -* **GCC** with support for **C++23** or higher -* **CMake 3.19.0** or higher -* **Boost 1.74** or higher (components: *chrono, filesystem, program-options, system, thread, nowide*) -* **OpenSSL 1.1.1** -* **CryptoPP 8.2.0** or higher -* **libscrypt** -* **zlib** -* **libsnappy** for database compression -* (optional) **clang-tidy** for linting - -If building with AvalancheGo support, you'll also need: - -* **Abseil (absl)** -* **libc-ares** -* **Protobuf 3.12** or higher -* **gRPC** - -### One-liners - -For **Debian 12 Bookworm or newer**: -* `sudo apt install build-essential cmake tmux clang-tidy autoconf libtool pkg-config libabsl-dev libboost-all-dev libc-ares-dev libcrypto++-dev libgrpc-dev libgrpc++-dev libscrypt-dev libssl-dev zlib1g-dev openssl protobuf-compiler protobuf-compiler-grpc` +You will need the following dependencies installed locally on your system: + +* *Toolchain binaries*: + * **git** + * **GCC** with support for **C++23** or higher + * **Make** + * **CMake 3.19.0** or higher + * **Protobuf** (protoc + grpc_cpp_plugin) + * **tmux** (for deploying) + * (optional) **ninja** if you prefer it over make + * (optional) **mold** if you prefer it over ld + * (optional) **doxygen** for generating docs + * (optional) **clang-tidy** for linting +* *Libraries*: + * **Boost 1.83** or higher (components: *chrono, filesystem, program-options, system, thread, nowide*) + * **OpenSSL 1.1.1** / **libssl 1.1.1** or higher + * **libzstd** + * **CryptoPP 8.2.0** or higher + * **libscrypt** + * **libc-ares** + * **gRPC** (libgrpc and libgrpc++) + * **secp256k1** + * **ethash** + **keccak** + * **EVMOne** + **EVMC** + * **Speedb** + +The versions of those dependencies should suffice out-of-the-box for at least the following distros (or greater, including their derivatives): + +* **Debian 13 (Trixie)** +* **Ubuntu 24.04 LTS (Noble Numbat)** +* **Linux Mint 22 (Wilma)** +* **Fedora 40** +* Any rolling release distro from around **May 2024** onwards (check their repos to be sure) + +### Tips for dependencies + +There is a script called `scripts/deps.sh` which you can use to check if you have those dependencies installed (`deps.sh --check`), install them in case you don't (`deps.sh --install`), and clean up the external ones for reinstalling (`deps.sh --cleanext`). The script expects dependencies to be installed either on `/usr` or `/usr/local`, giving preference to the latter if it finds anything there (so you can use a higher version of a dependency while still keeping your distro's default one). + +**Please note that installing most dependencies through the script only works on APT-based distros** (Debian, Ubuntu and derivatives) - you can still check the dependencies on any distro and install the few ones labeled as "external" (those are fetched through `git`), but if you're on a distro with another package manager and/or a distro older than one of the minimum ones listed above, you're on your own. + +For Debian specifically, you can (and should) use `update-alternatives` to register and set your GCC version to a more up-to-date build if required. + +If you're using a self-compiled GCC build out of the system path (e.g. `--prefix=/usr/local/gcc-X.Y.Z` instead of `--prefix=/usr/local`), don't forget to export its installation paths in your `PATH` and `LD_LIBRARY_PATH` env vars (to prevent e.g. "version `GLIBCXX_...'/`CXXABI_...` not found" errors). Put something like this in your `~/.bashrc` file for example, changing the version accordingly to whichever one you have installed: + +```bash +# For GCC in /usr/local +export LD_LIBRARY_PATH=/usr/local/lib64:$LD_LIBRARY_PATH + +# For self-contained GCC outside /usr/local +export PATH=/usr/local/gcc-14.2.0/bin:$PATH +export LD_LIBRARY_PATH=/usr/local/gcc-14.2.0/lib64:$LD_LIBRARY_PATH +``` ## Documentation @@ -74,18 +103,18 @@ We use [Doxygen](https://www.doxygen.nl/index.html) to generate documentation ov You should do this after running `cmake ..` in the build directory, as some header files need to be generated first. -For a more detailed explanation of the project's structure, check the [docs](https://github.com/SparqNet/sparq-docs/tree/main/Sparq_en-US) repository. +For a more detailed explanation of the project's structure, check the [docs](https://github.com/AppLayer/sparq-docs/tree/main/Sparq_en-US) repository. ## Compiling -* Clone the project: `git clone https://github.com/SparqNet/orbitersdk-cpp +* Clone the project: `git clone https://github.com/AppLayer/bdk-cpp * Go to the project's root folder, create a "build" folder and change to it: - * `cd orbitersdk-cpp && mkdir build && cd build` + * `cd bdk-cpp && mkdir build && cd build` * Run `cmake` inside the build folder: `cmake ..` * Use `-DCMAKE_BUILD_TYPE={Debug,RelWithDebInfo,Release}` to set the respective debug/release builds (Debug by default) * Use `-DDEBUG=OFF` to build without debug flags (ON by default) * Use `-DUSE_LINT=ON` to run clang-tidy along the build (OFF by default, WILL TAKE SIGNIFICANTLY LONGER TO COMPILE) -* Build the executable: `cmake --build . -- -j$(nproc)` +* Build the executable: `cmake --build . -- -j$(nproc)` (adjust `-j$(nproc)` accordingly if needed) * If using the linter, pipe stderr to a file (e.g. `cmake --build . -- -j$(nproc) 2> log.txt`) ## Deploying @@ -124,17 +153,13 @@ The deployed chain will have the following information by default: Nodes are all deployed on the same machine, under the following ports and tmux sessions: -| Session Name | Node Type | P2P Port | HTTP Port | Validator Key | -|--------------------------|-----------|----------|-----------|--------------------------------------------------------------------| -| local_testnet_discovery | Discovery | 8080 | 8090 | XXXX | -| local_testnet_validator1 | Validator | 8081 | 8090 | 0xba5e6e9dd9cbd263969b94ee385d885c2d303dfc181db2a09f6bf19a7ba26759 | -| local_testnet_validator2 | Validator | 8082 | 8091 | 0xfd84d99aa18b474bf383e10925d82194f1b0ca268e7a339032679d6e3a201ad4 | -| local_testnet_validator3 | Validator | 8083 | 8092 | 0x66ce71abe0b8acd92cfd3965d6f9d80122aed9b0e9bdd3dbe018230bafde5751 | -| local_testnet_validator4 | Validator | 8084 | 8093 | 0x856aeb3b9c20a80d1520a2406875f405d336e09475f43c478eb4f0dafb765fe7 | -| local_testnet_validator5 | Validator | 8085 | 8094 | 0x81f288dd776f4edfe256d34af1f7d719f511559f19115af3e3d692e741faadc6 | -| local_testnet_normal1 | Normal | 8086 | 8095 | XXXX | -| local_testnet_normal2 | Normal | 8087 | 8096 | XXXX | -| local_testnet_normal3 | Normal | 8088 | 8097 | XXXX | -| local_testnet_normal4 | Normal | 8089 | 8098 | XXXX | -| local_testnet_normal5 | Normal | 8110 | 8099 | XXXX | -| local_testnet_normal6 | Normal | 8111 | 8100 | XXXX | +| Session Name | Node Type | P2P Port | HTTP Port | Validator Key | +|--------------------------|-----------|----------|-----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| local_testnet_discovery | Discovery | 8080 | 8090 | XXXX | +| local_testnet_validator1 | Validator | 8081 | 8090 | 0xba5e6e9dd9cbd263969b94ee385d885c2d303dfc181db2a09f6bf19a7ba26759, 0xfd84d99aa18b474bf383e10925d82194f1b0ca268e7a339032679d6e3a201ad4, 0xfd84d99aa18b474bf383e10925d82194f1b0ca268e7a339032679d6e3a201ad4, 0x856aeb3b9c20a80d1520a2406875f405d336e09475f43c478eb4f0dafb765fe7, 0x81f288dd776f4edfe256d34af1f7d719f511559f19115af3e3d692e741faadc6 | +| local_testnet_normal1 | Normal | 8086 | 8095 | XXXX | +| local_testnet_normal2 | Normal | 8087 | 8096 | XXXX | +| local_testnet_normal3 | Normal | 8088 | 8097 | XXXX | +| local_testnet_normal4 | Normal | 8089 | 8098 | XXXX | +| local_testnet_normal5 | Normal | 8110 | 8099 | XXXX | +| local_testnet_normal6 | Normal | 8111 | 8100 | XXXX | diff --git a/cmake/FindAbsl.cmake b/cmake/FindAbsl.cmake deleted file mode 100644 index bf039dfc..00000000 --- a/cmake/FindAbsl.cmake +++ /dev/null @@ -1,1378 +0,0 @@ -# Finds the Abseil (absl) libraries in the system. -# Custom-built/"copied-then-modded" from the original CMake config files -# (abslConfig.cmake, abslTargets.cmake, abslTargets-none.cmake). -# Those are hardcoded to link SHARED libraries, with no way to toggle to STATIC. -# This one links STATIC by default, and can be toggled with `set(ABSL_FIND_SHARED ON)`. -# WORKS ON LINUX PATHS ONLY. I won't bother porting to other systems. - -# Set up prefixes/suffixes and lib type -set(ABSL_INCLUDE_DIR "/usr/include") -set(ABSL_PATH_PREFIX "/usr/lib/x86_64-linux-gnu/") -if(ABSL_FIND_SHARED) - set(ABSL_LIB_TYPE SHARED) - set(ABSL_LIB_SUFFIX ".so") -else() - set(ABSL_LIB_TYPE STATIC) - set(ABSL_LIB_SUFFIX ".a") -endif() - -# Protect against multiple inclusion, which would fail when already imported targets are added once more. -set(_targetsDefined) -set(_targetsNotDefined) -set(_expectedTargets) -foreach(_expectedTarget absl::atomic_hook absl::errno_saver absl::log_severity absl::raw_logging_internal absl::spinlock_wait absl::config absl::dynamic_annotations absl::core_headers absl::malloc_internal absl::base_internal absl::base absl::throw_delegate absl::pretty_function absl::endian absl::bits absl::exponential_biased absl::periodic_sampler absl::scoped_set_env absl::strerror absl::fast_type_id absl::algorithm absl::algorithm_container absl::container absl::btree absl::compressed_tuple absl::fixed_array absl::inlined_vector_internal absl::inlined_vector absl::counting_allocator absl::flat_hash_map absl::flat_hash_set absl::node_hash_map absl::node_hash_set absl::container_memory absl::hash_function_defaults absl::hash_policy_traits absl::hashtablez_sampler absl::hashtable_debug absl::hashtable_debug_hooks absl::have_sse absl::node_hash_policy absl::raw_hash_map absl::container_common absl::raw_hash_set absl::layout absl::stacktrace absl::symbolize absl::examine_stack absl::failure_signal_handler absl::debugging_internal absl::demangle_internal absl::leak_check absl::leak_check_disable absl::debugging absl::flags_path_util absl::flags_program_name absl::flags_config absl::flags_marshalling absl::flags_commandlineflag_internal absl::flags_commandlineflag absl::flags_private_handle_accessor absl::flags_reflection absl::flags_internal absl::flags absl::flags_usage_internal absl::flags_usage absl::flags_parse absl::bind_front absl::function_ref absl::hash absl::city absl::memory absl::type_traits absl::meta absl::int128 absl::numeric absl::random_random absl::random_bit_gen_ref absl::random_internal_mock_helpers absl::random_distributions absl::random_seed_gen_exception absl::random_seed_sequences absl::random_internal_traits absl::random_internal_distribution_caller absl::random_internal_fast_uniform_bits absl::random_internal_seed_material absl::random_internal_pool_urbg absl::random_internal_salted_seed_seq absl::random_internal_iostream_state_saver absl::random_internal_generate_real absl::random_internal_wide_multiply absl::random_internal_fastmath absl::random_internal_nonsecure_base absl::random_internal_pcg_engine absl::random_internal_randen_engine absl::random_internal_platform absl::random_internal_randen absl::random_internal_randen_slow absl::random_internal_randen_hwaes absl::random_internal_randen_hwaes_impl absl::random_internal_distribution_test_util absl::random_internal_uniform_helper absl::status absl::statusor absl::strings absl::strings_internal absl::str_format absl::str_format_internal absl::cord absl::graphcycles_internal absl::kernel_timeout_internal absl::synchronization absl::time absl::civil_time absl::time_zone absl::any absl::bad_any_cast absl::bad_any_cast_impl absl::span absl::optional absl::bad_optional_access absl::bad_variant_access absl::variant absl::compare absl::utility) - list(APPEND _expectedTargets ${_expectedTarget}) - if(NOT TARGET ${_expectedTarget}) - list(APPEND _targetsNotDefined ${_expectedTarget}) - endif() - if(TARGET ${_expectedTarget}) - list(APPEND _targetsDefined ${_expectedTarget}) - endif() -endforeach() -if("${_targetsDefined}" STREQUAL "${_expectedTargets}") - unset(_targetsDefined) - unset(_targetsNotDefined) - unset(_expectedTargets) - set(CMAKE_IMPORT_FILE_VERSION) - cmake_policy(POP) - return() -endif() -if(NOT "${_targetsDefined}" STREQUAL "") - message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_targetsDefined}\nTargets not yet defined: ${_targetsNotDefined}\n") -endif() -unset(_targetsDefined) -unset(_targetsNotDefined) -unset(_expectedTargets) - -# Create imported target absl::atomic_hook -add_library(absl::atomic_hook INTERFACE IMPORTED) - -set_target_properties(absl::atomic_hook PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::core_headers;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::errno_saver -add_library(absl::errno_saver INTERFACE IMPORTED) - -set_target_properties(absl::errno_saver PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::log_severity -add_library(absl::log_severity ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::log_severity PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::core_headers" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_log_severity${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_log_severity${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::raw_logging_internal -add_library(absl::raw_logging_internal ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::raw_logging_internal PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::atomic_hook;absl::config;absl::core_headers;absl::log_severity" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_raw_logging_internal${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_raw_logging_internal${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::spinlock_wait -add_library(absl::spinlock_wait ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::spinlock_wait PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::base_internal;absl::core_headers;absl::errno_saver" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_spinlock_wait${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_spinlock_wait${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::config -add_library(absl::config INTERFACE IMPORTED) - -set_target_properties(absl::config PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::dynamic_annotations -add_library(absl::dynamic_annotations INTERFACE IMPORTED) - -set_target_properties(absl::dynamic_annotations PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::core_headers -add_library(absl::core_headers INTERFACE IMPORTED) - -set_target_properties(absl::core_headers PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::malloc_internal -add_library(absl::malloc_internal ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::malloc_internal PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::base;absl::base_internal;absl::config;absl::core_headers;absl::dynamic_annotations;absl::raw_logging_internal;Threads::Threads" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_malloc_internal${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_malloc_internal${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::base_internal -add_library(absl::base_internal INTERFACE IMPORTED) - -set_target_properties(absl::base_internal PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::type_traits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::base -add_library(absl::base ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::base PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::atomic_hook;absl::base_internal;absl::config;absl::core_headers;absl::dynamic_annotations;absl::log_severity;absl::raw_logging_internal;absl::spinlock_wait;absl::type_traits;Threads::Threads" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_base${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_base${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::throw_delegate -add_library(absl::throw_delegate ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::throw_delegate PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::raw_logging_internal" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_throw_delegate${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_throw_delegate${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::pretty_function -add_library(absl::pretty_function INTERFACE IMPORTED) - -set_target_properties(absl::pretty_function PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::endian -add_library(absl::endian INTERFACE IMPORTED) - -set_target_properties(absl::endian PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::base;absl::config;absl::core_headers;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::bits -add_library(absl::bits INTERFACE IMPORTED) - -set_target_properties(absl::bits PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::core_headers;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::exponential_biased -add_library(absl::exponential_biased ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::exponential_biased PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::core_headers" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_exponential_biased${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_exponential_biased${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::periodic_sampler -add_library(absl::periodic_sampler ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::periodic_sampler PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::core_headers;absl::exponential_biased" -) - -# Create imported target absl::scoped_set_env -add_library(absl::scoped_set_env ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::scoped_set_env PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::raw_logging_internal" -) - -# Create imported target absl::strerror -add_library(absl::strerror ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::strerror PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::core_headers;absl::errno_saver" -) - -# Create imported target absl::fast_type_id -add_library(absl::fast_type_id INTERFACE IMPORTED) - -set_target_properties(absl::fast_type_id PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::algorithm -add_library(absl::algorithm INTERFACE IMPORTED) - -set_target_properties(absl::algorithm PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::algorithm_container -add_library(absl::algorithm_container INTERFACE IMPORTED) - -set_target_properties(absl::algorithm_container PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::algorithm;absl::core_headers;absl::meta;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::container -add_library(absl::container INTERFACE IMPORTED) - -set_target_properties(absl::container PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::btree -add_library(absl::btree INTERFACE IMPORTED) - -set_target_properties(absl::btree PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::container_common;absl::compare;absl::compressed_tuple;absl::container_memory;absl::cord;absl::core_headers;absl::layout;absl::memory;absl::strings;absl::throw_delegate;absl::type_traits;absl::utility;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::compressed_tuple -add_library(absl::compressed_tuple INTERFACE IMPORTED) - -set_target_properties(absl::compressed_tuple PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::utility;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::fixed_array -add_library(absl::fixed_array INTERFACE IMPORTED) - -set_target_properties(absl::fixed_array PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::compressed_tuple;absl::algorithm;absl::config;absl::core_headers;absl::dynamic_annotations;absl::throw_delegate;absl::memory;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::inlined_vector_internal -add_library(absl::inlined_vector_internal INTERFACE IMPORTED) - -set_target_properties(absl::inlined_vector_internal PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::compressed_tuple;absl::core_headers;absl::memory;absl::span;absl::type_traits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::inlined_vector -add_library(absl::inlined_vector INTERFACE IMPORTED) - -set_target_properties(absl::inlined_vector PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::algorithm;absl::core_headers;absl::inlined_vector_internal;absl::throw_delegate;absl::memory;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::counting_allocator -add_library(absl::counting_allocator INTERFACE IMPORTED) - -set_target_properties(absl::counting_allocator PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::flat_hash_map -add_library(absl::flat_hash_map INTERFACE IMPORTED) - -set_target_properties(absl::flat_hash_map PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::container_memory;absl::hash_function_defaults;absl::raw_hash_map;absl::algorithm_container;absl::memory;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::flat_hash_set -add_library(absl::flat_hash_set INTERFACE IMPORTED) - -set_target_properties(absl::flat_hash_set PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::container_memory;absl::hash_function_defaults;absl::raw_hash_set;absl::algorithm_container;absl::core_headers;absl::memory;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::node_hash_map -add_library(absl::node_hash_map INTERFACE IMPORTED) - -set_target_properties(absl::node_hash_map PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::container_memory;absl::hash_function_defaults;absl::node_hash_policy;absl::raw_hash_map;absl::algorithm_container;absl::memory;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::node_hash_set -add_library(absl::node_hash_set INTERFACE IMPORTED) - -set_target_properties(absl::node_hash_set PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::hash_function_defaults;absl::node_hash_policy;absl::raw_hash_set;absl::algorithm_container;absl::memory;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::container_memory -add_library(absl::container_memory INTERFACE IMPORTED) - -set_target_properties(absl::container_memory PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::memory;absl::type_traits;absl::utility;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::hash_function_defaults -add_library(absl::hash_function_defaults INTERFACE IMPORTED) - -set_target_properties(absl::hash_function_defaults PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::cord;absl::hash;absl::strings;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::hash_policy_traits -add_library(absl::hash_policy_traits INTERFACE IMPORTED) - -set_target_properties(absl::hash_policy_traits PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::meta;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::hashtablez_sampler -add_library(absl::hashtablez_sampler ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::hashtablez_sampler PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::base;absl::exponential_biased;absl::have_sse;absl::synchronization" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_hashtablez_sampler${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_hashtablez_sampler${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::hashtable_debug -add_library(absl::hashtable_debug INTERFACE IMPORTED) - -set_target_properties(absl::hashtable_debug PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::hashtable_debug_hooks;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::hashtable_debug_hooks -add_library(absl::hashtable_debug_hooks INTERFACE IMPORTED) - -set_target_properties(absl::hashtable_debug_hooks PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::have_sse -add_library(absl::have_sse INTERFACE IMPORTED) - -set_target_properties(absl::have_sse PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::node_hash_policy -add_library(absl::node_hash_policy INTERFACE IMPORTED) - -set_target_properties(absl::node_hash_policy PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::raw_hash_map -add_library(absl::raw_hash_map INTERFACE IMPORTED) - -set_target_properties(absl::raw_hash_map PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::container_memory;absl::raw_hash_set;absl::throw_delegate;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::container_common -add_library(absl::container_common INTERFACE IMPORTED) - -set_target_properties(absl::container_common PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::type_traits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::raw_hash_set -add_library(absl::raw_hash_set ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::raw_hash_set PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::bits;absl::compressed_tuple;absl::config;absl::container_common;absl::container_memory;absl::core_headers;absl::endian;absl::hash_policy_traits;absl::hashtable_debug_hooks;absl::have_sse;absl::layout;absl::memory;absl::meta;absl::optional;absl::utility;absl::hashtablez_sampler" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_raw_hash_set${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_raw_hash_set${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::layout -add_library(absl::layout INTERFACE IMPORTED) - -set_target_properties(absl::layout PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::core_headers;absl::meta;absl::strings;absl::span;absl::utility;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::stacktrace -add_library(absl::stacktrace ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::stacktrace PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::debugging_internal;absl::config;absl::core_headers" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_stacktrace${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_stacktrace${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::symbolize -add_library(absl::symbolize ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::symbolize PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::debugging_internal;absl::demangle_internal;absl::base;absl::config;absl::core_headers;absl::dynamic_annotations;absl::malloc_internal;absl::raw_logging_internal;absl::strings" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_symbolize${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_symbolize${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::examine_stack -add_library(absl::examine_stack ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::examine_stack PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::stacktrace;absl::symbolize;absl::config;absl::core_headers;absl::raw_logging_internal" -) - -# Create imported target absl::failure_signal_handler -add_library(absl::failure_signal_handler ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::failure_signal_handler PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::examine_stack;absl::stacktrace;absl::base;absl::config;absl::core_headers;absl::errno_saver;absl::raw_logging_internal" -) - -# Create imported target absl::debugging_internal -add_library(absl::debugging_internal ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::debugging_internal PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::core_headers;absl::config;absl::dynamic_annotations;absl::errno_saver;absl::raw_logging_internal" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_debugging_internal${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_debugging_internal${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::demangle_internal -add_library(absl::demangle_internal ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::demangle_internal PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::base;absl::core_headers" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_demangle_internal${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_demangle_internal${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::leak_check -add_library(absl::leak_check ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::leak_check PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::core_headers" -) - -# Create imported target absl::leak_check_disable -add_library(absl::leak_check_disable ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::leak_check_disable PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} -) - -# Create imported target absl::debugging -add_library(absl::debugging INTERFACE IMPORTED) - -set_target_properties(absl::debugging PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::stacktrace;absl::leak_check;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::flags_path_util -add_library(absl::flags_path_util INTERFACE IMPORTED) - -set_target_properties(absl::flags_path_util PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::strings;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::flags_program_name -add_library(absl::flags_program_name ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::flags_program_name PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::core_headers;absl::flags_path_util;absl::strings;absl::synchronization" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_flags_program_name${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_flags_program_name${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::flags_config -add_library(absl::flags_config ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::flags_config PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::flags_path_util;absl::flags_program_name;absl::core_headers;absl::strings;absl::synchronization" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_flags_config${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_flags_config${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::flags_marshalling -add_library(absl::flags_marshalling ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::flags_marshalling PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::core_headers;absl::log_severity;absl::strings;absl::str_format" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_flags_marshalling${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_flags_marshalling${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::flags_commandlineflag_internal -add_library(absl::flags_commandlineflag_internal ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::flags_commandlineflag_internal PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::fast_type_id" - IMPORTED_LOCATION - "${ABSL_PATH_PREFIX}libabsl_flags_commandlineflag_internal${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_flags_commandlineflag_internal${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::flags_commandlineflag -add_library(absl::flags_commandlineflag ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::flags_commandlineflag PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::fast_type_id;absl::flags_commandlineflag_internal;absl::optional;absl::strings" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_flags_commandlineflag${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_flags_commandlineflag${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::flags_private_handle_accessor -add_library(absl::flags_private_handle_accessor ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::flags_private_handle_accessor PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::flags_commandlineflag;absl::flags_commandlineflag_internal;absl::strings" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_flags_private_handle_accessor${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_flags_private_handle_accessor${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::flags_reflection -add_library(absl::flags_reflection ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::flags_reflection PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::flags_commandlineflag;absl::flags_private_handle_accessor;absl::flags_config;absl::strings;absl::synchronization;absl::flat_hash_map" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_flags_reflection${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_flags_reflection${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::flags_internal -add_library(absl::flags_internal ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::flags_internal PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::base;absl::config;absl::flags_commandlineflag;absl::flags_commandlineflag_internal;absl::flags_config;absl::flags_marshalling;absl::synchronization;absl::meta;absl::utility" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_flags_internal${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_flags_internal${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::flags -add_library(absl::flags ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::flags PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::flags_commandlineflag;absl::flags_config;absl::flags_internal;absl::flags_reflection;absl::base;absl::core_headers;absl::strings" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_flags${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_flags${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::flags_usage_internal -add_library(absl::flags_usage_internal ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::flags_usage_internal PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::flags_config;absl::flags;absl::flags_commandlineflag;absl::flags_internal;absl::flags_path_util;absl::flags_private_handle_accessor;absl::flags_program_name;absl::flags_reflection;absl::strings;absl::synchronization" -) - -# Create imported target absl::flags_usage -add_library(absl::flags_usage ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::flags_usage PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::core_headers;absl::flags_usage_internal;absl::strings;absl::synchronization" -) - -# Create imported target absl::flags_parse -add_library(absl::flags_parse ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::flags_parse PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::core_headers;absl::flags_config;absl::flags;absl::flags_commandlineflag;absl::flags_commandlineflag_internal;absl::flags_internal;absl::flags_private_handle_accessor;absl::flags_program_name;absl::flags_reflection;absl::flags_usage;absl::strings;absl::synchronization" -) - -# Create imported target absl::bind_front -add_library(absl::bind_front INTERFACE IMPORTED) - -set_target_properties(absl::bind_front PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::base_internal;absl::compressed_tuple;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::function_ref -add_library(absl::function_ref INTERFACE IMPORTED) - -set_target_properties(absl::function_ref PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::base_internal;absl::meta;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::hash -add_library(absl::hash ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::hash PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::core_headers;absl::endian;absl::fixed_array;absl::meta;absl::int128;absl::strings;absl::optional;absl::variant;absl::utility;absl::city" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_hash${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_hash${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::city -add_library(absl::city ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::city PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::core_headers;absl::endian" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_city${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_city${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::memory -add_library(absl::memory INTERFACE IMPORTED) - -set_target_properties(absl::memory PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::core_headers;absl::meta;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::type_traits -add_library(absl::type_traits INTERFACE IMPORTED) - -set_target_properties(absl::type_traits PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::meta -add_library(absl::meta INTERFACE IMPORTED) - -set_target_properties(absl::meta PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::type_traits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::int128 -add_library(absl::int128 ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::int128 PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::bits;absl::config;absl::core_headers" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_int128${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_int128${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::numeric -add_library(absl::numeric INTERFACE IMPORTED) - -set_target_properties(absl::numeric PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::int128;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::random_random -add_library(absl::random_random INTERFACE IMPORTED) - -set_target_properties(absl::random_random PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::random_distributions;absl::random_internal_nonsecure_base;absl::random_internal_pcg_engine;absl::random_internal_pool_urbg;absl::random_internal_randen_engine;absl::random_seed_sequences;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::random_bit_gen_ref -add_library(absl::random_bit_gen_ref INTERFACE IMPORTED) - -set_target_properties(absl::random_bit_gen_ref PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::core_headers;absl::random_internal_distribution_caller;absl::random_internal_fast_uniform_bits;absl::type_traits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::random_internal_mock_helpers -add_library(absl::random_internal_mock_helpers INTERFACE IMPORTED) - -set_target_properties(absl::random_internal_mock_helpers PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::fast_type_id;absl::optional;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::random_distributions -add_library(absl::random_distributions ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::random_distributions PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::base_internal;absl::config;absl::core_headers;absl::random_internal_generate_real;absl::random_internal_distribution_caller;absl::random_internal_fast_uniform_bits;absl::random_internal_fastmath;absl::random_internal_iostream_state_saver;absl::random_internal_traits;absl::random_internal_uniform_helper;absl::random_internal_wide_multiply;absl::strings;absl::type_traits" -) - -# Create imported target absl::random_seed_gen_exception -add_library(absl::random_seed_gen_exception ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::random_seed_gen_exception PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config" -) - -# Create imported target absl::random_seed_sequences -add_library(absl::random_seed_sequences ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::random_seed_sequences PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::inlined_vector;absl::random_internal_nonsecure_base;absl::random_internal_pool_urbg;absl::random_internal_salted_seed_seq;absl::random_internal_seed_material;absl::random_seed_gen_exception;absl::span" -) - -# Create imported target absl::random_internal_traits -add_library(absl::random_internal_traits INTERFACE IMPORTED) - -set_target_properties(absl::random_internal_traits PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::random_internal_distribution_caller -add_library(absl::random_internal_distribution_caller INTERFACE IMPORTED) - -set_target_properties(absl::random_internal_distribution_caller PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::utility;absl::fast_type_id;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::random_internal_fast_uniform_bits -add_library(absl::random_internal_fast_uniform_bits INTERFACE IMPORTED) - -set_target_properties(absl::random_internal_fast_uniform_bits PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::random_internal_seed_material -add_library(absl::random_internal_seed_material ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::random_internal_seed_material PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::core_headers;absl::optional;absl::random_internal_fast_uniform_bits;absl::raw_logging_internal;absl::span;absl::strings" -) - -# Create imported target absl::random_internal_pool_urbg -add_library(absl::random_internal_pool_urbg ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::random_internal_pool_urbg PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::base;absl::config;absl::core_headers;absl::endian;absl::random_internal_randen;absl::random_internal_seed_material;absl::random_internal_traits;absl::random_seed_gen_exception;absl::raw_logging_internal;absl::span" -) - -# Create imported target absl::random_internal_salted_seed_seq -add_library(absl::random_internal_salted_seed_seq INTERFACE IMPORTED) - -set_target_properties(absl::random_internal_salted_seed_seq PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::inlined_vector;absl::optional;absl::span;absl::random_internal_seed_material;absl::type_traits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::random_internal_iostream_state_saver -add_library(absl::random_internal_iostream_state_saver INTERFACE IMPORTED) - -set_target_properties(absl::random_internal_iostream_state_saver PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::int128;absl::type_traits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::random_internal_generate_real -add_library(absl::random_internal_generate_real INTERFACE IMPORTED) - -set_target_properties(absl::random_internal_generate_real PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::bits;absl::random_internal_fastmath;absl::random_internal_traits;absl::type_traits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::random_internal_wide_multiply -add_library(absl::random_internal_wide_multiply INTERFACE IMPORTED) - -set_target_properties(absl::random_internal_wide_multiply PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::bits;absl::config;absl::int128;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::random_internal_fastmath -add_library(absl::random_internal_fastmath INTERFACE IMPORTED) - -set_target_properties(absl::random_internal_fastmath PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::bits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::random_internal_nonsecure_base -add_library(absl::random_internal_nonsecure_base INTERFACE IMPORTED) - -set_target_properties(absl::random_internal_nonsecure_base PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::core_headers;absl::optional;absl::random_internal_pool_urbg;absl::random_internal_salted_seed_seq;absl::random_internal_seed_material;absl::span;absl::type_traits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::random_internal_pcg_engine -add_library(absl::random_internal_pcg_engine INTERFACE IMPORTED) - -set_target_properties(absl::random_internal_pcg_engine PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::int128;absl::random_internal_fastmath;absl::random_internal_iostream_state_saver;absl::type_traits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::random_internal_randen_engine -add_library(absl::random_internal_randen_engine INTERFACE IMPORTED) - -set_target_properties(absl::random_internal_randen_engine PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::endian;absl::random_internal_iostream_state_saver;absl::random_internal_randen;absl::raw_logging_internal;absl::type_traits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::random_internal_platform -add_library(absl::random_internal_platform ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::random_internal_platform PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config" -) - -# Create imported target absl::random_internal_randen -add_library(absl::random_internal_randen ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::random_internal_randen PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::random_internal_platform;absl::random_internal_randen_hwaes;absl::random_internal_randen_slow" -) - -# Create imported target absl::random_internal_randen_slow -add_library(absl::random_internal_randen_slow ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::random_internal_randen_slow PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::random_internal_platform;absl::config" -) - -# Create imported target absl::random_internal_randen_hwaes -add_library(absl::random_internal_randen_hwaes ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::random_internal_randen_hwaes PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::random_internal_platform;absl::random_internal_randen_hwaes_impl;absl::config" -) - -# Create imported target absl::random_internal_randen_hwaes_impl -add_library(absl::random_internal_randen_hwaes_impl ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::random_internal_randen_hwaes_impl PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::random_internal_platform;absl::config" -) - -# Create imported target absl::random_internal_distribution_test_util -add_library(absl::random_internal_distribution_test_util ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::random_internal_distribution_test_util PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::core_headers;absl::raw_logging_internal;absl::strings;absl::str_format;absl::span" -) - -# Create imported target absl::random_internal_uniform_helper -add_library(absl::random_internal_uniform_helper INTERFACE IMPORTED) - -set_target_properties(absl::random_internal_uniform_helper PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::random_internal_traits;absl::type_traits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::status -add_library(absl::status ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::status PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::atomic_hook;absl::config;absl::core_headers;absl::raw_logging_internal;absl::inlined_vector;absl::stacktrace;absl::symbolize;absl::strings;absl::cord;absl::str_format;absl::optional" -) - -# Create imported target absl::statusor -add_library(absl::statusor ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::statusor PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::status;absl::core_headers;absl::raw_logging_internal;absl::type_traits;absl::strings;absl::utility;absl::variant" -) - -# Create imported target absl::strings -add_library(absl::strings ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::strings PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::strings_internal;absl::base;absl::bits;absl::config;absl::core_headers;absl::endian;absl::int128;absl::memory;absl::raw_logging_internal;absl::throw_delegate;absl::type_traits" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_strings${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_strings${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::strings_internal -add_library(absl::strings_internal ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::strings_internal PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::core_headers;absl::endian;absl::raw_logging_internal;absl::type_traits" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_strings_internal${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_strings_internal${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::str_format -add_library(absl::str_format INTERFACE IMPORTED) - -set_target_properties(absl::str_format PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::str_format_internal;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::str_format_internal -add_library(absl::str_format_internal ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::str_format_internal PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::bits;absl::strings;absl::config;absl::core_headers;absl::type_traits;absl::int128;absl::span" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_str_format_internal${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_str_format_internal${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::cord -add_library(absl::cord ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::cord PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::base;absl::base_internal;absl::compressed_tuple;absl::core_headers;absl::endian;absl::fixed_array;absl::function_ref;absl::inlined_vector;absl::optional;absl::raw_logging_internal;absl::strings;absl::strings_internal;absl::type_traits" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_cord${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_cord${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::graphcycles_internal -add_library(absl::graphcycles_internal ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::graphcycles_internal PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::base;absl::base_internal;absl::config;absl::core_headers;absl::malloc_internal;absl::raw_logging_internal" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_graphcycles_internal${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_graphcycles_internal${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::kernel_timeout_internal -add_library(absl::kernel_timeout_internal INTERFACE IMPORTED) - -set_target_properties(absl::kernel_timeout_internal PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::core_headers;absl::raw_logging_internal;absl::time;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::synchronization -add_library(absl::synchronization ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::synchronization PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::graphcycles_internal;absl::kernel_timeout_internal;absl::atomic_hook;absl::base;absl::base_internal;absl::config;absl::core_headers;absl::dynamic_annotations;absl::malloc_internal;absl::raw_logging_internal;absl::stacktrace;absl::symbolize;absl::time;Threads::Threads" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_synchronization${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_synchronization${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::time -add_library(absl::time ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::time PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::base;absl::civil_time;absl::core_headers;absl::int128;absl::raw_logging_internal;absl::strings;absl::time_zone" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_time${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_time${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::civil_time -add_library(absl::civil_time ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::civil_time PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_civil_time${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_civil_time${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::time_zone -add_library(absl::time_zone ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::time_zone PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "\$<\$:>" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_time_zone${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_time_zone${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::any -add_library(absl::any INTERFACE IMPORTED) - -set_target_properties(absl::any PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::bad_any_cast;absl::config;absl::core_headers;absl::fast_type_id;absl::type_traits;absl::utility;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::bad_any_cast -add_library(absl::bad_any_cast INTERFACE IMPORTED) - -set_target_properties(absl::bad_any_cast PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::bad_any_cast_impl;absl::config;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::bad_any_cast_impl -add_library(absl::bad_any_cast_impl ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::bad_any_cast_impl PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::raw_logging_internal" -) - -# Create imported target absl::span -add_library(absl::span INTERFACE IMPORTED) - -set_target_properties(absl::span PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::algorithm;absl::core_headers;absl::throw_delegate;absl::type_traits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::optional -add_library(absl::optional INTERFACE IMPORTED) - -set_target_properties(absl::optional PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::bad_optional_access;absl::base_internal;absl::config;absl::core_headers;absl::memory;absl::type_traits;absl::utility;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::bad_optional_access -add_library(absl::bad_optional_access ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::bad_optional_access PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::raw_logging_internal" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_bad_optional_access${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_bad_optional_access${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::bad_variant_access -add_library(absl::bad_variant_access ${ABSL_LIB_TYPE} IMPORTED) - -set_target_properties(absl::bad_variant_access PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::config;absl::raw_logging_internal" - IMPORTED_LOCATION "${ABSL_PATH_PREFIX}libabsl_bad_variant_access${ABSL_LIB_SUFFIX}" - IMPORTED_SONAME "libabsl_bad_variant_access${ABSL_LIB_SUFFIX}" -) - -# Create imported target absl::variant -add_library(absl::variant INTERFACE IMPORTED) - -set_target_properties(absl::variant PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::bad_variant_access;absl::base_internal;absl::config;absl::core_headers;absl::type_traits;absl::utility;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::compare -add_library(absl::compare INTERFACE IMPORTED) - -set_target_properties(absl::compare PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::core_headers;absl::type_traits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Create imported target absl::utility -add_library(absl::utility INTERFACE IMPORTED) - -set_target_properties(absl::utility PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${ABSL_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "absl::base_internal;absl::config;absl::type_traits;-Wl,--as-needed;-latomic;-Wl,--no-as-needed" -) - -# Get all library paths for checking if they exist -list(APPEND _IMPORT_CHECK_TARGETS absl::log_severity) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::log_severity - "${ABSL_PATH_PREFIX}libabsl_log_severity${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::raw_logging_internal) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::raw_logging_internal - "${ABSL_PATH_PREFIX}libabsl_raw_logging_internal${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::spinlock_wait) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::spinlock_wait - "${ABSL_PATH_PREFIX}libabsl_spinlock_wait${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::malloc_internal) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::malloc_internal - "${ABSL_PATH_PREFIX}libabsl_malloc_internal${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::base) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::base - "${ABSL_PATH_PREFIX}libabsl_base${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::throw_delegate) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::throw_delegate - "${ABSL_PATH_PREFIX}libabsl_throw_delegate${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::exponential_biased) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::exponential_biased - "${ABSL_PATH_PREFIX}libabsl_exponential_biased${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::periodic_sampler) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::periodic_sampler - "${ABSL_PATH_PREFIX}libabsl_periodic_sampler${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::scoped_set_env) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::scoped_set_env - "${ABSL_PATH_PREFIX}libabsl_scoped_set_env${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::strerror) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::strerror - "${ABSL_PATH_PREFIX}libabsl_strerror${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::hashtablez_sampler) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::hashtablez_sampler - "${ABSL_PATH_PREFIX}libabsl_hashtablez_sampler${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::raw_hash_set) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::raw_hash_set - "${ABSL_PATH_PREFIX}libabsl_raw_hash_set${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::stacktrace) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::stacktrace - "${ABSL_PATH_PREFIX}libabsl_stacktrace${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::symbolize) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::symbolize - "${ABSL_PATH_PREFIX}libabsl_symbolize${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::examine_stack) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::examine_stack - "${ABSL_PATH_PREFIX}libabsl_examine_stack${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::failure_signal_handler) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::failure_signal_handler - "${ABSL_PATH_PREFIX}libabsl_failure_signal_handler${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::debugging_internal) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::debugging_internal - "${ABSL_PATH_PREFIX}libabsl_debugging_internal${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::demangle_internal) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::demangle_internal - "${ABSL_PATH_PREFIX}libabsl_demangle_internal${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::leak_check) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::leak_check - "${ABSL_PATH_PREFIX}libabsl_leak_check${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::leak_check_disable) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::leak_check_disable - "${ABSL_PATH_PREFIX}libabsl_leak_check_disable${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::flags_program_name) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::flags_program_name - "${ABSL_PATH_PREFIX}libabsl_flags_program_name${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::flags_config) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::flags_config - "${ABSL_PATH_PREFIX}libabsl_flags_config${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::flags_marshalling) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::flags_marshalling - "${ABSL_PATH_PREFIX}libabsl_flags_marshalling${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::flags_commandlineflag_internal) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::flags_commandlineflag_internal - "${ABSL_PATH_PREFIX}libabsl_flags_commandlineflag_internal${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::flags_commandlineflag) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::flags_commandlineflag - "${ABSL_PATH_PREFIX}libabsl_flags_commandlineflag${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::flags_private_handle_accessor) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::flags_private_handle_accessor - "${ABSL_PATH_PREFIX}libabsl_flags_private_handle_accessor${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::flags_reflection) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::flags_reflection - "${ABSL_PATH_PREFIX}libabsl_flags_reflection${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::flags_internal) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::flags_internal - "${ABSL_PATH_PREFIX}libabsl_flags_internal${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::flags) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::flags - "${ABSL_PATH_PREFIX}libabsl_flags${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::flags_usage_internal) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::flags_usage_internal - "${ABSL_PATH_PREFIX}libabsl_flags_usage_internal${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::flags_usage) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::flags_usage - "${ABSL_PATH_PREFIX}libabsl_flags_usage${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::flags_parse) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::flags_parse - "${ABSL_PATH_PREFIX}libabsl_flags_parse${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::hash) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::hash - "${ABSL_PATH_PREFIX}libabsl_hash${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::city) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::city - "${ABSL_PATH_PREFIX}libabsl_city${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::int128) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::int128 - "${ABSL_PATH_PREFIX}libabsl_int128${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::random_distributions) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::random_distributions - "${ABSL_PATH_PREFIX}libabsl_random_distributions${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::random_seed_gen_exception) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::random_seed_gen_exception - "${ABSL_PATH_PREFIX}libabsl_random_seed_gen_exception${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::random_seed_sequences) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::random_seed_sequences - "${ABSL_PATH_PREFIX}libabsl_random_seed_sequences${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::random_internal_seed_material) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::random_internal_seed_material - "${ABSL_PATH_PREFIX}libabsl_random_internal_seed_material${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::random_internal_pool_urbg) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::random_internal_pool_urbg - "${ABSL_PATH_PREFIX}libabsl_random_internal_pool_urbg${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::random_internal_platform) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::random_internal_platform - "${ABSL_PATH_PREFIX}libabsl_random_internal_platform${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::random_internal_randen) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::random_internal_randen - "${ABSL_PATH_PREFIX}libabsl_random_internal_randen${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::random_internal_randen_slow) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::random_internal_randen_slow - "${ABSL_PATH_PREFIX}libabsl_random_internal_randen_slow${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::random_internal_randen_hwaes) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::random_internal_randen_hwaes - "${ABSL_PATH_PREFIX}libabsl_random_internal_randen_hwaes${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::random_internal_randen_hwaes_impl) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::random_internal_randen_hwaes_impl - "${ABSL_PATH_PREFIX}libabsl_random_internal_randen_hwaes_impl${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::random_internal_distribution_test_util) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::random_internal_distribution_test_util - "${ABSL_PATH_PREFIX}libabsl_random_internal_distribution_test_util${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::status) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::status - "${ABSL_PATH_PREFIX}libabsl_status${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::statusor) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::statusor - "${ABSL_PATH_PREFIX}libabsl_statusor${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::strings) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::strings - "${ABSL_PATH_PREFIX}libabsl_strings${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::strings_internal) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::strings_internal - "${ABSL_PATH_PREFIX}libabsl_strings_internal${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::str_format_internal) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::str_format_internal - "${ABSL_PATH_PREFIX}libabsl_str_format_internal${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::cord) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::cord - "${ABSL_PATH_PREFIX}libabsl_cord${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::graphcycles_internal) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::graphcycles_internal - "${ABSL_PATH_PREFIX}libabsl_graphcycles_internal${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::synchronization) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::synchronization - "${ABSL_PATH_PREFIX}libabsl_synchronization${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::time) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::time - "${ABSL_PATH_PREFIX}libabsl_time${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::civil_time) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::civil_time - "${ABSL_PATH_PREFIX}libabsl_civil_time${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::time_zone) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::time_zone - "${ABSL_PATH_PREFIX}libabsl_time_zone${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::bad_any_cast_impl) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::bad_any_cast_impl - "${ABSL_PATH_PREFIX}libabsl_bad_any_cast_impl${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::bad_optional_access) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::bad_optional_access - "${ABSL_PATH_PREFIX}libabsl_bad_optional_access${ABSL_LIB_SUFFIX}" -) -list(APPEND _IMPORT_CHECK_TARGETS absl::bad_variant_access) -list(APPEND _IMPORT_CHECK_FILES_FOR_absl::bad_variant_access - "${ABSL_PATH_PREFIX}libabsl_bad_variant_access${ABSL_LIB_SUFFIX}" -) - -# Loop over all imported files and verify that they actually exist -foreach(target ${_IMPORT_CHECK_TARGETS} ) - foreach(file ${_IMPORT_CHECK_FILES_FOR_${target}} ) - if(NOT EXISTS "${file}" ) - message(FATAL_ERROR "The imported target \"${target}\" references the file - \"${file}\" -but this file does not exist. Possible reasons include: -* The file was deleted, renamed, or moved to another location. -* An install or uninstall procedure did not complete successfully. -* The installation package was faulty and contained - \"${CMAKE_CURRENT_LIST_FILE}\" -but not all the files it references. -") - endif() - endforeach() - unset(_IMPORT_CHECK_FILES_FOR_${target}) -endforeach() -unset(_IMPORT_CHECK_TARGETS) - diff --git a/cmake/FindCares.cmake b/cmake/FindCares.cmake index 97beb316..5122c420 100644 --- a/cmake/FindCares.cmake +++ b/cmake/FindCares.cmake @@ -7,7 +7,7 @@ include(SelectLibraryConfigurations) include(FindPackageHandleStandardArgs) find_path(CARES_INCLUDE_DIR NAMES ares.h) -find_library(CARES_LIBRARY NAMES libcares.a) +find_library(CARES_LIBRARY NAMES libcares_static.a libcares.a) SELECT_LIBRARY_CONFIGURATIONS(Cares) diff --git a/cmake/FindEthash.cmake b/cmake/FindEthash.cmake new file mode 100644 index 00000000..d04eed65 --- /dev/null +++ b/cmake/FindEthash.cmake @@ -0,0 +1,20 @@ +# Find the Ethash libraries and define the following variables: +# ETHASH_FOUND +# ETHASH_INCLUDE_DIR +# ETHASH_LIBRARY + +include(SelectLibraryConfigurations) +include(FindPackageHandleStandardArgs) + +find_path(ETHASH_INCLUDE_DIR NAMES ethash.h PATH_SUFFIXES ethash) +find_library(ETHASH_LIBRARY NAMES libethash.a) + +SELECT_LIBRARY_CONFIGURATIONS(Ethash) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS( + Ethash DEFAULT_MSG + ETHASH_LIBRARY ETHASH_INCLUDE_DIR +) + +mark_as_advanced(ETHASH_INCLUDE_DIR ETHASH_LIBRARY) + diff --git a/cmake/FindEvmc.cmake b/cmake/FindEvmc.cmake new file mode 100644 index 00000000..56e606ce --- /dev/null +++ b/cmake/FindEvmc.cmake @@ -0,0 +1,22 @@ +# Find the EVMC libraries and define the following variables: +# EVMC_FOUND +# EVMC_INCLUDE_DIR +# EVMC_INSTRUCTIONS_LIBRARY +# EVMC_LOADER_LIBRARY + +include(SelectLibraryConfigurations) +include(FindPackageHandleStandardArgs) + +find_path(EVMC_INCLUDE_DIR NAMES evmc.h PATH_SUFFIXES evmc) +find_library(EVMC_INSTRUCTIONS_LIBRARY NAMES libevmc-instructions.a) +find_library(EVMC_LOADER_LIBRARY NAMES libevmc-loader.a) + +SELECT_LIBRARY_CONFIGURATIONS(Evmc) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS( + Evmc DEFAULT_MSG + EVMC_INSTRUCTIONS_LIBRARY EVMC_LOADER_LIBRARY EVMC_INCLUDE_DIR +) + +mark_as_advanced(EVMC_INCLUDE_DIR EVMC_INSTRUCTIONS_LIBRARY EVMC_LOADER_LIBRARY) + diff --git a/cmake/FindEvmone.cmake b/cmake/FindEvmone.cmake new file mode 100644 index 00000000..50c71f85 --- /dev/null +++ b/cmake/FindEvmone.cmake @@ -0,0 +1,20 @@ +# Find the EVMOne library and define the following variables: +# EVMONE_FOUND +# EVMONE_INCLUDE_DIR +# EVMONE_LIBRARY + +include(SelectLibraryConfigurations) +include(FindPackageHandleStandardArgs) + +find_path(EVMONE_INCLUDE_DIR NAMES evmone.h PATH_SUFFIXES evmone) +find_library(EVMONE_LIBRARY NAMES libevmone.a) + +SELECT_LIBRARY_CONFIGURATIONS(Evmone) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS( + Evmone DEFAULT_MSG + EVMONE_LIBRARY EVMONE_INCLUDE_DIR +) + +mark_as_advanced(EVMONE_INCLUDE_DIR EVMONE_LIBRARY) + diff --git a/cmake/FindKeccak.cmake b/cmake/FindKeccak.cmake new file mode 100644 index 00000000..4a81c826 --- /dev/null +++ b/cmake/FindKeccak.cmake @@ -0,0 +1,20 @@ +# Find the Keccak libraries and define the following variables: +# KECCAK_FOUND +# KECCAK_INCLUDE_DIR +# KECCAK_LIBRARY + +include(SelectLibraryConfigurations) +include(FindPackageHandleStandardArgs) + +find_path(KECCAK_INCLUDE_DIR NAMES keccak.h PATH_SUFFIXES ethash) +find_library(KECCAK_LIBRARY NAMES libkeccak.a) + +SELECT_LIBRARY_CONFIGURATIONS(Keccak) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS( + Keccak DEFAULT_MSG + KECCAK_LIBRARY KECCAK_INCLUDE_DIR +) + +mark_as_advanced(KECCAK_INCLUDE_DIR KECCAK_LIBRARY) + diff --git a/cmake/FindSecp256k1.cmake b/cmake/FindSecp256k1.cmake new file mode 100644 index 00000000..835ee5dd --- /dev/null +++ b/cmake/FindSecp256k1.cmake @@ -0,0 +1,20 @@ +# Find the secp256k1 library and define the following variables: +# SECP256K1_FOUND +# SECP256K1_INCLUDE_DIR +# SECP256K1_LIBRARY + +include(SelectLibraryConfigurations) +include(FindPackageHandleStandardArgs) + +find_path(SECP256K1_INCLUDE_DIR NAMES secp256k1.h) +find_library(SECP256K1_LIBRARY NAMES libsecp256k1.a) + +SELECT_LIBRARY_CONFIGURATIONS(Secp256k1) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS( + Secp256k1 DEFAULT_MSG + SECP256K1_LIBRARY SECP256K1_INCLUDE_DIR +) + +mark_as_advanced(SECP256K1_INCLUDE_DIR SECP256K1_LIBRARY) + diff --git a/cmake/FindSpeedb.cmake b/cmake/FindSpeedb.cmake new file mode 100644 index 00000000..cb2959af --- /dev/null +++ b/cmake/FindSpeedb.cmake @@ -0,0 +1,20 @@ +# Find the Speedb library and define the following variables: +# SPEEDB_FOUND +# SPEEDB_INCLUDE_DIR +# SPEEDB_LIBRARY + +include(SelectLibraryConfigurations) +include(FindPackageHandleStandardArgs) + +find_path(SPEEDB_INCLUDE_DIR NAMES db.h PATH_SUFFIXES rocksdb) +find_library(SPEEDB_LIBRARY NAMES libspeedb.a) + +SELECT_LIBRARY_CONFIGURATIONS(Speedb) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS( + Speedb DEFAULT_MSG + SPEEDB_LIBRARY SPEEDB_INCLUDE_DIR +) + +mark_as_advanced(SPEEDB_INCLUDE_DIR SPEEDB_LIBRARY) + diff --git a/cmake/ProjectBoostCertify.cmake b/cmake/ProjectBoostCertify.cmake deleted file mode 100644 index 89f610a0..00000000 --- a/cmake/ProjectBoostCertify.cmake +++ /dev/null @@ -1,39 +0,0 @@ -include(ExternalProject) - -if (MSVC) - set(_only_release_configuration -DCMAKE_CONFIGURATION_TYPES=Release) - set(_overwrite_install_command INSTALL_COMMAND cmake --build --config Release --target install) -endif() - -set(prefix "${CMAKE_BINARY_DIR}/deps") -set(CERTIFY_LIBRARY "${prefix}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}certify${CMAKE_STATIC_LIBRARY_SUFFIX}") -set(CERTIFY_INCLUDE_DIR "${prefix}/include") - -ExternalProject_Add( - certify - PREFIX "${prefix}" - URL https://github.com/djarek/certify/archive/97f5eebfd99a5d6e99d07e4820240994e4e59787.tar.gz - URL_HASH SHA256=1c964b0aba47cd90081eaacc4946ea8e58d0c14fb267856f26515219e8ca1d68 - PATCH_COMMAND patch -p1 < ${CMAKE_CURRENT_SOURCE_DIR}/cmake/certifyPatch.patch - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= - -DCMAKE_POSITION_INDEPENDENT_CODE=${BUILD_SHARED_LIBS} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} - -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} - ${_only_release_configuration} - ${_windows_configuration} - -DCMAKE_INSTALL_LIBDIR=lib - LOG_CONFIGURE 1 - ${_overwrite_install_command} - LOG_INSTALL 1 - BUILD_BYPRODUCTS "${CERTIFY_BYPRODUCTS}" -) - -# Create imported library -add_library(Certify STATIC IMPORTED) -file(MAKE_DIRECTORY "${CERTIFY_INCLUDE_DIR}") # Must exist. -set_property(TARGET Certify PROPERTY IMPORTED_CONFIGURATIONS Release) -set_property(TARGET Certify PROPERTY IMPORTED_LOCATION_RELEASE "${CERTIFY_LIBRARY}") -set_property(TARGET Certify PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CERTIFY_INCLUDE_DIR}") -add_dependencies(Certify certify ${CERTIFY_BYPRODUCTS}) diff --git a/cmake/ProjectEthash.cmake b/cmake/ProjectEthash.cmake deleted file mode 100644 index 55f99dc6..00000000 --- a/cmake/ProjectEthash.cmake +++ /dev/null @@ -1,42 +0,0 @@ -include(ExternalProject) - -if (MSVC) - set(_only_release_configuration -DCMAKE_CONFIGURATION_TYPES=Release) - set(_overwrite_install_command INSTALL_COMMAND cmake --build --config Release --target install) -endif() - -set(prefix "${CMAKE_BINARY_DIR}/deps") -set(ETHASH_LIBRARY "${prefix}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}ethash${CMAKE_STATIC_LIBRARY_SUFFIX}") -set(ETHASH_INCLUDE_DIR "${prefix}/include") -set(ETHASH_BYPRODUCTS "${prefix}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}keccak${CMAKE_STATIC_LIBRARY_SUFFIX}") - -ExternalProject_Add( - ethash - PREFIX "${prefix}" - DOWNLOAD_NAME ethash-v1.0.0.tar.gz - DOWNLOAD_NO_PROGRESS 1 - URL https://github.com/chfast/ethash/archive/refs/tags/v1.0.0.tar.gz - URL_HASH SHA256=36071d9c4aaf3fd9e43155d7c2604404d6ab70613e6978cff964c5814f461a1a - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${prefix} - -DCMAKE_POSITION_INDEPENDENT_CODE=${BUILD_SHARED_LIBS} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} - -DETHASH_BUILD_TESTS=OFF - -DETHASH_BUILD_ETHASH=ON - -DCMAKE_INSTALL_LIBDIR=lib - ${_only_release_configuration} - LOG_CONFIGURE 1 - ${_overwrite_install_command} - LOG_INSTALL 1 - BUILD_BYPRODUCTS "${ETHASH_LIBRARY}" - BUILD_BYPRODUCTS "${ETHASH_BYPRODUCTS}" -) - -# Create imported library -add_library(Ethash STATIC IMPORTED) -file(MAKE_DIRECTORY "${ETHASH_INCLUDE_DIR}") # Must exist. -set_property(TARGET Ethash PROPERTY IMPORTED_CONFIGURATIONS Release) -set_property(TARGET Ethash PROPERTY IMPORTED_LOCATION_RELEASE "${ETHASH_LIBRARY}") -set_property(TARGET Ethash PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${ETHASH_INCLUDE_DIR}") -add_dependencies(Ethash ethash ${ETHASH_LIBRARY} ${ETHASH_BYPRODUCTS}) diff --git a/cmake/ProjectSecp256k1.cmake b/cmake/ProjectSecp256k1.cmake deleted file mode 100644 index 98e4771b..00000000 --- a/cmake/ProjectSecp256k1.cmake +++ /dev/null @@ -1,40 +0,0 @@ -include(ExternalProject) - -if (MSVC) - set(_only_release_configuration -DCMAKE_CONFIGURATION_TYPES=Release) - set(_overwrite_install_command INSTALL_COMMAND cmake --build --config Release --target install) -endif() - -set(prefix "${CMAKE_BINARY_DIR}/deps") -set(SECP256K1_LIBRARY "${prefix}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}secp256k1${CMAKE_STATIC_LIBRARY_SUFFIX}") -set(SECP256K1_INCLUDE_DIR "${prefix}/include") - -ExternalProject_Add( - secp256k1 - PREFIX "${prefix}" - DOWNLOAD_NAME secp256k1-ac8ccf29.tar.gz - DOWNLOAD_NO_PROGRESS 1 - GIT_REPOSITORY https://github.com/bitcoin-core/secp256k1 - GIT_TAG "bdf39000b9c6a0818e7149ccb500873d079e6e85" - PATCH_COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_CURRENT_LIST_DIR}/secp256k1/CMakeLists.txt - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${prefix} - -DCMAKE_POSITION_INDEPENDENT_CODE=${BUILD_SHARED_LIBS} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} - ${_only_release_configuration} - -DCMAKE_INSTALL_LIBDIR=lib - LOG_CONFIGURE 1 - ${_overwrite_install_command} - LOG_INSTALL 1 - BUILD_BYPRODUCTS "${SECP256K1_LIBRARY}" -) - -# Create imported library -add_library(Secp256k1 STATIC IMPORTED) -file(MAKE_DIRECTORY "${SECP256K1_INCLUDE_DIR}") # Must exist. -set_property(TARGET Secp256k1 PROPERTY IMPORTED_CONFIGURATIONS Release) -set_property(TARGET Secp256k1 PROPERTY IMPORTED_LOCATION_RELEASE "${SECP256K1_LIBRARY}") -set_property(TARGET Secp256k1 PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${SECP256K1_INCLUDE_DIR}") -add_dependencies(Secp256k1 secp256k1) diff --git a/cmake/ProjectSpeedb.cmake b/cmake/ProjectSpeedb.cmake deleted file mode 100644 index 16ec6a6d..00000000 --- a/cmake/ProjectSpeedb.cmake +++ /dev/null @@ -1,48 +0,0 @@ -include(ExternalProject) - -if (MSVC) - set(_only_release_configuration -DCMAKE_CONFIGURATION_TYPES=Release) - set(_overwrite_install_command INSTALL_COMMAND cmake --build --config Release --target install) -endif() - -set(prefix "${CMAKE_BINARY_DIR}/deps") -set(SPEEDB_LIBRARY "${prefix}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}speedb${CMAKE_STATIC_LIBRARY_SUFFIX}") -set(SPEEDB_INCLUDE_DIR "${prefix}/include") - -ExternalProject_Add( - speedb - PREFIX "${prefix}" - DOWNLOAD_NAME speedb-2.4.1.tar.gz - DOWNLOAD_NO_PROGRESS 1 - GIT_REPOSITORY https://github.com/speedb-io/speedb - GIT_TAG "speedb/v2.4.1" - #URL https://github.com/speedb-io/speedb/releases/download/speedb/v2.4.1/speedb-2.4.1.tar.gz - #URL_HASH SHA256=4e984515bbed0942d4ba22d8a219c752b0679d261a4baf7ac72c206f5ab1cd04 - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${prefix} - -DCMAKE_POSITION_INDEPENDENT_CODE=${BUILD_SHARED_LIBS} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} - -DCMAKE_BUILD_TYPE=Release - ${_only_release_configuration} - -DCMAKE_INSTALL_LIBDIR=lib - -DROCKSDB_BUILD_SHARED=OFF - -DFAIL_ON_WARNINGS=OFF - -DWITH_GFLAGS=OFF - -DWITH_RUNTIME_DEBUG=OFF - -DWITH_TESTS=OFF - -DWITH_BENCHMARK_TOOLS=OFF - -DWITH_CORE_TOOLS=OFF - -DWITH_TOOLS=OFF - -DWITH_TRACE_TOOLS=OFF - ${_overwrite_install_command} - BUILD_BYPRODUCTS "${SPEEDB_LIBRARY}" - UPDATE_COMMAND "" -) - -# Create imported library -add_library(Speedb STATIC IMPORTED) -set_property(TARGET Speedb PROPERTY IMPORTED_CONFIGURATIONS Release) -set_property(TARGET Speedb PROPERTY IMPORTED_LOCATION_RELEASE "${SPEEDB_LIBRARY}") -set_property(TARGET Speedb PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${SPEEDB_INCLUDE_DIR}") -add_dependencies(Speedb speedb SPEEDB_LIBRARY) diff --git a/cmake/certifyPatch.patch b/cmake/certifyPatch.patch deleted file mode 100644 index 270d2c30..00000000 --- a/cmake/certifyPatch.patch +++ /dev/null @@ -1,121 +0,0 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 7cceb2c..630f262 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -59,13 +59,13 @@ write_basic_package_version_file( - COMPATIBILITY AnyNewerVersion) - - install(FILES -- "netutilsConfig.cmake" -+ "certifyConfig.cmake" - "${CMAKE_BINARY_DIR}/certifyConfigVersion.cmake" - DESTINATION lib/cmake/certify) - - install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/ - DESTINATION include -- FILES_MATCHING PATTERN "*.hpp") -+ FILES_MATCHING PATTERN "*.hpp" PATTERN "*.ipp") - - install(TARGETS core - EXPORT certifyTargets -diff --git a/certifyConfig.cmake b/certifyConfig.cmake -index 272dd90..87313e8 100644 ---- a/certifyConfig.cmake -+++ b/certifyConfig.cmake -@@ -1,6 +1,7 @@ - include(CMakeFindDependencyMacro) - --find_dependency(Boost COMPONENTS system) -+find_dependency(Boost COMPONENTS system filesystem date_time) -+find_dependency(OpenSSL) - find_dependency(Threads) - --include("${CMAKE_CURRENT_LIST_DIR}/certify-Targets.cmake") -+include("${CMAKE_CURRENT_LIST_DIR}/certifyTargets.cmake") -diff --git a/include/boost/certify/crlset_parser.hpp b/include/boost/certify/crlset_parser.hpp -index 7174944..29ab461 100644 ---- a/include/boost/certify/crlset_parser.hpp -+++ b/include/boost/certify/crlset_parser.hpp -@@ -4,6 +4,7 @@ - #include - #include - #include -+#include - - namespace boost - { -diff --git a/include/boost/certify/detail/keystore_windows.ipp b/include/boost/certify/detail/keystore_windows.ipp -index efcc697..625ef00 100644 ---- a/include/boost/certify/detail/keystore_windows.ipp -+++ b/include/boost/certify/detail/keystore_windows.ipp -@@ -6,6 +6,7 @@ - - #include - #include -+#include - - namespace boost - { -diff --git a/include/boost/certify/detail/spki_blacklist.hpp b/include/boost/certify/detail/spki_blacklist.hpp -index 7833d80..2f456e9 100644 ---- a/include/boost/certify/detail/spki_blacklist.hpp -+++ b/include/boost/certify/detail/spki_blacklist.hpp -@@ -2,6 +2,7 @@ - #define BOOST_CERTIFY_DETAIL_SPKI_BLACKLIST_HPP - - #include -+#include - - namespace boost - { -diff --git a/include/boost/certify/detail/spki_digest.hpp b/include/boost/certify/detail/spki_digest.hpp -index d9e4ba9..5f937c2 100644 ---- a/include/boost/certify/detail/spki_digest.hpp -+++ b/include/boost/certify/detail/spki_digest.hpp -@@ -5,6 +5,7 @@ - #include - #include - #include -+#include - - namespace boost - { -diff --git a/include/boost/certify/impl/crlset_parser.ipp b/include/boost/certify/impl/crlset_parser.ipp -index d41fb7f..853894e 100644 ---- a/include/boost/certify/impl/crlset_parser.ipp -+++ b/include/boost/certify/impl/crlset_parser.ipp -@@ -2,6 +2,7 @@ - #define BOOST_CERTIFY_IMPL_CRLSET_PARSER_IPP - - #include -+#include - - namespace boost - { -diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt -index c1cda26..03be79c 100644 ---- a/tests/CMakeLists.txt -+++ b/tests/CMakeLists.txt -@@ -11,5 +11,9 @@ function (certify_verify_add_test test_file) - COMMAND ${target_name}) - endfunction(certify_verify_add_test) - -+certify_verify_add_test(extensions.cpp) - certify_verify_add_test(https_verification_success.cpp) - certify_verify_add_test(https_verification_fail.cpp) -+certify_verify_add_test(crl_set_parser.cpp) -+certify_verify_add_test(detail_spki_digest.cpp) -+certify_verify_add_test(status_cache.cpp) -\ No newline at end of file -diff --git a/tests/crl_set_parser.cpp b/tests/crl_set_parser.cpp -index 4e5b221..8f29a27 100644 ---- a/tests/crl_set_parser.cpp -+++ b/tests/crl_set_parser.cpp -@@ -5,6 +5,7 @@ - #include - #include - #include -+#include - - const std::uint8_t array[46] = { - 0x02, 0x00, 0x7b, 0x7d, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, diff --git a/cmake/secp256k1/CMakeLists.txt b/cmake/secp256k1/CMakeLists.txt deleted file mode 100644 index 14267f67..00000000 --- a/cmake/secp256k1/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -# This CMake config file for secp256k1 project from https://github.com/bitcoin-core/secp256k1 -# -# The secp256k1 project has been configured following official docs with following options: -# -# ./configure --disable-shared --disable-tests --disable-coverage --disable-openssl-tests --disable-exhaustive-tests --disable-jni --with-bignum=no --with-field=64bit --with-scalar=64bit --with-asm=no -# -# Build static context: -# make src/ecmult_static_context.h -# -# Copy src/ecmult_static_context.h and src/libsecp256k1-config.h -# -# Copy CFLAGS from Makefile to COMPILE_OPTIONS. - -list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules) - -cmake_minimum_required(VERSION 3.4) -project(secp256k1 LANGUAGES C) - -set(COMMON_COMPILE_FLAGS ENABLE_MODULE_RECOVERY ENABLE_MODULE_ECDH USE_ECMULT_STATIC_PRECOMPUTATION USE_FIELD_INV_BUILTIN USE_NUM_NONE USE_SCALAR_INV_BUILTIN) -if (MSVC) - set(COMPILE_FLAGS USE_FIELD_10X26 USE_SCALAR_8X32) - set(COMPILE_OPTIONS "") -else() - set(COMPILE_FLAGS USE_FIELD_5X52 USE_SCALAR_4X64 HAVE_BUILTIN_EXPECT HAVE___INT128) - set(COMPILE_OPTIONS -O2 -W -std=c89 -pedantic -Wall -Wextra -Wcast-align -Wnested-externs -Wshadow -Wstrict-prototypes -Wno-unused-function -Wno-long-long -Wno-overlength-strings -fvisibility=hidden) -endif() - -add_library(secp256k1 STATIC src/secp256k1.c src/precomputed_ecmult.h src/precomputed_ecmult_gen.h src/precomputed_ecmult.c src/precomputed_ecmult_gen.c) -target_compile_definitions(secp256k1 PRIVATE ${COMMON_COMPILE_FLAGS} ${COMPILE_FLAGS}) -target_include_directories(secp256k1 PRIVATE ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src) -target_compile_options(secp256k1 PRIVATE ${COMPILE_OPTIONS}) - -install(TARGETS secp256k1 ARCHIVE DESTINATION lib) -install(DIRECTORY include/ DESTINATION include) - diff --git a/deployedcontracts b/deployedcontracts new file mode 100644 index 00000000..a115e239 --- /dev/null +++ b/deployedcontracts @@ -0,0 +1,5 @@ + +BTVEnergy: 0x33F2f10CbcC56D3d615594FF271200eF11cb2610 +BTVPlayer: 0x9bD17306692d837743b6fb0597c8dA3279a8e501 +BTVProposals: 0x2668009cCeCAc872A37CeD6a5c3DF4682C6dfe89 +BuildTheVoid: 0x30C37F6B1d6321C4398238525046c604C7b26150 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index ecf7fddf..34660f4a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,17 +1,15 @@ -# Copyright (c) [2023-2024] [Sparq Network] +# Copyright (c) [2023-2024] [AppLayer Developers] # This software is distributed under the MIT License. # See the LICENSE.txt file in the project root for more information. -version: '3' - services: - orbitersdk-cpp-dev: - build: + bdk: + build: context: . - ports: - - "8080-8099:8080-8099" - - "8110-8111:8110-8111" + dockerfile: docker/bdk_cpp.dockerfile volumes: - - :/orbitersdk-volume + - .:/bdk-cpp + entrypoint: ["/entrypoint.sh"] + working_dir: /bdk-cpp tty: true stdin_open: true diff --git a/docker/bdk_cpp.dockerfile b/docker/bdk_cpp.dockerfile new file mode 100644 index 00000000..aaf2a1db --- /dev/null +++ b/docker/bdk_cpp.dockerfile @@ -0,0 +1,55 @@ +# Copyright (c) [2023-2024] [AppLayer Developers] +# This software is distributed under the MIT License. +# See the LICENSE.txt file in the project root for more information. + +# Start from a base Debian image +FROM debian:trixie + +# Set shell to Bash because Docker standards are stupid +SHELL ["/bin/bash", "-c"] + +# Update the system +RUN apt-get update && apt-get upgrade -y + +# Install Docker-specific dependencies +RUN apt-get -y install nano vim unison curl jq unzip wget +RUN apt-get install -y python3 python3-pip python3-venv + +# Create venv and install gcovr (for SonarQube) +# Locked at 5.0 due to https://github.com/gcovr/gcovr/issues/583#issuecomment-1079974142 +RUN pip install gcovr==5.0 --break-system-packages + +# Copy the deps script to the container +COPY scripts/deps.sh / + +# Install dependencies +RUN bash ./deps.sh --install + +# Create a directory for sonarcloud +RUN mkdir /root/.sonar + +# Copy sonarcloud scripts to sonarcloud +COPY scripts/sonarcloud.sh /sonarcloud + +# Copy Unison configuration file +COPY sync.prf /root/.unison/sync.prf + +# Copy the entrypoint script +COPY docker/entrypoint.sh /entrypoint.sh + +# Copy the entrypoint script +COPY scripts/sonarcloud.sh /sonarcloud.sh + +# Execute sonarcloud install script +RUN /sonarcloud.sh + +# Update running paths +ENV PATH=/root/.sonar/build-wrapper-linux-x86:$PATH +ENV PATH=/root/.sonar/sonar-scanner-6.2.1.4610-linux-x64/bin:$PATH +ENV PATH=/root/.sonar/sonar-scanner-6.2.0.4584-linux-x64/bin:$PATH +ENV PATH=/root/.sonar/sonar-scanner-7.0.1.4817-linux-x64/bin:$PATH +ENV PATH=/usr/local/bin:$PATH + +# Copy the entrypoint script +COPY docker/entrypoint.sh /entrypoint.sh + diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 00000000..b947c7f3 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# Start Unison in the background, ignoring files that should not be synced +nohup unison -repeat 1 /bdk-volume /bdk-cpp -auto -batch \ + -ignore 'Name {build}' \ + -ignore 'Name {build_local_testnet}' \ + -ignore 'Name {.vscode}' \ + -ignore 'Name {proto/metrics.pb.cc}' \ + -ignore 'Name {proto/metrics.pb.h}' \ + -ignore 'Name {proto/vm.grpc.pb.cc}' \ + -ignore 'Name {proto/vm.grpc.pb.h}' \ + -ignore 'Name {proto/vm.pb.cc}' \ + -ignore 'Name {proto/vm.pb.h}' \ + -ignore 'Name {storageVM}' \ + -ignore 'Name {info.txt}' \ + -ignore 'Name {.vscode}' \ + -ignore 'Name {vmInfo.txt}' \ + -ignore 'Name {*.[pP][bB].[hH]}' \ + -ignore 'Name {tests/node_modules}' \ + -ignore 'Name {depends/x86_64-pc-linux-gnu}' \ + -ignore 'Name {scripts/AIO-setup.log}' \ + -ignore 'Name {compile_commands.json}' \ + -ignore 'Name {.cache}' \ + -ignore 'Name {Dockerfile}' \ + -ignore 'Name {docker-compose.yml}' \ + -ignore 'Name {sync.prf}' \ + -ignore 'Name {kateproject}' \ + -ignore 'Name {*.o}' \ + -ignore 'Name {*.gch}' \ +> /dev/null 2>&1 & /bin/bash diff --git a/proto/aliasreader.proto b/proto/aliasreader.proto deleted file mode 100644 index 926f86b1..00000000 --- a/proto/aliasreader.proto +++ /dev/null @@ -1,23 +0,0 @@ -syntax = "proto3"; - -package aliasreader; - -option go_package = "github.com/ava-labs/avalanchego/proto/pb/aliasreader"; - -service AliasReader { - rpc Lookup(Alias) returns (ID); - rpc PrimaryAlias(ID) returns (Alias); - rpc Aliases(ID) returns (AliasList); -} - -message ID { - bytes id = 1; -} - -message Alias { - string alias = 1; -} - -message AliasList { - repeated string aliases = 1; -} diff --git a/proto/appsender.proto b/proto/appsender.proto deleted file mode 100644 index 338d9e9a..00000000 --- a/proto/appsender.proto +++ /dev/null @@ -1,65 +0,0 @@ -syntax = "proto3"; - -package appsender; - -import "google/protobuf/empty.proto"; - -option go_package = "github.com/ava-labs/avalanchego/proto/pb/appsender"; - -service AppSender { - rpc SendAppRequest(SendAppRequestMsg) returns (google.protobuf.Empty); - rpc SendAppResponse(SendAppResponseMsg) returns (google.protobuf.Empty); - rpc SendAppGossip(SendAppGossipMsg) returns (google.protobuf.Empty); - rpc SendAppGossipSpecific(SendAppGossipSpecificMsg) returns (google.protobuf.Empty); - - rpc SendCrossChainAppRequest(SendCrossChainAppRequestMsg) returns (google.protobuf.Empty); - rpc SendCrossChainAppResponse(SendCrossChainAppResponseMsg) returns (google.protobuf.Empty); -} - -message SendAppRequestMsg { - // The nodes to send this request to - repeated bytes node_ids = 1; - // The ID of this request - uint32 request_id = 2; - // The request body - bytes request = 3; -} - -message SendAppResponseMsg { - // The node to send a response to - bytes node_id = 1; - // ID of this request - uint32 request_id = 2; - // The response body - bytes response = 3; -} - -message SendAppGossipMsg { - // The message body - bytes msg = 1; -} - -message SendAppGossipSpecificMsg { - // The nodes to send this request to - repeated bytes node_ids = 1; - // The message body - bytes msg = 2; -} - -message SendCrossChainAppRequestMsg { - // The chain to send this request to - bytes chain_id = 1; - // the ID of this request - uint32 request_id = 2; - // The request body - bytes request = 3; -} - -message SendCrossChainAppResponseMsg { - // The chain to send this response to - bytes chain_id = 1; - // the ID of this request - uint32 request_id = 2; - // The response body - bytes response = 3; -} diff --git a/proto/keystore.proto b/proto/keystore.proto deleted file mode 100644 index c59f1b4d..00000000 --- a/proto/keystore.proto +++ /dev/null @@ -1,23 +0,0 @@ -syntax = "proto3"; - -package keystore; - -option go_package = "github.com/ava-labs/avalanchego/proto/pb/keystore"; - -service Keystore { - rpc GetDatabase(GetDatabaseRequest) returns (GetDatabaseResponse); -} - -message GetDatabaseRequest { - string username = 1; - string password = 2; -} - -message GetDatabaseResponse { - // reserved for backward compatibility - // avalanchego <=v1.7.9 used the field "1" as an id to identify the gRPC server - // address which served the Database service via the now removed service broker - reserved 1; - // server_addr is the address of the gRPC server hosting the Database service - string server_addr = 2; -} diff --git a/proto/messenger.proto b/proto/messenger.proto deleted file mode 100644 index 027fcdeb..00000000 --- a/proto/messenger.proto +++ /dev/null @@ -1,15 +0,0 @@ -syntax = "proto3"; - -package messenger; - -option go_package = "github.com/ava-labs/avalanchego/proto/pb/messenger"; - -service Messenger { - rpc Notify(NotifyRequest) returns (NotifyResponse); -} - -message NotifyRequest { - uint32 message = 1; -} - -message NotifyResponse {} diff --git a/proto/metrics.proto b/proto/metrics.proto deleted file mode 100644 index c9546f14..00000000 --- a/proto/metrics.proto +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2013 Prometheus Team -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto2"; - -package io.prometheus.client; -option java_package = "io.prometheus.client"; -option go_package = "github.com/prometheus/client_model/go;io_prometheus_client"; - -import "google/protobuf/timestamp.proto"; - -message LabelPair { - optional string name = 1; - optional string value = 2; -} - -enum MetricType { - COUNTER = 0; - GAUGE = 1; - SUMMARY = 2; - UNTYPED = 3; - HISTOGRAM = 4; -} - -message Gauge { - optional double value = 1; -} - -message Counter { - optional double value = 1; - optional Exemplar exemplar = 2; -} - -message Quantile { - optional double quantile = 1; - optional double value = 2; -} - -message Summary { - optional uint64 sample_count = 1; - optional double sample_sum = 2; - repeated Quantile quantile = 3; -} - -message Untyped { - optional double value = 1; -} - -message Histogram { - optional uint64 sample_count = 1; - optional double sample_sum = 2; - repeated Bucket bucket = 3; // Ordered in increasing order of upper_bound, +Inf bucket is optional. -} - -message Bucket { - optional uint64 cumulative_count = 1; // Cumulative in increasing order. - optional double upper_bound = 2; // Inclusive. - optional Exemplar exemplar = 3; -} - -message Exemplar { - repeated LabelPair label = 1; - optional double value = 2; - optional google.protobuf.Timestamp timestamp = 3; // OpenMetrics-style. -} - -message Metric { - repeated LabelPair label = 1; - optional Gauge gauge = 2; - optional Counter counter = 3; - optional Summary summary = 4; - optional Untyped untyped = 5; - optional Histogram histogram = 7; - optional int64 timestamp_ms = 6; -} - -message MetricFamily { - optional string name = 1; - optional string help = 2; - optional MetricType type = 3; - repeated Metric metric = 4; -} diff --git a/proto/rpcdb.proto b/proto/rpcdb.proto deleted file mode 100644 index c1800bd7..00000000 --- a/proto/rpcdb.proto +++ /dev/null @@ -1,122 +0,0 @@ -syntax = "proto3"; - -package rpcdb; - -import "google/protobuf/empty.proto"; - -option go_package = "github.com/ava-labs/avalanchego/proto/pb/rpcdb"; - -service Database { - rpc Has(HasRequest) returns (HasResponse); - rpc Get(GetRequest) returns (GetResponse); - rpc Put(PutRequest) returns (PutResponse); - rpc Delete(DeleteRequest) returns (DeleteResponse); - rpc Compact(CompactRequest) returns (CompactResponse); - rpc Close(CloseRequest) returns (CloseResponse); - rpc HealthCheck(google.protobuf.Empty) returns (HealthCheckResponse); - rpc WriteBatch(WriteBatchRequest) returns (WriteBatchResponse); - rpc NewIteratorWithStartAndPrefix(NewIteratorWithStartAndPrefixRequest) returns (NewIteratorWithStartAndPrefixResponse); - rpc IteratorNext(IteratorNextRequest) returns (IteratorNextResponse); - rpc IteratorError(IteratorErrorRequest) returns (IteratorErrorResponse); - rpc IteratorRelease(IteratorReleaseRequest) returns (IteratorReleaseResponse); -} - -message HasRequest { - bytes key = 1; -} - -message HasResponse { - bool has = 1; - uint32 err = 2; -} - -message GetRequest { - bytes key = 1; -} - -message GetResponse { - bytes value = 1; - uint32 err = 2; -} - -message PutRequest { - bytes key = 1; - bytes value = 2; -} - -message PutResponse { - uint32 err = 1; -} - -message DeleteRequest { - bytes key = 1; -} - -message DeleteResponse { - uint32 err = 1; -} - -message CompactRequest { - bytes start = 1; - bytes limit = 2; -} - -message CompactResponse { - uint32 err = 1; -} - -message CloseRequest {} - -message CloseResponse { - uint32 err = 1; -} - -message WriteBatchRequest { - repeated PutRequest puts = 1; - repeated DeleteRequest deletes = 2; - int64 id = 3; - bool continues = 4; -} - -message WriteBatchResponse { - uint32 err = 1; -} - -message NewIteratorRequest {} - -message NewIteratorWithStartAndPrefixRequest { - bytes start = 1; - bytes prefix = 2; -} - -message NewIteratorWithStartAndPrefixResponse { - uint64 id = 1; -} - -message IteratorNextRequest { - uint64 id = 1; -} - -message IteratorNextResponse { - repeated PutRequest data = 1; -} - -message IteratorErrorRequest { - uint64 id = 1; -} - -message IteratorErrorResponse { - uint32 err = 1; -} - -message IteratorReleaseRequest { - uint64 id = 1; -} - -message IteratorReleaseResponse { - uint32 err = 1; -} - -message HealthCheckResponse { - bytes details = 1; -} diff --git a/proto/sharedmemory.proto b/proto/sharedmemory.proto deleted file mode 100644 index 62e8277c..00000000 --- a/proto/sharedmemory.proto +++ /dev/null @@ -1,76 +0,0 @@ -syntax = "proto3"; - -package sharedmemory; - -option go_package = "github.com/ava-labs/avalanchego/proto/pb/sharedmemory"; - -service SharedMemory { - rpc Get(GetRequest) returns (GetResponse); - rpc Indexed(IndexedRequest) returns (IndexedResponse); - rpc Apply(ApplyRequest) returns (ApplyResponse); -} - -message BatchPut { - bytes key = 1; - bytes value = 2; -} - -message BatchDelete { - bytes key = 1; -} - -message Batch { - repeated BatchPut puts = 1; - repeated BatchDelete deletes = 2; - int64 id = 3; -} - -message AtomicRequest { - repeated bytes remove_requests = 1; - repeated Element put_requests = 2; - bytes peer_chain_id = 3; -} - -message Element { - bytes key = 1; - bytes value = 2; - repeated bytes traits = 3; -} - -message GetRequest { - bytes peer_chain_id = 1; - repeated bytes keys = 2; - int64 id = 3; - bool continues = 4; -} - -message GetResponse { - repeated bytes values = 1; - bool continues = 2; -} - -message IndexedRequest { - bytes peer_chain_id = 1; - repeated bytes traits = 2; - bytes start_trait = 3; - bytes start_key = 4; - int32 limit = 5; - int64 id = 6; - bool continues = 7; -} - -message IndexedResponse { - repeated bytes values = 1; - bytes last_trait = 2; - bytes last_key = 3; - bool continues = 4; -} - -message ApplyRequest { - repeated AtomicRequest requests = 1; - repeated Batch batches = 2; - int64 id = 3; - bool continues = 4; -} - -message ApplyResponse {} diff --git a/proto/vm.proto b/proto/vm.proto deleted file mode 100644 index b635e108..00000000 --- a/proto/vm.proto +++ /dev/null @@ -1,380 +0,0 @@ -syntax = "proto3"; - -package vm; - -import "google/protobuf/empty.proto"; -import "google/protobuf/timestamp.proto"; -import "metrics.proto"; - -option go_package = "github.com/ava-labs/avalanchego/proto/pb/vm"; - -// ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/snow/engine/snowman/block -// ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/snow/consensus/snowman#Block -service VM { - // ChainVM - // - // Initialize this VM. - rpc Initialize(InitializeRequest) returns (InitializeResponse); - // SetState communicates to VM its next state it starts - rpc SetState(SetStateRequest) returns (SetStateResponse); - // Shutdown is called when the node is shutting down. - rpc Shutdown(google.protobuf.Empty) returns (google.protobuf.Empty); - // Creates the HTTP handlers for custom chain network calls. - rpc CreateHandlers(google.protobuf.Empty) returns (CreateHandlersResponse); - // Creates the HTTP handlers for custom VM network calls. - // - // Note: RPC Chain VM Factory will start a new instance of the VM in a - // seperate process which will populate the static handlers. After this - // process is created other processes will be created to populate blockchains, - // but they will not have the static handlers be called again. - rpc CreateStaticHandlers(google.protobuf.Empty) returns (CreateStaticHandlersResponse); - rpc Connected(ConnectedRequest) returns (google.protobuf.Empty); - rpc Disconnected(DisconnectedRequest) returns (google.protobuf.Empty); - // Attempt to create a new block from data contained in the VM. - rpc BuildBlock(BuildBlockRequest) returns (BuildBlockResponse); - // Attempt to create a block from a stream of bytes. - rpc ParseBlock(ParseBlockRequest) returns (ParseBlockResponse); - // Attempt to load a block. - rpc GetBlock(GetBlockRequest) returns (GetBlockResponse); - // Notify the VM of the currently preferred block. - rpc SetPreference(SetPreferenceRequest) returns (google.protobuf.Empty); - // Attempt to verify the health of the VM. - rpc Health(google.protobuf.Empty) returns (HealthResponse); - // Version returns the version of the VM. - rpc Version(google.protobuf.Empty) returns (VersionResponse); - // Notify this engine of a request for data from [nodeID]. - rpc AppRequest(AppRequestMsg) returns (google.protobuf.Empty); - // Notify this engine that an AppRequest message it sent to [nodeID] with - // request ID [requestID] failed. - rpc AppRequestFailed(AppRequestFailedMsg) returns (google.protobuf.Empty); - // Notify this engine of a response to the AppRequest message it sent to - // [nodeID] with request ID [requestID]. - rpc AppResponse(AppResponseMsg) returns (google.protobuf.Empty); - // Notify this engine of a gossip message from [nodeID]. - rpc AppGossip(AppGossipMsg) returns (google.protobuf.Empty); - // Attempts to gather metrics from a VM. - rpc Gather(google.protobuf.Empty) returns (GatherResponse); - rpc CrossChainAppRequest(CrossChainAppRequestMsg) returns (google.protobuf.Empty); - rpc CrossChainAppRequestFailed(CrossChainAppRequestFailedMsg) returns (google.protobuf.Empty); - rpc CrossChainAppResponse(CrossChainAppResponseMsg) returns (google.protobuf.Empty); - - // BatchedChainVM - rpc GetAncestors(GetAncestorsRequest) returns (GetAncestorsResponse); - rpc BatchedParseBlock(BatchedParseBlockRequest) returns (BatchedParseBlockResponse); - - // HeightIndexedChainVM - rpc VerifyHeightIndex(google.protobuf.Empty) returns (VerifyHeightIndexResponse); - rpc GetBlockIDAtHeight(GetBlockIDAtHeightRequest) returns (GetBlockIDAtHeightResponse); - - // StateSyncableVM - // - // StateSyncEnabled indicates whether the state sync is enabled for this VM. - rpc StateSyncEnabled(google.protobuf.Empty) returns (StateSyncEnabledResponse); - // GetOngoingSyncStateSummary returns an in-progress state summary if it exists. - rpc GetOngoingSyncStateSummary(google.protobuf.Empty) returns (GetOngoingSyncStateSummaryResponse); - // GetLastStateSummary returns the latest state summary. - rpc GetLastStateSummary(google.protobuf.Empty) returns (GetLastStateSummaryResponse); - // ParseStateSummary parses a state summary out of [summaryBytes]. - rpc ParseStateSummary(ParseStateSummaryRequest) returns (ParseStateSummaryResponse); - // GetStateSummary retrieves the state summary that was generated at height - // [summaryHeight]. - rpc GetStateSummary(GetStateSummaryRequest) returns (GetStateSummaryResponse); - - // Block - rpc BlockVerify(BlockVerifyRequest) returns (BlockVerifyResponse); - rpc BlockAccept(BlockAcceptRequest) returns (google.protobuf.Empty); - rpc BlockReject(BlockRejectRequest) returns (google.protobuf.Empty); - - // StateSummary - rpc StateSummaryAccept(StateSummaryAcceptRequest) returns (StateSummaryAcceptResponse); -} - -message InitializeRequest { - uint32 network_id = 1; - bytes subnet_id = 2; - bytes chain_id = 3; - bytes node_id = 4; - bytes x_chain_id = 5; - bytes c_chain_id = 6; - bytes avax_asset_id = 7; - string chain_data_dir = 8; - bytes genesis_bytes = 9; - bytes upgrade_bytes = 10; - bytes config_bytes = 11; - repeated VersionedDBServer db_servers = 12; - // server_addr is the address of the gRPC server which serves - // the messenger, keystore, shared memory, blockchain alias, - // subnet alias, and appSender services - string server_addr = 13; -} - -message InitializeResponse { - bytes last_accepted_id = 1; - bytes last_accepted_parent_id = 2; - uint64 height = 3; - bytes bytes = 4; - google.protobuf.Timestamp timestamp = 5; -} - -message VersionedDBServer { - string version = 1; - // server_addr is the address of the gRPC server which serves the - // Database service - string server_addr = 2; -} - -message SetStateRequest { - uint32 state = 1; -} - -message SetStateResponse { - bytes last_accepted_id = 1; - bytes last_accepted_parent_id = 2; - uint64 height = 3; - bytes bytes = 4; - google.protobuf.Timestamp timestamp = 5; -} - -message CreateHandlersResponse { - repeated Handler handlers = 1; -} - -message CreateStaticHandlersResponse { - repeated Handler handlers = 1; -} - -message Handler { - string prefix = 1; - uint32 lock_options = 2; - // server_addr is the address of the gRPC server which serves the - // HTTP service - string server_addr = 3; -} - -message BuildBlockRequest { - optional uint64 p_chain_height = 1; -} - -// Note: The status of a freshly built block is assumed to be Processing. -message BuildBlockResponse { - bytes id = 1; - bytes parent_id = 2; - bytes bytes = 3; - uint64 height = 4; - google.protobuf.Timestamp timestamp = 5; - bool verify_with_context = 6; -} - -message ParseBlockRequest { - bytes bytes = 1; -} - -message ParseBlockResponse { - bytes id = 1; - bytes parent_id = 2; - uint32 status = 3; - uint64 height = 4; - google.protobuf.Timestamp timestamp = 5; - bool verify_with_context = 6; -} - -message GetBlockRequest { - bytes id = 1; -} - -message GetBlockResponse { - bytes parent_id = 1; - bytes bytes = 2; - uint32 status = 3; - uint64 height = 4; - google.protobuf.Timestamp timestamp = 5; - // used to propagate database.ErrNotFound through RPC - uint32 err = 6; - bool verify_with_context = 7; -} - -message SetPreferenceRequest { - bytes id = 1; -} - -message BlockVerifyRequest { - bytes bytes = 1; - - // If set, the VM server casts the block to a [block.WithVerifyContext] and - // calls [VerifyWithContext] instead of [Verify]. - optional uint64 p_chain_height = 2; -} - -message BlockVerifyResponse { - google.protobuf.Timestamp timestamp = 1; -} - -message BlockAcceptRequest { - bytes id = 1; -} - -message BlockRejectRequest { - bytes id = 1; -} - -message HealthResponse { - bytes details = 1; -} - -message VersionResponse { - string version = 1; -} - -message AppRequestMsg { - // The node that sent us this request - bytes node_id = 1; - // The ID of this request - uint32 request_id = 2; - // deadline for this request - google.protobuf.Timestamp deadline = 3; - // The request body - bytes request = 4; -} - -message AppRequestFailedMsg { - // The node that we failed to get a response from - bytes node_id = 1; - // The ID of the request we sent and didn't get a response to - uint32 request_id = 2; -} - -message AppResponseMsg { - // The node that we got a response from - bytes node_id = 1; - // Request ID of request that this is in response to - uint32 request_id = 2; - // The response body - bytes response = 3; -} - -message AppGossipMsg { - // The node that sent us a gossip message - bytes node_id = 1; - // The message body - bytes msg = 2; -} - -message CrossChainAppRequestMsg { - // The chain that sent us this request - bytes chain_id = 1; - // The ID of this request - uint32 request_id = 2; - // deadline for this request - google.protobuf.Timestamp deadline = 3; - // The request body - bytes request = 4; -} - -message CrossChainAppRequestFailedMsg { - // The chain that we failed to get a response from - bytes chain_id = 1; - // The ID of the request we sent and didn't get a response to - uint32 request_id = 2; -} - -message CrossChainAppResponseMsg { - // The chain that we got a response from - bytes chain_id = 1; - // Request ID of request that this is in response to - uint32 request_id = 2; - // The response body - bytes response = 3; -} - -message ConnectedRequest { - bytes node_id = 1; - string version = 2; -} - -message DisconnectedRequest { - bytes node_id = 1; -} - -message GetAncestorsRequest { - bytes blk_id = 1; - int32 max_blocks_num = 2; - int32 max_blocks_size = 3; - int64 max_blocks_retrival_time = 4; -} - -message GetAncestorsResponse { - repeated bytes blks_bytes = 1; -} - -message BatchedParseBlockRequest { - repeated bytes request = 1; -} - -message BatchedParseBlockResponse { - repeated ParseBlockResponse response = 1; -} - -message VerifyHeightIndexResponse { - uint32 err = 1; -} - -message GetBlockIDAtHeightRequest { - uint64 height = 1; -} - -message GetBlockIDAtHeightResponse { - bytes blk_id = 1; - uint32 err = 2; -} - -message GatherResponse { - repeated io.prometheus.client.MetricFamily metric_families = 1; -} - -message StateSyncEnabledResponse { - bool enabled = 1; - uint32 err = 2; -} - -message GetOngoingSyncStateSummaryResponse { - bytes id = 1; - uint64 height = 2; - bytes bytes = 3; - uint32 err = 4; -} - -message GetLastStateSummaryResponse { - bytes id = 1; - uint64 height = 2; - bytes bytes = 3; - uint32 err = 4; -} - -message ParseStateSummaryRequest { - bytes bytes = 1; -} - -message ParseStateSummaryResponse { - bytes id = 1; - uint64 height = 2; - uint32 err = 3; -} - -message GetStateSummaryRequest { - uint64 height = 1; -} - -message GetStateSummaryResponse { - bytes id = 1; - bytes bytes = 2; - uint32 err = 3; -} - -message StateSummaryAcceptRequest { - bytes bytes = 1; -} - -message StateSummaryAcceptResponse { - bool accepted = 1; - uint32 err = 2; -} diff --git a/scripts/AIO-setup.sh b/scripts/AIO-setup.sh index 20794076..5b9d6857 100755 --- a/scripts/AIO-setup.sh +++ b/scripts/AIO-setup.sh @@ -1,4 +1,4 @@ -# Copyright (c) [2023-2024] [Sparq Network] +# Copyright (c) [2023-2024] [AppLayer Developers] # This software is distributed under the MIT License. # See the LICENSE.txt file in the project root for more information. @@ -12,10 +12,6 @@ # Kill the tmux terminals "local_testnet_validatorX" and "local_testnet_discovery" tmux kill-session -t local_testnet_validator1 -tmux kill-session -t local_testnet_validator2 -tmux kill-session -t local_testnet_validator3 -tmux kill-session -t local_testnet_validator4 -tmux kill-session -t local_testnet_validator5 tmux kill-session -t local_testnet_normal1 tmux kill-session -t local_testnet_normal2 tmux kill-session -t local_testnet_normal3 @@ -28,8 +24,8 @@ tmux kill-session -t local_testnet_discovery CLEAN=false # Clean the build folder DEPLOY=true # Deploy the executables to the local_testnet folder ONLY_DEPLOY=false # Only deploy, do not build -DEBUG=ON # Build the project in debug mode -CORES=$(grep -c ^processor /proc/cpuinfo) # Number of cores for parallel build +DEBUG=OFF # Build the project in debug mode +CORES=12 # Number of cores for parallel build for arg in "$@" do @@ -103,7 +99,7 @@ mkdir local_testnet if [ "$ONLY_DEPLOY" = false ]; then ## Build the project cd build_local_testnet - cmake -DDEBUG=$DEBUG .. + cmake -DDEBUG=$DEBUG -DBUILD_TESTS=OFF -DBUILD_VARIABLES_TESTS=OFF .. make -j${CORES} fi @@ -111,37 +107,44 @@ if [ "$DEPLOY" = true ]; then if [ "$ONLY_DEPLOY" = true ]; then cd build_local_testnet fi - ## Copy the orbitersdkd and orbitersdk-discovery executables to the local_testnet directory - cp orbitersdkd ../local_testnet - cp orbitersdkd-discovery ../local_testnet + ## Copy the bdkd and bdkd-discovery executables to the local_testnet directory + cp src/bins/bdkd/bdkd ../local_testnet + cp src/bins/bdkd-discovery/bdkd-discovery ../local_testnet # Create the directories for the Validators and Discovery Node and copy the executables cd ../local_testnet - for i in $(seq 1 5); do + for i in $(seq 1 1); do mkdir local_testnet_validator$i mkdir local_testnet_validator$i/blockchain - cp orbitersdkd local_testnet_validator$i + cp bdkd local_testnet_validator$i done for i in $(seq 1 6); do mkdir local_testnet_normal$i mkdir local_testnet_normal$i/blockchain - cp orbitersdkd local_testnet_normal$i + cp bdkd local_testnet_normal$i done mkdir local_testnet_discovery mkdir local_testnet_discovery/discoveryNode - cp orbitersdkd-discovery local_testnet_discovery + cp bdkd-discovery local_testnet_discovery # Create the JSON files for the Discovery Node, Validators and Normal Nodes echo '{ "rootPath": "discoveryNode", - "web3clientVersion": "OrbiterSDK/cpp/linux_x86-64/0.2.0", + "web3clientVersion": "bdk/cpp/linux_x86-64/0.2.0", "version": 1, "chainID": 808080, "chainOwner": "0x00dead00665771855a34155f5e7405489df2c3c6", - "wsPort": 8080, + "p2pIp" : "127.0.0.1", + "p2pPort": 8080, "httpPort": 9999, + "minDiscoveryConns": 11, + "minNormalConns": 11, + "maxDiscoveryConns": 200, + "maxNormalConns": 50, "eventBlockCap": 2000, "eventLogCap": 10000, + "stateDumpTrigger" : 1000, + "minValidators": 4, "privKey": "0000000000000000000000000000000000000000000000000000000000000000", "genesis" : { "validators": [ @@ -154,23 +157,37 @@ if [ "$DEPLOY" = true ]; then "timestamp" : 1656356646000000, "signer" : "0x4d48bdf34d65ef2bed2e4ee9020a7d3162b494ac31d3088153425f286f3d3c8c", "balances": [ - { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "1000000000000000000000" } + { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "100000000000000000000000000000000000000000" } ] - } + }, + "indexingMode" : "RPC" }' >> local_testnet_discovery/discoveryNode/options.json # Create the JSON file for the Validators echo '{ "rootPath": "blockchain", - "web3clientVersion": "OrbiterSDK/cpp/linux_x86-64/0.2.0", + "web3clientVersion": "bdk/cpp/linux_x86-64/0.2.0", "version": 1, "chainID": 808080, "chainOwner": "0x00dead00665771855a34155f5e7405489df2c3c6", - "wsPort": 8081, + "p2pIp" : "127.0.0.1", + "p2pPort": 8081, "httpPort": 8090, + "minDiscoveryConns": 5, + "minNormalConns": 5, + "maxDiscoveryConns": 200, + "maxNormalConns": 50, "eventBlockCap": 2000, "eventLogCap": 10000, + "stateDumpTrigger" : 1000, + "minValidators": 4, "privKey": "0xba5e6e9dd9cbd263969b94ee385d885c2d303dfc181db2a09f6bf19a7ba26759", + "extraValidators": [ + "0xfd84d99aa18b474bf383e10925d82194f1b0ca268e7a339032679d6e3a201ad4", + "0x66ce71abe0b8acd92cfd3965d6f9d80122aed9b0e9bdd3dbe018230bafde5751", + "0x856aeb3b9c20a80d1520a2406875f405d336e09475f43c478eb4f0dafb765fe7", + "0x81f288dd776f4edfe256d34af1f7d719f511559f19115af3e3d692e741faadc6" + ], "genesis" : { "validators": [ "0x7588b0f553d1910266089c58822e1120db47e572", @@ -182,7 +199,7 @@ if [ "$DEPLOY" = true ]; then "timestamp" : 1656356646000000, "signer" : "0x4d48bdf34d65ef2bed2e4ee9020a7d3162b494ac31d3088153425f286f3d3c8c", "balances": [ - { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "1000000000000000000000" } + { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "100000000000000000000000000000000000000000" } ] }, "discoveryNodes": [ @@ -190,152 +207,28 @@ if [ "$DEPLOY" = true ]; then "address" : "127.0.0.1", "port" : 8080 } - ] + ], + "indexingMode" : "RPC_TRACE" }' >> local_testnet_validator1/blockchain/options.json - echo '{ - "rootPath": "blockchain", - "web3clientVersion": "OrbiterSDK/cpp/linux_x86-64/0.2.0", - "version": 1, - "chainID": 808080, - "chainOwner": "0x00dead00665771855a34155f5e7405489df2c3c6", - "wsPort": 8082, - "httpPort": 8091, - "eventBlockCap": 2000, - "eventLogCap": 10000, - "privKey": "0xfd84d99aa18b474bf383e10925d82194f1b0ca268e7a339032679d6e3a201ad4", - "genesis" : { - "validators": [ - "0x7588b0f553d1910266089c58822e1120db47e572", - "0xcabf34a268847a610287709d841e5cd590cc5c00", - "0x5fb516dc2cfc1288e689ed377a9eebe2216cf1e3", - "0x795083c42583842774febc21abb6df09e784fce5", - "0xbec7b74f70c151707a0bfb20fe3767c6e65499e0" - ], - "timestamp" : 1656356646000000, - "signer" : "0x4d48bdf34d65ef2bed2e4ee9020a7d3162b494ac31d3088153425f286f3d3c8c", - "balances": [ - { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "1000000000000000000000" } - ] - }, - "discoveryNodes": [ - { - "address" : "127.0.0.1", - "port" : 8080 - } - ] - }' >> local_testnet_validator2/blockchain/options.json - - echo '{ - "rootPath": "blockchain", - "web3clientVersion": "OrbiterSDK/cpp/linux_x86-64/0.2.0", - "version": 1, - "chainID": 808080, - "chainOwner": "0x00dead00665771855a34155f5e7405489df2c3c6", - "wsPort": 8083, - "httpPort": 8092, - "eventBlockCap": 2000, - "eventLogCap": 10000, - "privKey": "0x66ce71abe0b8acd92cfd3965d6f9d80122aed9b0e9bdd3dbe018230bafde5751", - "genesis" : { - "validators": [ - "0x7588b0f553d1910266089c58822e1120db47e572", - "0xcabf34a268847a610287709d841e5cd590cc5c00", - "0x5fb516dc2cfc1288e689ed377a9eebe2216cf1e3", - "0x795083c42583842774febc21abb6df09e784fce5", - "0xbec7b74f70c151707a0bfb20fe3767c6e65499e0" - ], - "timestamp" : 1656356646000000, - "signer" : "0x4d48bdf34d65ef2bed2e4ee9020a7d3162b494ac31d3088153425f286f3d3c8c", - "balances": [ - { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "1000000000000000000000" } - ] - }, - "discoveryNodes": [ - { - "address" : "127.0.0.1", - "port" : 8080 - } - ] - }' >> local_testnet_validator3/blockchain/options.json - - echo '{ - "rootPath": "blockchain", - "web3clientVersion": "OrbiterSDK/cpp/linux_x86-64/0.2.0", - "version": 1, - "chainID": 808080, - "chainOwner": "0x00dead00665771855a34155f5e7405489df2c3c6", - "wsPort": 8084, - "httpPort": 8093, - "eventBlockCap": 2000, - "eventLogCap": 10000, - "privKey": "0x856aeb3b9c20a80d1520a2406875f405d336e09475f43c478eb4f0dafb765fe7", - "genesis" : { - "validators": [ - "0x7588b0f553d1910266089c58822e1120db47e572", - "0xcabf34a268847a610287709d841e5cd590cc5c00", - "0x5fb516dc2cfc1288e689ed377a9eebe2216cf1e3", - "0x795083c42583842774febc21abb6df09e784fce5", - "0xbec7b74f70c151707a0bfb20fe3767c6e65499e0" - ], - "timestamp" : 1656356646000000, - "signer" : "0x4d48bdf34d65ef2bed2e4ee9020a7d3162b494ac31d3088153425f286f3d3c8c", - "balances": [ - { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "1000000000000000000000" } - ] - }, - "discoveryNodes": [ - { - "address" : "127.0.0.1", - "port" : 8080 - } - ] - }' >> local_testnet_validator4/blockchain/options.json - - echo '{ - "rootPath": "blockchain", - "web3clientVersion": "OrbiterSDK/cpp/linux_x86-64/0.2.0", - "version": 1, - "chainID": 808080, - "chainOwner": "0x00dead00665771855a34155f5e7405489df2c3c6", - "wsPort": 8085, - "httpPort": 8094, - "eventBlockCap": 2000, - "eventLogCap": 10000, - "privKey": "0x81f288dd776f4edfe256d34af1f7d719f511559f19115af3e3d692e741faadc6", - "genesis" : { - "validators": [ - "0x7588b0f553d1910266089c58822e1120db47e572", - "0xcabf34a268847a610287709d841e5cd590cc5c00", - "0x5fb516dc2cfc1288e689ed377a9eebe2216cf1e3", - "0x795083c42583842774febc21abb6df09e784fce5", - "0xbec7b74f70c151707a0bfb20fe3767c6e65499e0" - ], - "timestamp" : 1656356646000000, - "signer" : "0x4d48bdf34d65ef2bed2e4ee9020a7d3162b494ac31d3088153425f286f3d3c8c", - "balances": [ - { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "1000000000000000000000" } - ] - }, - "discoveryNodes": [ - { - "address" : "127.0.0.1", - "port" : 8080 - } - ] - }' >> local_testnet_validator5/blockchain/options.json - # Create the json file for the Normal Nodes echo '{ "rootPath": "blockchain", - "web3clientVersion": "OrbiterSDK/cpp/linux_x86-64/0.2.0", + "web3clientVersion": "bdk/cpp/linux_x86-64/0.2.0", "version": 1, "chainID": 808080, "chainOwner": "0x00dead00665771855a34155f5e7405489df2c3c6", - "wsPort": 8086, + "p2pIp" : "127.0.0.1", + "p2pPort": 8086, "httpPort": 8095, + "minDiscoveryConns": 5, + "minNormalConns": 5, + "maxDiscoveryConns": 200, + "maxNormalConns": 50, "eventBlockCap": 2000, "eventLogCap": 10000, + "stateDumpTrigger" : 1000, + "minValidators": 4, "genesis" : { "validators": [ "0x7588b0f553d1910266089c58822e1120db47e572", @@ -347,7 +240,7 @@ if [ "$DEPLOY" = true ]; then "timestamp" : 1656356646000000, "signer" : "0x4d48bdf34d65ef2bed2e4ee9020a7d3162b494ac31d3088153425f286f3d3c8c", "balances": [ - { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "1000000000000000000000" } + { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "100000000000000000000000000000000000000000" } ] }, "discoveryNodes": [ @@ -355,19 +248,27 @@ if [ "$DEPLOY" = true ]; then "address" : "127.0.0.1", "port" : 8080 } - ] + ], + "indexingMode" : "RPC" }' >> local_testnet_normal1/blockchain/options.json echo '{ "rootPath": "blockchain", - "web3clientVersion": "OrbiterSDK/cpp/linux_x86-64/0.2.0", + "web3clientVersion": "bdk/cpp/linux_x86-64/0.2.0", "version": 1, "chainID": 808080, "chainOwner": "0x00dead00665771855a34155f5e7405489df2c3c6", - "wsPort": 8087, + "p2pIp" : "127.0.0.1", + "p2pPort": 8087, "httpPort": 8096, + "minDiscoveryConns": 5, + "minNormalConns": 5, + "maxDiscoveryConns": 200, + "maxNormalConns": 50, "eventBlockCap": 2000, "eventLogCap": 10000, + "stateDumpTrigger" : 1000, + "minValidators": 4, "genesis" : { "validators": [ "0x7588b0f553d1910266089c58822e1120db47e572", @@ -379,7 +280,7 @@ if [ "$DEPLOY" = true ]; then "timestamp" : 1656356646000000, "signer" : "0x4d48bdf34d65ef2bed2e4ee9020a7d3162b494ac31d3088153425f286f3d3c8c", "balances": [ - { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "1000000000000000000000" } + { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "100000000000000000000000000000000000000000" } ] }, "discoveryNodes": [ @@ -387,19 +288,27 @@ if [ "$DEPLOY" = true ]; then "address" : "127.0.0.1", "port" : 8080 } - ] + ], + "indexingMode" : "RPC" }' >> local_testnet_normal2/blockchain/options.json echo '{ "rootPath": "blockchain", - "web3clientVersion": "OrbiterSDK/cpp/linux_x86-64/0.2.0", + "web3clientVersion": "bdk/cpp/linux_x86-64/0.2.0", "version": 1, "chainID": 808080, "chainOwner": "0x00dead00665771855a34155f5e7405489df2c3c6", - "wsPort": 8088, + "p2pIp" : "127.0.0.1", + "p2pPort": 8088, "httpPort": 8097, + "minDiscoveryConns": 5, + "minNormalConns": 5, + "maxDiscoveryConns": 200, + "maxNormalConns": 50, "eventBlockCap": 2000, "eventLogCap": 10000, + "stateDumpTrigger" : 1000, + "minValidators": 4, "genesis" : { "validators": [ "0x7588b0f553d1910266089c58822e1120db47e572", @@ -411,7 +320,7 @@ if [ "$DEPLOY" = true ]; then "timestamp" : 1656356646000000, "signer" : "0x4d48bdf34d65ef2bed2e4ee9020a7d3162b494ac31d3088153425f286f3d3c8c", "balances": [ - { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "1000000000000000000000" } + { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "100000000000000000000000000000000000000000" } ] }, "discoveryNodes": [ @@ -419,19 +328,27 @@ if [ "$DEPLOY" = true ]; then "address" : "127.0.0.1", "port" : 8080 } - ] + ], + "indexingMode" : "RPC" }' >> local_testnet_normal3/blockchain/options.json echo '{ "rootPath": "blockchain", - "web3clientVersion": "OrbiterSDK/cpp/linux_x86-64/0.2.0", + "web3clientVersion": "bdk/cpp/linux_x86-64/0.2.0", "version": 1, "chainID": 808080, "chainOwner": "0x00dead00665771855a34155f5e7405489df2c3c6", - "wsPort": 8089, + "p2pIp" : "127.0.0.1", + "p2pPort": 8089, "httpPort": 8098, + "minDiscoveryConns": 5, + "minNormalConns": 5, + "maxDiscoveryConns": 200, + "maxNormalConns": 50, "eventBlockCap": 2000, "eventLogCap": 10000, + "stateDumpTrigger" : 1000, + "minValidators": 4, "genesis" : { "validators": [ "0x7588b0f553d1910266089c58822e1120db47e572", @@ -443,7 +360,7 @@ if [ "$DEPLOY" = true ]; then "timestamp" : 1656356646000000, "signer" : "0x4d48bdf34d65ef2bed2e4ee9020a7d3162b494ac31d3088153425f286f3d3c8c", "balances": [ - { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "1000000000000000000000" } + { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "100000000000000000000000000000000000000000" } ] }, "discoveryNodes": [ @@ -451,19 +368,27 @@ if [ "$DEPLOY" = true ]; then "address" : "127.0.0.1", "port" : 8080 } - ] + ], + "indexingMode" : "RPC" }' >> local_testnet_normal4/blockchain/options.json echo '{ "rootPath": "blockchain", - "web3clientVersion": "OrbiterSDK/cpp/linux_x86-64/0.2.0", + "web3clientVersion": "bdk/cpp/linux_x86-64/0.2.0", "version": 1, "chainID": 808080, "chainOwner": "0x00dead00665771855a34155f5e7405489df2c3c6", - "wsPort": 8110, + "p2pIp" : "127.0.0.1", + "p2pPort": 8110, "httpPort": 8099, + "minDiscoveryConns": 5, + "minNormalConns": 5, + "maxDiscoveryConns": 200, + "maxNormalConns": 50, "eventBlockCap": 2000, "eventLogCap": 10000, + "stateDumpTrigger" : 1000, + "minValidators": 4, "genesis" : { "validators": [ "0x7588b0f553d1910266089c58822e1120db47e572", @@ -475,7 +400,7 @@ if [ "$DEPLOY" = true ]; then "timestamp" : 1656356646000000, "signer" : "0x4d48bdf34d65ef2bed2e4ee9020a7d3162b494ac31d3088153425f286f3d3c8c", "balances": [ - { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "1000000000000000000000" } + { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "100000000000000000000000000000000000000000" } ] }, "discoveryNodes": [ @@ -483,19 +408,27 @@ if [ "$DEPLOY" = true ]; then "address" : "127.0.0.1", "port" : 8080 } - ] + ], + "indexingMode" : "RPC" }' >> local_testnet_normal5/blockchain/options.json echo '{ "rootPath": "blockchain", - "web3clientVersion": "OrbiterSDK/cpp/linux_x86-64/0.2.0", + "web3clientVersion": "bdk/cpp/linux_x86-64/0.2.0", "version": 1, "chainID": 808080, "chainOwner": "0x00dead00665771855a34155f5e7405489df2c3c6", - "wsPort": 8111, + "p2pIp" : "127.0.0.1", + "p2pPort": 8111, "httpPort": 8100, + "minDiscoveryConns": 5, + "minNormalConns": 5, + "maxDiscoveryConns": 200, + "maxNormalConns": 50, "eventBlockCap": 2000, "eventLogCap": 10000, + "stateDumpTrigger" : 1000, + "minValidators": 4, "genesis" : { "validators": [ "0x7588b0f553d1910266089c58822e1120db47e572", @@ -507,7 +440,7 @@ if [ "$DEPLOY" = true ]; then "timestamp" : 1656356646000000, "signer" : "0x4d48bdf34d65ef2bed2e4ee9020a7d3162b494ac31d3088153425f286f3d3c8c", "balances": [ - { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "1000000000000000000000" } + { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "100000000000000000000000000000000000000000" } ] }, "discoveryNodes": [ @@ -515,19 +448,27 @@ if [ "$DEPLOY" = true ]; then "address" : "127.0.0.1", "port" : 8080 } - ] + ], + "indexingMode" : "RPC" }' >> local_testnet_normal6/blockchain/options.json echo '{ "rootPath": "blockchain", - "web3clientVersion": "OrbiterSDK/cpp/linux_x86-64/0.2.0", + "web3clientVersion": "bdk/cpp/linux_x86-64/0.2.0", "version": 1, "chainID": 808080, "chainOwner": "0x00dead00665771855a34155f5e7405489df2c3c6", - "wsPort": 8110, + "p2pIp" : "127.0.0.1", + "p2pPort": 8110, "httpPort": 8099, + "minDiscoveryConns": 5, + "minNormalConns": 5, + "maxDiscoveryConns": 200, + "maxNormalConns": 50, "eventBlockCap": 2000, "eventLogCap": 10000, + "stateDumpTrigger" : 1000, + "minValidators": 4, "genesis" : { "validators": [ "0x7588b0f553d1910266089c58822e1120db47e572", @@ -539,7 +480,7 @@ if [ "$DEPLOY" = true ]; then "timestamp" : 1656356646000000, "signer" : "0x4d48bdf34d65ef2bed2e4ee9020a7d3162b494ac31d3088153425f286f3d3c8c", "balances": [ - { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "1000000000000000000000" } + { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "100000000000000000000000000000000000000000" } ] }, "discoveryNodes": [ @@ -547,19 +488,27 @@ if [ "$DEPLOY" = true ]; then "address" : "127.0.0.1", "port" : 8080 } - ] + ], + "indexingMode" : "RPC" }' >> local_testnet_normal5/blockchain/options.json echo '{ "rootPath": "blockchain", - "web3clientVersion": "OrbiterSDK/cpp/linux_x86-64/0.2.0", + "web3clientVersion": "bdk/cpp/linux_x86-64/0.2.0", "version": 1, "chainID": 808080, "chainOwner": "0x00dead00665771855a34155f5e7405489df2c3c6", - "wsPort": 8111, + "p2pIp" : "127.0.0.1", + "p2pPort": 8111, "httpPort": 8100, + "minDiscoveryConns": 5, + "minNormalConns": 5, + "maxDiscoveryConns": 200, + "maxNormalConns": 50, "eventBlockCap": 2000, "eventLogCap": 10000, + "stateDumpTrigger" : 1000, + "minValidators": 4, "genesis" : { "validators": [ "0x7588b0f553d1910266089c58822e1120db47e572", @@ -571,7 +520,7 @@ if [ "$DEPLOY" = true ]; then "timestamp" : 1656356646000000, "signer" : "0x4d48bdf34d65ef2bed2e4ee9020a7d3162b494ac31d3088153425f286f3d3c8c", "balances": [ - { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "1000000000000000000000" } + { "address": "0x00dead00665771855a34155f5e7405489df2c3c6", "balance": "100000000000000000000000000000000000000000" } ] }, "discoveryNodes": [ @@ -579,60 +528,45 @@ if [ "$DEPLOY" = true ]; then "address" : "127.0.0.1", "port" : 8080 } - ] + ], + "indexingMode" : "RPC" }' >> local_testnet_normal6/blockchain/options.json # Launch the Discovery Node through tmux echo "Launching Discovery Node" cd local_testnet_discovery - tmux new-session -d -s local_testnet_discovery './orbitersdkd-discovery || bash && bash' + tmux new-session -d -s local_testnet_discovery './bdkd-discovery || bash && bash' sleep 1 # Launch the Validators through tmux, don't exit the tmux session when closing the terminal echo "Launching Validator 1" cd ../local_testnet_validator1 - tmux new-session -d -s local_testnet_validator1 './orbitersdkd || bash && bash' - - echo "Launching Validator 2" - cd ../local_testnet_validator2 - tmux new-session -d -s local_testnet_validator2 './orbitersdkd || bash && bash' - - echo "Launching Validator 3" - cd ../local_testnet_validator3 - tmux new-session -d -s local_testnet_validator3 './orbitersdkd || bash && bash' - - echo "Launching Validator 4" - cd ../local_testnet_validator4 - tmux new-session -d -s local_testnet_validator4 './orbitersdkd || bash && bash' - - echo "Launching Validator 5" - cd ../local_testnet_validator5 - tmux new-session -d -s local_testnet_validator5 './orbitersdkd || bash && bash' + tmux new-session -d -s local_testnet_validator1 './bdkd || bash && bash' # Launch the Normal Nodes through tmux, don't exit the tmux session when closing the terminal echo "Launching Normal Node 1" cd ../local_testnet_normal1 - tmux new-session -d -s local_testnet_normal1 './orbitersdkd || bash && bash' + tmux new-session -d -s local_testnet_normal1 './bdkd || bash && bash' echo "Launching Normal Node 2" cd ../local_testnet_normal2 - tmux new-session -d -s local_testnet_normal2 './orbitersdkd || bash && bash' + tmux new-session -d -s local_testnet_normal2 './bdkd || bash && bash' echo "Launching Normal Node 3" cd ../local_testnet_normal3 - tmux new-session -d -s local_testnet_normal3 './orbitersdkd || bash && bash' + tmux new-session -d -s local_testnet_normal3 './bdkd || bash && bash' echo "Launching Normal Node 4" cd ../local_testnet_normal4 - tmux new-session -d -s local_testnet_normal4 './orbitersdkd || bash && bash' + tmux new-session -d -s local_testnet_normal4 './bdkd || bash && bash' echo "Launching Normal Node 5" cd ../local_testnet_normal5 - tmux new-session -d -s local_testnet_normal5 './orbitersdkd || bash && bash' + tmux new-session -d -s local_testnet_normal5 './bdkd || bash && bash' echo "Launching Normal Node 6" cd ../local_testnet_normal6 - tmux new-session -d -s local_testnet_normal6 './orbitersdkd || bash && bash' + tmux new-session -d -s local_testnet_normal6 './bdkd > output.txt || bash && bash' # Finish deploying GREEN=$'\e[0;32m' diff --git a/scripts/auto.sh b/scripts/auto.sh new file mode 100755 index 00000000..53ff040e --- /dev/null +++ b/scripts/auto.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash + +# debug +# set -x + +# set working directory +_AUTO_DIR=$(dirname ${0}) + +# load modules +. ${_AUTO_DIR}/auto_defines.sh +. ${_AUTO_DIR}/auto_compose.sh +. ${_AUTO_DIR}/auto_actions.sh + +# logs, printf wrapper +log () +{ + printf "$@"; +} + +# error +log_error () +{ + printf $_RED; log "$@"; printf $_RESET; +} + +# success +log_ok () +{ + printf $_GREEN; log "$@"; printf $_RESET; +} + +# fatal, die: logs error and exit +die () +{ + log_error "[-] Fatal: $@"; printf $_RESET; exit 1; +} + +check_action () +{ + # any function named as _NAME_action can be executed directly as an action + ( command -v _${_ACTION}_action 2>&1 > /dev/null ) && return 0 + + die "invalid action! Use: $0 help\n" +} + +# parse user options +parse_opts () +{ + # get options + for opt in "$@"; do + case $opt in + # enable debugging + -x) shift 1; set -x ;; + + # enable verbose + -v) shift 1; set -v ;; + + # enable errexit + -e) shift 1; set -e ;; + + # set compose file + -f) shift 1; _COMPOSE_FILE=$(echo "${1}" | tr , ' '); shift 1 ;; + + # selected services + -s) shift 1; _COMPOSE_SERVICE=$1; shift ;; + esac + done + + # set action + _ACTION=${1} ; shift ; + + # update compose services variable + _COMPOSE_SERVICE=$(echo "${_COMPOSE_SERVICE}" | tr , ' ') + + # additional params with will be appended to the action function parameters + _PARAMS="${@}" +} + +# select an action and execute it associated function +handle_action () +{ + eval _${_ACTION}_action $_PARAMS +} + +# main entry point +main () +{ + # get/set user options (command-line) + parse_opts "$@" + + # verify action argument + check_action + + # logs and continue + log "Params: $(printf '%s' "${_PARAMS[@]}")\n" + + # select build related actions and handle it + handle_action ${_PARAMS[@]} +} + +# main routine +main $@ diff --git a/scripts/auto_actions.sh b/scripts/auto_actions.sh new file mode 100644 index 00000000..16a95ec5 --- /dev/null +++ b/scripts/auto_actions.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash + +_help_action () +{ + # print help message + printf $_GREEN + cat >&1 < /dev/null | head -n 1) + FOUND2=$(find /usr/bin -name "$1" 2> /dev/null | head -n 1) + if [ -n "$FOUND1" ]; then echo "$FOUND1"; elif [ -n "$FOUND2" ]; then echo "$FOUND2"; else echo ""; fi +} + +# Helper function to check for a library in the system. +# ONLY CHECKS /usr/local AND /usr. If both match, gives preference to the former. +# Returns the first found match, or an empty string if there is no match. +# Usage: HAS_LIB=$(check_lib "libname") +# $1 = library name, including suffix (e.g. "libz.a") +check_lib() { + FOUND1=$(find /usr/local/lib -name "$1" 2> /dev/null | head -n 1) + FOUND2=$(find /usr/lib -name "$1" 2> /dev/null | head -n 1) + if [ -n "$FOUND1" ]; then echo "$FOUND1"; elif [ -n "$FOUND2" ]; then echo "$FOUND2"; else echo ""; fi +} + +# Another version of check_lib() for use with libs with multiple components (e.g. Boost). +# Returns the first found match, or an empty string if there is no match. +# Usage: HAS_LIBS=$(check_libs "libname") +# $1 = library name, including suffix (e.g. "libboost_*.a") +check_libs() { + FOUND1=$(find /usr/local/lib -name "$1" 2> /dev/null | head -n 1) + FOUND2=$(find /usr/lib -name "$1" 2> /dev/null | head -n 1) + if [ -n "$FOUND1" ]; then echo "/usr/local/lib/$1"; elif [ -n "$FOUND2" ]; then echo "/usr/lib/$1"; else echo ""; fi +} + +# Versions for external dependencies - update numbers here if required +ETHASH_VERSION="1.1.0" +EVMONE_VERSION="0.15.0" +SPEEDB_VERSION="2.8.0" +SQLITECPP_VERSION="3.3.2" + +# =========================================================================== +# SCRIPT STARTS HERE +# =========================================================================== + +echo "-- Scanning for dependencies..." + +# Check toolchain binaries +# Necessary: git, wget, gcc/g++, make, ld, autoconf, libtool, pkg-config, cmake, tmux, +# protoc + grpc_cpp_plugin (external) +# Optional: ninja, mold, doxygen, clang-tidy +HAS_GIT=$(check_exec git) +HAS_WGET=$(check_exec wget) +HAS_GCC=$(check_exec gcc) +HAS_GPP=$(check_exec g++) +HAS_MAKE=$(check_exec make) +HAS_LD=$(check_exec ld) +HAS_AUTOCONF=$(check_exec autoconf) # Required for local gRPC compilation +HAS_LIBTOOL=$(check_exec libtool) # Required for local gRPC compilation +HAS_PKGCONFIG=$(check_exec pkg-config) # Required for local gRPC compilation +HAS_CMAKE=$(check_exec cmake) +HAS_TMUX=$(check_exec tmux) +HAS_PROTOC=$(check_exec protoc) +HAS_GRPC=$(check_exec grpc_cpp_plugin) + +HAS_NINJA=$(check_exec ninja) +HAS_MOLD=$(check_exec mold) +HAS_DOXYGEN=$(check_exec doxygen) +HAS_CLANGTIDY=$(check_exec clang-tidy) + +# Check internal libraries +# Necessary: libboost-all-dev, openssl/libssl-dev, libzstd-dev, liblz4-dev, libcrypto++-dev, +# libscrypt-dev, libgrpc-dev, libgrpc++-dev, libc-ares-dev, libsecp256k1-dev +HAS_BOOST=$(check_libs "libboost_*.a") +HAS_LIBSSL=$(check_lib "libssl.a") +HAS_ZSTD=$(check_lib "libzstd.a") +HAS_LZ4=$(check_lib "liblz4.a") +HAS_LIBCRYPTOPP=$(check_lib "libcryptopp.a") +HAS_LIBSCRYPT=$(check_lib "libscrypt.a") +HAS_LIBCARES=$(check_lib "libcares_static.a") # Debian 13 and higher +if [ -z "$HAS_LIBCARES" ]; then HAS_LIBCARES=$(check_lib "libcares.a"); fi # Debian 12 and lower +HAS_LIBGRPC=$(check_lib "libgrpc.a") +HAS_LIBGRPCPP=$(check_lib "libgrpc++.a") +HAS_SECP256K1=$(check_lib "libsecp256k1.a") + +# Check external libraries +# Necessary: ethash (+ keccak), evmone (+ evmc), speedb +HAS_ETHASH=$(check_lib "libethash.a") +HAS_KECCAK=$(check_lib "libkeccak.a") +HAS_EVMC_INSTRUCTIONS=$(check_lib "libevmc-instructions.a") +HAS_EVMC_LOADER=$(check_lib "libevmc-loader.a") +HAS_EVMONE=$(check_lib "libevmone.a") +HAS_SPEEDB=$(check_lib "libspeedb.a") +HAS_SQLITECPP=$(check_lib "libSQLiteCpp.a") + +if [ "${1:-}" == "--check" ]; then + echo "-- Required toolchain binaries:" + echo -n "git: " && [ -n "$HAS_GIT" ] && echo "$HAS_GIT" || echo "not found" + echo -n "wget: " && [ -n "$HAS_WGET" ] && echo "$HAS_WGET" || echo "not found" + echo -n "gcc: " && [ -n "$HAS_GCC" ] && echo "$HAS_GCC" || echo "not found" + echo -n "g++: " && [ -n "$HAS_GPP" ] && echo "$HAS_GPP" || echo "not found" + echo -n "make: " && [ -n "$HAS_MAKE" ] && echo "$HAS_MAKE" || echo "not found" + echo -n "ld: " && [ -n "$HAS_LD" ] && echo "$HAS_LD" || echo "not found" + echo -n "autoconf: " && [ -n "$HAS_AUTOCONF" ] && echo "$HAS_AUTOCONF" || echo "not found" + echo -n "libtool: " && [ -n "$HAS_LIBTOOL" ] && echo "$HAS_LIBTOOL" || echo "not found" + echo -n "pkg-config: " && [ -n "$HAS_PKGCONFIG" ] && echo "$HAS_PKGCONFIG" || echo "not found" + echo -n "cmake: " && [ -n "$HAS_CMAKE" ] && echo "$HAS_CMAKE" || echo "not found" + echo -n "tmux: " && [ -n "$HAS_TMUX" ] && echo "$HAS_TMUX" || echo "not found" + echo -n "protoc: " && [ -n "$HAS_PROTOC" ] && echo "$HAS_PROTOC" || echo "not found" + echo -n "grpc_cpp_plugin: " && [ -n "$HAS_GRPC" ] && echo "$HAS_GRPC" || echo "not found" + + echo "-- Optional toolchain binaries:" + echo -n "ninja: " && [ -n "$HAS_NINJA" ] && echo "$HAS_NINJA" || echo "not found" + echo -n "mold: " && [ -n "$HAS_MOLD" ] && echo "$HAS_MOLD" || echo "not found" + echo -n "doxygen: " && [ -n "$HAS_DOXYGEN" ] && echo "$HAS_DOXYGEN" || echo "not found" + echo -n "clang-tidy: " && [ -n "$HAS_CLANGTIDY" ] && echo "$HAS_CLANGTIDY" || echo "not found" + + echo "-- Internal libraries:" + echo -n "boost: " && [ -n "$HAS_BOOST" ] && echo "$HAS_BOOST" || echo "not found" + echo -n "libssl: " && [ -n "$HAS_LIBSSL" ] && echo "$HAS_LIBSSL" || echo "not found" + echo -n "libzstd: " && [ -n "$HAS_ZSTD" ] && echo "$HAS_ZSTD" || echo "not found" + echo -n "liblz4: " && [ -n "$HAS_LZ4" ] && echo "$HAS_LZ4" || echo "not found" + echo -n "libcryptopp: " && [ -n "$HAS_LIBCRYPTOPP" ] && echo "$HAS_LIBCRYPTOPP" || echo "not found" + echo -n "libscrypt: " && [ -n "$HAS_LIBSCRYPT" ] && echo "$HAS_LIBSCRYPT" || echo "not found" + echo -n "libcares: " && [ -n "$HAS_LIBCARES" ] && echo "$HAS_LIBCARES" || echo "not found" + echo -n "libgrpc: " && [ -n "$HAS_LIBGRPC" ] && echo "$HAS_LIBGRPC" || echo "not found" + echo -n "libgrpc++: " && [ -n "$HAS_LIBGRPCPP" ] && echo "$HAS_LIBGRPCPP" || echo "not found" + echo -n "libsecp256k1: " && [ -n "$HAS_SECP256K1" ] && echo "$HAS_SECP256K1" || echo "not found" + + echo "-- External libraries:" + echo -n "libethash: " && [ -n "$HAS_ETHASH" ] && echo "$HAS_ETHASH" || echo "not found" + echo -n "libkeccak: " && [ -n "$HAS_KECCAK" ] && echo "$HAS_KECCAK" || echo "not found" + echo -n "libevmc-instructions: " && [ -n "$HAS_EVMC_INSTRUCTIONS" ] && echo "$HAS_EVMC_INSTRUCTIONS" || echo "not found" + echo -n "libevmc-loader: " && [ -n "$HAS_EVMC_LOADER" ] && echo "$HAS_EVMC_LOADER" || echo "not found" + echo -n "libevmone: " && [ -n "$HAS_EVMONE" ] && echo "$HAS_EVMONE" || echo "not found" + echo -n "libspeedb: " && [ -n "$HAS_SPEEDB" ] && echo "$HAS_SPEEDB" || echo "not found" + echo -n "libSQLiteCpp: " && [ -n "$HAS_SQLITECPP" ] && echo "$HAS_SQLITECPP" || echo "not found" +elif [ "${1:-}" == "--install" ]; then + # Anti-anti-sudo prevention + if [ $(id -u) -ne 0 ]; then + echo "Please run this command as root." + exit + fi + + # Install binaries and internal libs (skip if not on an APT-based distro) + HAS_APT=$(check_exec apt) + if [ -n "$HAS_APT" ]; then + echo "-- Checking internal dependencies..." + PKGS="" + if [ -z "$HAS_GIT" ]; then PKGS+="git "; fi + if [ -z "$HAS_WGET" ]; then PKGS+="wget "; fi + if [ -z "$HAS_GCC" ] || [ -z "$HAS_GPP" ] || [ -z "$HAS_MAKE" ] || [ -z "$HAS_LD" ]; then PKGS+="build-essential "; fi + if [ -z "$HAS_AUTOCONF" ]; then PKGS+="autoconf "; fi + if [ -z "$HAS_LIBTOOL" ]; then PKGS+="libtool-bin "; fi + if [ -z "$HAS_PKGCONFIG" ]; then PKGS+="pkg-config "; fi + if [ -z "$HAS_CMAKE" ]; then PKGS+="cmake "; fi + if [ -z "$HAS_TMUX" ]; then PKGS+="tmux "; fi + if [ -z "$HAS_PROTOC" ]; then PKGS+="protobuf-compiler "; fi + if [ -z "$HAS_GRPC" ]; then PKGS+="protobuf-compiler-grpc "; fi + if [ -z "$HAS_NINJA" ]; then PKGS+="ninja-build "; fi + if [ -z "$HAS_MOLD" ]; then PKGS+="mold "; fi + if [ -z "$HAS_DOXYGEN" ]; then PKGS+="doxygen "; fi + if [ -z "$HAS_CLANGTIDY" ]; then PKGS+="clang-tidy "; fi + if [ -z "$HAS_BOOST" ]; then PKGS+="libboost-all-dev "; fi + if [ -z "$HAS_LIBSSL" ]; then PKGS+="libssl-dev "; fi + if [ -z "$HAS_ZSTD" ]; then PKGS+="libzstd-dev "; fi + if [ -z "$HAS_LZ4" ]; then PKGS+="liblz4-dev "; fi + if [ -z "$HAS_LIBCRYPTOPP" ]; then PKGS+="libcrypto++-dev "; fi + if [ -z "$HAS_LIBSCRYPT" ]; then PKGS+="libscrypt-dev "; fi + if [ -z "$HAS_LIBCARES" ]; then PKGS+="libc-ares-dev "; fi + if [ -z "$HAS_LIBGRPC" ]; then PKGS+="libgrpc-dev "; fi + if [ -z "$HAS_LIBGRPCPP" ]; then PKGS+="libgrpc++-dev "; fi + if [ -z "$HAS_SECP256K1" ]; then PKGS+="libsecp256k1-dev "; fi + if [ -n "$PKGS" ]; then + echo "-- Installing internal dependencies..." + apt-get install -y $PKGS + fi + else + echo "-- Skipping internal dependencies (non-APT-based distro, please install those manually)" + fi + + # Take note of the EVMONE Patch Path + EVMONEPATCH_PATH="$(realpath evmoneCLI11.patch)" + + # Install external libs + echo "-- Checking external dependencies..." + if [ -z "$HAS_ETHASH" ] || [ -z "$HAS_KECCAK" ]; then + echo "-- Installing ethash..." + cd /usr/local/src && git clone --depth 1 --branch "v${ETHASH_VERSION}" https://github.com/chfast/ethash + cd ethash && mkdir build && cd build + cmake -DCMAKE_INSTALL_PREFIX="/usr/local" .. + cmake --build . -- -j$(nproc) && cmake --install . + fi + if [ -z "$HAS_EVMC_INSTRUCTIONS" ] || [ -z "$HAS_EVMC_LOADER" ] || [ -z "$HAS_EVMONE" ]; then + echo "-- Installing evmone..." + cd /usr/local/src && git clone --recurse-submodules --depth 1 --branch "v${EVMONE_VERSION}" https://github.com/ethereum/evmone + cd evmone + # Apply patch located at the same path of this script called "evmoneCLI11.patch" + echo "-- Applying patch to evmone..." + git apply "$EVMONEPATCH_PATH" + mkdir build && cd build + cmake -DCMAKE_INSTALL_PREFIX="/usr/local" -DBUILD_SHARED_LIBS=ON -DEVMC_INSTALL=ON -DEVMONE_TESTING=ON .. + cmake --build . -- -j$(nproc) + cmake -DCMAKE_INSTALL_PREFIX="/usr/local" -DBUILD_SHARED_LIBS=OFF -DEVMC_INSTALL=ON .. + cmake --build . -- -j$(nproc) + ./bin/evmc-vmtester /usr/local/src/evmone/build/lib/libevmone.so && ./bin/evmone-unittests + cmake --install . + fi + if [ -z "$HAS_SPEEDB" ]; then + echo "-- Installing speedb..." + cd /usr/local/src && git clone --depth 1 --branch "speedb/v${SPEEDB_VERSION}" https://github.com/speedb-io/speedb + cd speedb && mkdir build && cd build + cmake -DCMAKE_INSTALL_PREFIX="/usr/local" -DCMAKE_BUILD_TYPE=Release \ + -DROCKSDB_BUILD_SHARED=OFF -DFAIL_ON_WARNINGS=OFF -DWITH_GFLAGS=OFF -DWITH_RUNTIME_DEBUG=OFF \ + -DWITH_BENCHMARK_TOOLS=OFF -DWITH_CORE_TOOLS=OFF -DWITH_TOOLS=OFF -DWITH_TRACE_TOOLS=OFF \ + -DWITH_LZ4=ON .. + cmake --build . -- -j$(nproc) && cmake --install . + fi + if [ -z "$HAS_SQLITECPP" ]; then + echo "-- Installing SQLiteCpp..." + cd /usr/local/src && git clone --depth 1 --branch "${SQLITECPP_VERSION}" https://github.com/SRombauts/SQLiteCpp + cd SQLiteCpp && mkdir build && cd build + cmake -DCMAKE_INSTALL_PREFIX="/usr/local" -DCMAKE_BUILD_TYPE=Release .. + cmake --build . -- -j$(nproc) && cmake --install . + fi + echo "-- Dependencies installed" +elif [ "${1:-}" == "--cleanext" ]; then + # Anti-anti-sudo prevention + if [ $(id -u) -ne 0 ]; then + echo "Please run this command as root." + exit + fi + + # Uninstall any external dependencies (+ source code repos) found in the system + if [ -n "$HAS_ETHASH" ] || [ -n "$HAS_KECCAK" ]; then + echo "-- Uninstalling ethash..." + rm -rf "/usr/local/src/ethash" + rm -rf "/usr/local/include/ethash" + rm "/usr/local/lib/libethash.a" + rm "/usr/local/lib/libethash-global-context.a" + rm "/usr/local/lib/libkeccak.a" + fi + if [ -n "$HAS_EVMC_INSTRUCTIONS" ] || [ -n "$HAS_EVMC_LOADER" ] || [ -n "$HAS_EVMONE" ]; then + echo "-- Uninstalling evmone..." + rm -rf "/usr/local/src/evmone" + rm -rf "/usr/local/include/evmc" + rm -rf "/usr/local/include/evmmax" + rm -rf "/usr/local/include/evmone" + rm "/usr/local/lib/libevmc-instructions.a" + rm "/usr/local/lib/libevmc-loader.a" + rm "/usr/local/lib/libevmone.a" + rm "/usr/local/lib/libevmone-standalone.a" + fi + if [ -n "$HAS_SPEEDB" ]; then + echo "-- Uninstalling speedb..." + rm -rf "/usr/local/src/speedb" + rm -rf "/usr/local/include/rocksdb" + rm "/usr/local/lib/libspeedb.a" + fi + if [ -n "$HAS_SQLITECPP" ]; then + echo "-- Uninstalling SQLiteCpp..." + rm "/usr/local/lib/libSQLiteCpp.a" + rm "/usr/local/lib/libsqlite3.a" + rm -rf "/usr/local/include/SQLiteCpp" + rm -rf "/usr/local/src/SQLiteCpp" + fi + echo "-- External dependencies cleaned, please reinstall them later with --install" +fi + diff --git a/scripts/evmoneCLI11.patch b/scripts/evmoneCLI11.patch new file mode 100644 index 00000000..dd39ff20 --- /dev/null +++ b/scripts/evmoneCLI11.patch @@ -0,0 +1,55 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index b7fc715..5360074 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -131,8 +131,10 @@ set(include_dir ${CMAKE_CURRENT_SOURCE_DIR}/include) + add_subdirectory(lib) + + if(EVMONE_TESTING) ++ hunter_add_package(CLI11) ++ find_package(CLI11 REQUIRED) + enable_testing() +- add_subdirectory(test) ++ add_subdirectory(test) + endif() + + +diff --git a/test/blockchaintest/CMakeLists.txt b/test/blockchaintest/CMakeLists.txt +index 6823b4c..1f6d200 100644 +--- a/test/blockchaintest/CMakeLists.txt ++++ b/test/blockchaintest/CMakeLists.txt +@@ -3,7 +3,7 @@ + # SPDX-License-Identifier: Apache-2.0 + + add_executable(evmone-blockchaintest) +-target_link_libraries(evmone-blockchaintest PRIVATE evmone evmone::statetestutils evmone-buildinfo GTest::gtest) ++target_link_libraries(evmone-blockchaintest PRIVATE evmone evmone::statetestutils evmone-buildinfo GTest::gtest CLI11::CLI11) + target_include_directories(evmone-blockchaintest PRIVATE ${evmone_private_include_dir}) + target_sources( + evmone-blockchaintest PRIVATE +diff --git a/test/eoftest/CMakeLists.txt b/test/eoftest/CMakeLists.txt +index 25df1d0..7f6d728 100644 +--- a/test/eoftest/CMakeLists.txt ++++ b/test/eoftest/CMakeLists.txt +@@ -3,7 +3,7 @@ + # SPDX-License-Identifier: Apache-2.0 + + add_executable(evmone-eoftest) +-target_link_libraries(evmone-eoftest PRIVATE evmone evmone::testutils nlohmann_json::nlohmann_json GTest::gtest) ++target_link_libraries(evmone-eoftest PRIVATE evmone evmone::testutils nlohmann_json::nlohmann_json GTest::gtest CLI11::CLI11) + target_include_directories(evmone-eoftest PRIVATE ${evmone_private_include_dir}) + target_sources( + evmone-eoftest PRIVATE +diff --git a/test/statetest/CMakeLists.txt b/test/statetest/CMakeLists.txt +index eaf85df..f156d77 100644 +--- a/test/statetest/CMakeLists.txt ++++ b/test/statetest/CMakeLists.txt +@@ -17,7 +17,7 @@ target_sources( + ) + + add_executable(evmone-statetest) +-target_link_libraries(evmone-statetest PRIVATE evmone::statetestutils evmone evmone-buildinfo GTest::gtest) ++target_link_libraries(evmone-statetest PRIVATE evmone::statetestutils evmone evmone-buildinfo GTest::gtest CLI11::CLI11) + target_include_directories(evmone-statetest PRIVATE ${evmone_private_include_dir}) + target_sources( + evmone-statetest PRIVATE diff --git a/scripts/format-code.sh b/scripts/format-code.sh index 91fad982..bea6e970 100755 --- a/scripts/format-code.sh +++ b/scripts/format-code.sh @@ -1,4 +1,4 @@ -# Copyright (c) [2023-2024] [Sparq Network] +# Copyright (c) [2023-2024] [AppLayer Developers] # This software is distributed under the MIT License. # See the LICENSE.txt file in the project root for more information. diff --git a/scripts/sonarcloud.sh b/scripts/sonarcloud.sh new file mode 100755 index 00000000..9367b3bc --- /dev/null +++ b/scripts/sonarcloud.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash + +OS=linux +ARCH="x64" +TMP_PATH=/tmp +INSTALL_PATH=/root/.sonar +VERIFY_CORRECTNESS=false + +check_status() { + exit_status=$? + if [ $exit_status -ne 0 ]; then + echo "ERROR $1" + exit $exit_status + fi +} + +realpath() { + readlink -f "$1" +} + +parse_args() { + while getopts "hv" arg; do + case $arg in + x) set -x + ;; + v) VERIFY_CORRECTNESS=true + echo "Verify correctness is set to true" + ;; + ?) exit 0 ;; + esac + done +} + +config_sonar_path() { + echo "Installation path is '${INSTALL_PATH}'" + + test ! -z "${INSTALL_PATH}" + check_status "Empty installation path specified" + + if [[ ! -e "${INSTALL_PATH}" ]]; then + mkdir -p "${INSTALL_PATH}" + check_status "Failed to create non-existing installation path '${INSTALL_PATH}'" + fi + + ABSOLUTE_INSTALL_PATH=$(realpath "${INSTALL_PATH}") + echo "Absolute installation path is '${ABSOLUTE_INSTALL_PATH}'" + + test -d "${INSTALL_PATH}" + check_status "Installation path '${INSTALL_PATH}' is not a directory (absolute path is '${ABSOLUTE_INSTALL_PATH}')" + + test -r "${INSTALL_PATH}" + check_status "Installation path '${INSTALL_PATH}' is not readable (absolute path is '${ABSOLUTE_INSTALL_PATH}')" + + test -w "${INSTALL_PATH}" + check_status "Installation path '${INSTALL_PATH}' is not writeable (absolute path is '${ABSOLUTE_INSTALL_PATH}')" +} + +set_sonar_vars() { + SONAR_HOST_URL=${SONAR_HOST_URL:-https://sonarcloud.io} + SONAR_SCANNER_NAME="sonar-scanner" + SONAR_SCANNER_SUFFIX="linux-x64" + SONAR_SCANNER_VERSION=$(curl -sSL -H "Accept: application/vnd.github+json" \ + https://api.github.com/repos/SonarSource/sonar-scanner-cli/releases/latest | jq -r '.tag_name') + check_status "Failed to fetch latest sonar-scanner version from GitHub API" + SONAR_SCANNER_DIR="${INSTALL_PATH}/sonar-scanner-${SONAR_SCANNER_VERSION}-${SONAR_SCANNER_SUFFIX}" + SONAR_SCANNER_URL="https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SONAR_SCANNER_VERSION}-${OS}-${ARCH}.zip" + check_status "Failed to download ${OS} ${ARCH} sonar-scanner checksum from '${SONAR_SCANNER_URL}'" + BUILD_WRAPPER_SUFFIX="linux-x86" + BUILD_WRAPPER_NAME="build-wrapper-linux-x86-64" + BUILD_WRAPPER_DIR="${INSTALL_PATH}/build-wrapper-${BUILD_WRAPPER_SUFFIX}" + BUILD_WRAPPER_URL=${SONAR_HOST_URL}/static/cpp/build-wrapper-${BUILD_WRAPPER_SUFFIX}.zip + + echo "sonar-scanner-version=${SONAR_SCANNER_VERSION}" + echo "sonar-scanner-url-${OS}-${ARCH}=${SONAR_SCANNER_URL}" + echo "sonar-scanner-dir=${SONAR_SCANNER_DIR}" + echo "sonar-scanner-bin=${SONAR_SCANNER_DIR}/bin/${SONAR_SCANNER_NAME}" + echo "build-wrapper-url=${SONAR_HOST_URL}/static/cpp/build-wrapper-${BUILD_WRAPPER_SUFFIX}.zip" + echo "build-wrapper-dir=${BUILD_WRAPPER_DIR}" + echo "build-wrapper-bin=${BUILD_WRAPPER_DIR}/${BUILD_WRAPPER_NAME}" +} + +fetch_sonar() { + echo "Downloading '${SONAR_SCANNER_URL}'" + curl -sSLo "${TMP_PATH}/sonar-scanner.zip" "${SONAR_SCANNER_URL}" + check_status "Failed to download '${SONAR_SCANNER_URL}'" + echo "Downloading '${BUILD_WRAPPER_URL}'" + curl -sSLo "${TMP_PATH}/build-wrapper-linux-x86.zip" "${BUILD_WRAPPER_URL}" + check_status "Failed to download '${BUILD_WRAPPER_URL}'" +} + +decompress_sonar() { + echo "Decompressing" + unzip -o -d "${INSTALL_PATH}" "${TMP_PATH}/sonar-scanner.zip" + check_status "Failed to unzip the archive into '${INSTALL_PATH}'" + unzip -o -d "${INSTALL_PATH}" "${TMP_PATH}/build-wrapper-linux-x86.zip" + check_status "Failed to unzip the archive into '${INSTALL_PATH}'" +} + +main() { + parse_args "$@" + set_sonar_vars + config_sonar_path + fetch_sonar + decompress_sonar +} + +main "$@" diff --git a/sonar-project.properties b/sonar-project.properties index a428eba3..d05d2fb0 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,6 +1,6 @@ -sonar.projectKey=SparqNet_orbitersdk-cpp_AY16kEq6lKE0qFQROdKZ -sonar.organization=SparqNet -sonar.cfamily.threads=4 +sonar.projectKey=AppLayerLabs_bdk-cpp_ccf13a2c-7f2c-4116-b2fe-a974ebed07ff +sonar.organization=AppLayerLabs +sonar.cfamily.threads=12 sonar.projectVersion=1.0 # ===================================================== @@ -11,5 +11,7 @@ sonar.projectVersion=1.0 sonar.sources=src sonar.sourceEncoding=UTF-8 +sonar.c.file.suffixes=- +sonar.cpp.file.suffixes=.cc,.cpp,.cxx,.c++,.hh,.hpp,.hxx,.h++,.ipp,.c,.h sonar.cfamily.cpp23.enabled=true sonar.exclusions=src/libs/**, tests/** diff --git a/src/bins/CMakeLists.txt b/src/bins/CMakeLists.txt new file mode 100644 index 00000000..57583282 --- /dev/null +++ b/src/bins/CMakeLists.txt @@ -0,0 +1,8 @@ +add_subdirectory(bdkd) +add_subdirectory(bdkd-tests) +add_subdirectory(bdkd-discovery) +add_subdirectory(networkdeployer) +add_subdirectory(contractabigenerator) +add_subdirectory(network-sim) +add_subdirectory(faucet-api) +add_subdirectory(btv-server) \ No newline at end of file diff --git a/src/bins/bdkd-discovery/CMakeLists.txt b/src/bins/bdkd-discovery/CMakeLists.txt new file mode 100644 index 00000000..16c2fc38 --- /dev/null +++ b/src/bins/bdkd-discovery/CMakeLists.txt @@ -0,0 +1,17 @@ +# Compile and link the Discovery Node test executable if set to build it +if (BUILD_DISCOVERY) + add_executable(bdkd-discovery "main.cpp") + + add_dependencies(bdkd-discovery bdk_lib) + + target_include_directories(bdkd-discovery PRIVATE + bdk_lib ${OPENSSL_INCLUDE_DIR} ${ETHASH_INCLUDE_DIR} ${KECCAK_INCLUDE_DIR} + ${SPEEDB_INCLUDE_DIR} ${SECP256K1_INCLUDE_DIR} + ) + + target_link_libraries(bdkd-discovery + bdk_lib ${SPEEDB_LIBRARY} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} + ${ZLIB_LIBRARIES} ${SECP256K1_LIBRARY} ${ETHASH_LIBRARY} ${KECCAK_LIBRARY} + ) +endif() + diff --git a/src/bins/bdkd-discovery/main.cpp b/src/bins/bdkd-discovery/main.cpp new file mode 100644 index 00000000..e52fc28a --- /dev/null +++ b/src/bins/bdkd-discovery/main.cpp @@ -0,0 +1,77 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include +#include +#include + +#include "src/net/p2p/managerdiscovery.h" + +#include "src/utils/clargs.h" +#include "src/utils/options.h" + +std::condition_variable cv; +std::mutex cv_m; +int signalCaught = 0; + +void signalHandler(int signum) { + { + std::unique_lock lk(cv_m); + Utils::safePrint("Signal caught: " + Utils::getSignalName(signum)); + signalCaught = signum; + } + cv.notify_one(); +} + +/// Executable with a discovery node for the given Options default chain. +int main(int argc, char* argv[]) { + Log::logToCout = true; + Utils::safePrint("bdkd-discovery: Blockchain Development Kit discovery node daemon"); + std::signal(SIGINT, signalHandler); + std::signal(SIGHUP, signalHandler); + + // Parse command-line options + ProcessOptions opt = parseCommandLineArgs(argc, argv, BDKTool::DISCOVERY_NODE); + + // Select a default log level for this program if none is specified + if (opt.logLevel == "") opt.logLevel = "INFO"; + + // Apply selected process options + if (!applyProcessOptions(opt)) return 1; + + // Start the discovery node + Utils::safePrint("Main thread starting node..."); + // Local binary path + /blockchain + std::string blockchainPath = std::filesystem::current_path().string() + std::string("/discoveryNode"); + const auto options = Options::fromFile(blockchainPath); + auto p2p = std::make_unique(options.getP2PIp(), options); + p2p->start(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + p2p->startDiscovery(); + + // Main thread waits for a non-zero signal code to be raised and caught + Utils::safePrint("Main thread waiting for interrupt signal..."); + int exitCode = 0; + { + std::unique_lock lk(cv_m); + cv.wait(lk, [] { return signalCaught != 0; }); + exitCode = signalCaught; + } + Utils::safePrint("Main thread stopping due to interrupt signal [" + Utils::getSignalName(exitCode) + "], shutting down node..."); + + // Shut down the node + SLOGINFO("Received signal " + std::to_string(exitCode)); + Utils::safePrint("Main thread stopping node..."); + p2p->stopDiscovery(); + Utils::safePrint("Main thread shutting down..."); + p2p.reset(); + + // Return the signal code + Utils::safePrint("Main thread exiting with code " + std::to_string(exitCode) + "."); + return exitCode; +} + diff --git a/src/bins/bdkd-tests/CMakeLists.txt b/src/bins/bdkd-tests/CMakeLists.txt new file mode 100644 index 00000000..2fa369d9 --- /dev/null +++ b/src/bins/bdkd-tests/CMakeLists.txt @@ -0,0 +1,18 @@ +# Compile and link the test executable if set to build it +if (BUILD_TESTS) + add_executable(bdkd-tests ${TESTS_HEADERS} ${TESTS_SOURCES}) + + add_dependencies(bdkd-tests bdk_lib) + + target_include_directories(bdkd-tests PRIVATE + bdk_lib ${OPENSSL_INCLUDE_DIR} ${ETHASH_INCLUDE_DIR} ${KECCAK_INCLUDE_DIR} + ${SPEEDB_INCLUDE_DIR} ${SECP256K1_INCLUDE_DIR} + ) + + target_link_libraries(bdkd-tests PRIVATE + bdk_lib catch2 + ${SPEEDB_LIBRARY} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} + ${SECP256K1_LIBRARY} ${ETHASH_LIBRARY} ${KECCAK_LIBRARY} + ) +endif() + diff --git a/src/bins/bdkd/CMakeLists.txt b/src/bins/bdkd/CMakeLists.txt new file mode 100644 index 00000000..3766f460 --- /dev/null +++ b/src/bins/bdkd/CMakeLists.txt @@ -0,0 +1,15 @@ +# Compile and link the executable +add_executable(bdkd "main.cpp") + +add_dependencies(bdkd bdk_lib) + +target_include_directories(bdkd PRIVATE + bdk_lib ${OPENSSL_INCLUDE_DIR} ${ETHASH_INCLUDE_DIR} ${KECCAK_INCLUDE_DIR} + ${SPEEDB_INCLUDE_DIR} ${SECP256K1_INCLUDE_DIR} +) + +target_link_libraries(bdkd + bdk_lib ${SPEEDB_LIBRARY} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} + ${SECP256K1_LIBRARY} ${ETHASH_LIBRARY} ${KECCAK_LIBRARY} +) + diff --git a/src/bins/bdkd/main.cpp b/src/bins/bdkd/main.cpp new file mode 100644 index 00000000..6b57328e --- /dev/null +++ b/src/bins/bdkd/main.cpp @@ -0,0 +1,75 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include +#include + +#include +#include + +#include "src/core/blockchain.h" +#include "src/utils/clargs.h" + +#include "src/utils/logger.h" + +std::unique_ptr blockchain = nullptr; + +std::condition_variable cv; +std::mutex cv_m; +int signalCaught = 0; + +void signalHandler(int signum) { + { + std::unique_lock lk(cv_m); + Utils::safePrint("Signal caught: " + Utils::getSignalName(signum)); + signalCaught = signum; + } + cv.notify_one(); +} + +int main(int argc, char* argv[]) { + Log::logToCout = true; + Utils::safePrint("bdkd: Blockchain Development Kit full node daemon"); + std::signal(SIGINT, signalHandler); + std::signal(SIGHUP, signalHandler); + + // Parse command-line options + ProcessOptions opt = parseCommandLineArgs(argc, argv, BDKTool::FULL_NODE); + + // Select a default log level for this program if none is specified + if (opt.logLevel == "") opt.logLevel = "INFO"; + + // Apply selected process options + if (!applyProcessOptions(opt)) return 1; + + // Start the blockchain syncing engine. + Utils::safePrint("Main thread starting node..."); + std::string blockchainPath = std::filesystem::current_path().string() + std::string("/blockchain"); + blockchain = std::make_unique(blockchainPath); + blockchain->start(); + + // Main thread waits for a non-zero signal code to be raised and caught + Utils::safePrint("Main thread waiting for interrupt signal..."); + int exitCode = 0; + { + std::unique_lock lk(cv_m); + cv.wait(lk, [] { return signalCaught != 0; }); + exitCode = signalCaught; + } + Utils::safePrint("Main thread stopping due to interrupt signal [" + Utils::getSignalName(exitCode) + "], shutting down node..."); + + // Shut down the node + SLOGINFO("Received signal " + std::to_string(exitCode)); + Utils::safePrint("Main thread stopping node..."); + blockchain->stop(); + Utils::safePrint("Main thread shutting down..."); + blockchain = nullptr; // Destroy the blockchain object, calling the destructor of every module and dumping to DB. + + // Return the signal code + Utils::safePrint("Main thread exiting with code " + std::to_string(exitCode) + "."); + return exitCode; +} diff --git a/src/bins/btv-server/CMakeLists.txt b/src/bins/btv-server/CMakeLists.txt new file mode 100644 index 00000000..aa079845 --- /dev/null +++ b/src/bins/btv-server/CMakeLists.txt @@ -0,0 +1,47 @@ +if (BUILD_BTVSERVER) + add_library(btv_server_lib STATIC + ${CMAKE_SOURCE_DIR}/src/bins/btv-server/src/utils.h + ${CMAKE_SOURCE_DIR}/src/bins/btv-server/src/httpclient.h + ${CMAKE_SOURCE_DIR}/src/bins/btv-server/src/manager.h + ${CMAKE_SOURCE_DIR}/src/bins/btv-server/src/socketlistener.h + ${CMAKE_SOURCE_DIR}/src/bins/btv-server/src/websocketsession.h + ${CMAKE_SOURCE_DIR}/src/bins/btv-server/src/websocketserver.h + ${CMAKE_SOURCE_DIR}/src/bins/btv-server/src/utils.cpp + ${CMAKE_SOURCE_DIR}/src/bins/btv-server/src/httpclient.cpp + ${CMAKE_SOURCE_DIR}/src/bins/btv-server/src/manager.cpp + ${CMAKE_SOURCE_DIR}/src/bins/btv-server/src/socketlistener.cpp + ${CMAKE_SOURCE_DIR}/src/bins/btv-server/src/websocketsession.cpp + ${CMAKE_SOURCE_DIR}/src/bins/btv-server/src/websocketserver.cpp + ) + + target_include_directories(btv_server_lib PRIVATE + ${CMAKE_SOURCE_DIR}/include ${OPENSSL_INCLUDE_DIR} bdk_lib + ${ETHASH_INCLUDE_DIR} ${KECCAK_INCLUDE_DIR} + ${SPEEDB_INCLUDE_DIR} ${SECP256K1_INCLUDE_DIR} + ) + + target_link_libraries(btv_server_lib PRIVATE bdk_lib + ${CRYPTOPP_LIBRARIES} ${SCRYPT_LIBRARY} ${SECP256K1_LIBRARY} + ${ETHASH_LIBRARY} ${KECCAK_LIBRARY} ${SPEEDB_LIBRARY} + ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} bdk_lib + ) + + # Compile and link the btv-server executable + add_executable(btv-server "main.cpp") + + add_dependencies(btv-server bdk_lib btv_server_lib) + + target_include_directories(btv-server PRIVATE + bdk_lib btv_server_lib ${OPENSSL_INCLUDE_DIR} + ${ETHASH_INCLUDE_DIR} ${KECCAK_INCLUDE_DIR} + ${SPEEDB_INCLUDE_DIR} ${SECP256K1_INCLUDE_DIR} + ) + + target_link_libraries(btv-server + bdk_lib btv_server_lib + ${SPEEDB_LIBRARY} ${SNAPPY_LIBRARY} ${Boost_LIBRARIES} + ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${SECP256K1_LIBRARY} + ${ETHASH_LIBRARY} ${KECCAK_LIBRARY} + ) + +endif () \ No newline at end of file diff --git a/src/bins/btv-server/main.cpp b/src/bins/btv-server/main.cpp new file mode 100644 index 00000000..27709d6b --- /dev/null +++ b/src/bins/btv-server/main.cpp @@ -0,0 +1,22 @@ +#include "src/manager.h" + + +std::unique_ptr manager = nullptr; + +void signalHandler(int signum) { + BTVServer::Printer::safePrint("Signal caught: " + Utils::getSignalName(signum)); + manager.reset(); +} + +int main() { + std::signal(SIGINT, signalHandler); + std::signal(SIGHUP, signalHandler); + Log::logToCout = true; + manager = std::make_unique(); + BTVServer::Printer::safePrint("Starting Build The Void Websocket Server..."); + manager->start(); + BTVServer::Printer::safePrint("Exitting Build The Void Websocket Server..."); + + + return 0; +} \ No newline at end of file diff --git a/src/bins/btv-server/src/httpclient.cpp b/src/bins/btv-server/src/httpclient.cpp new file mode 100644 index 00000000..1a47598a --- /dev/null +++ b/src/bins/btv-server/src/httpclient.cpp @@ -0,0 +1,67 @@ +#include "httpclient.h" +#include "manager.h" + +namespace BTVServer { + HTTPSemiSyncClient::HTTPSemiSyncClient(const std::string& host, const std::string& port, net::io_context& ioc_, Manager& manager) + : host(host), port(port), resolver(ioc_), stream(ioc_), strand_(ioc_.get_executor()), manager(manager) {} + + // TODO: either close() shouldn't be throwing, or the dtor shouldn't be calling it + HTTPSemiSyncClient::~HTTPSemiSyncClient() { if (stream.socket().is_open()) this->close(); } + + void HTTPSemiSyncClient::connect() { + boost::system::error_code ec; + auto const results = resolver.resolve(host, port, ec); + if (ec) throw DynamicException("Error while resolving the HTTP Client: " + ec.message()); + stream.connect(results, ec); + if (ec) throw DynamicException("Error while connecting the HTTP Client: " + ec.message()); + Printer::safePrint("HTTPSemiSyncClient connected to " + host + ":" + port); + } + + void HTTPSemiSyncClient::close() { + boost::system::error_code ec; + stream.socket().shutdown(tcp::socket::shutdown_both, ec); + if (ec) throw DynamicException("Error while closing the HTTP Client: " + ec.message()); + } + + std::string HTTPSemiSyncClient::makeHTTPRequestInternal(const std::shared_ptr reqBody) { + namespace http = boost::beast::http; // from + + boost::system::error_code ec; + // Set up an HTTP POST/GET request message + http::request req{ http::verb::post , "/", 11}; + + req.set(http::field::host, host); + req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + req.set(http::field::accept, "application/json"); + req.set(http::field::content_type, "application/json"); + req.body() = *reqBody; + req.prepare_payload(); + + // Send the HTTP request to the remote host + http::write(stream, req, ec); + if (ec) throw DynamicException("Error while writing the HTTP request: " + ec.message()); + + boost::beast::flat_buffer buffer; + // Declare a container to hold the response + http::response res; + + // Receive the HTTP response + http::read(stream, buffer, res, ec); + if (ec) throw DynamicException("Error while reading the HTTP response: " + ec.message() + " " + std::to_string(ec.value())); + + // Write only the body answer to output + return { + boost::asio::buffers_begin(res.body().data()), + boost::asio::buffers_end(res.body().data()) + }; + } + + void HTTPSemiSyncClient::makeHTTPRequest(std::string &&reqBody) { + // DO NOT forget to post to the strand!!! + // each write/read should be SEQUENTIAL + boost::asio::post(strand_, [this, reqBodyPtr = std::make_shared(std::move(reqBody))]() { + auto response = this->makeHTTPRequestInternal(reqBodyPtr); + this->manager.handleHTTPResponse(response); + }); + } +} \ No newline at end of file diff --git a/src/bins/btv-server/src/httpclient.h b/src/bins/btv-server/src/httpclient.h new file mode 100644 index 00000000..c4ebf2d3 --- /dev/null +++ b/src/bins/btv-server/src/httpclient.h @@ -0,0 +1,50 @@ +#ifndef HTTPASYNCCLIENT_H +#define HTTPASYNCCLIENT_H + +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" + +namespace beast = boost::beast; // from +namespace http = beast::http; // from +namespace websocket = beast::websocket; // from +namespace net = boost::asio; // from +namespace ssl = boost::asio::ssl; // from +using tcp = boost::asio::ip::tcp; // from + +namespace BTVServer { + // Forward declaration + class Manager; + class HTTPSemiSyncClient { + private: + const std::string host; + const std::string port; + Manager& manager; + tcp::resolver resolver; + beast::tcp_stream stream; + net::strand strand_; + std::string makeHTTPRequestInternal(const std::shared_ptr reqBody); + uint64_t highestBlock = 0; + + public: + HTTPSemiSyncClient(const std::string& host, const std::string& port, net::io_context& ioc_, Manager& manager); + ~HTTPSemiSyncClient() noexcept; + HTTPSemiSyncClient(const HTTPSemiSyncClient&) = delete; + HTTPSemiSyncClient& operator=(const HTTPSemiSyncClient&) = delete; + HTTPSemiSyncClient(HTTPSemiSyncClient&&) = delete; + HTTPSemiSyncClient& operator=(HTTPSemiSyncClient&&) = delete; + + void connect(); + void close(); + + void makeHTTPRequest(std::string&& reqBody); + }; +} + + +#endif // HTTPASYNCCLIENT_H \ No newline at end of file diff --git a/src/bins/btv-server/src/manager.cpp b/src/bins/btv-server/src/manager.cpp new file mode 100644 index 00000000..e2504692 --- /dev/null +++ b/src/bins/btv-server/src/manager.cpp @@ -0,0 +1,336 @@ +#include "manager.h" + +#include "bins/network-sim/src/httpclient.h" +#include "contract/abi.h" +#include "net/http/jsonrpc/methods.h" + +namespace BTVServer { + Manager::Manager() : world_(), ioc_(8), server_(*this, ioc_, tcp::endpoint(tcp::v4(), 29345)), httpClient_("149.112.84.202", "8095", ioc_, *this) {} + Manager::~Manager() { + this->server_.close(); + this->ioc_.stop(); + Utils::safePrint("Manager destroyed"); + this->httpClient_.close(); + + } + void Manager::handleHTTPResponse(const std::string& reqBody) { + static auto lastTimeResponded = std::chrono::system_clock::now(); + // ONLY USED TO GET LOGS FROM THE SERVER! + // {"jsonrpc":"2.0","id":1,"result":[{"address":"0x30c37f6b1d6321c4398238525046c604c7b26150","blockHash":"0x02bb902e386b9b8baf792294f158897f0677a0d114105f886b9dd73ce8cec7c9","blockNumber":"0x0000000000002621","data":"0x0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000004","logIndex":"0x0000000000000000","removed":false,"topics":["0x88c1435105c4190f4c8be13e5dbc689ebf4dbec75a17e63a51697ce761b5a1d2"],"transactionHash":"0x84cb3ac1ac98c1f481e3fa861230e100696896a04275d7af850dc90ff9930618","transactionIndex":"0x0000000000000000"}]} + json response = json::parse(reqBody); + /* + void PlayerMoved(const EventParam& playerId, const EventParam& x, const EventParam& y, const EventParam& z) { + this->emitEvent("PlayerMoved", std::make_tuple(playerId, x, y, z)); + } + void PlayerLogin(const EventParam& playerId, const EventParam& x, const EventParam& y, const EventParam& z) { + this->emitEvent("PlayerLogin", std::make_tuple(playerId, x, y, z)); + } + void PlayerLogout(const EventParam& playerId) { + this->emitEvent("PlayerLogout", std::make_tuple(playerId)); + } + void BlockChanged(const EventParam& playerId, const EventParam& x, const EventParam& y, const EventParam& z, const EventParam& blockType, const EventParam& timestamp) { + this->emitEvent("BlockChanged", std::make_tuple(playerId, x, y, z, blockType, timestamp)); + } + void ClaimedEnergy(const EventParam& playerId, const EventParam& value) { + this->emitEvent("ClaimedEnergy", std::make_tuple(playerId, value)); + } + void PlayerDead(const EventParam& playerId) { + this->emitEvent("PlayerDead", std::make_tuple(playerId)); + } + */ + static Hash PlayerMovedTopic = ABI::EventEncoder::encodeSignature("PlayerMoved"); + static Hash PlayerLoginTopic = ABI::EventEncoder::encodeSignature("PlayerLogin"); + static Hash PlayerLogoutTopic = ABI::EventEncoder::encodeSignature("PlayerLogout"); + static Hash BlockChangedTopic = ABI::EventEncoder::encodeSignature("BlockChanged"); + static Hash ClaimedEnergyTopic = ABI::EventEncoder::encodeSignature("ClaimedEnergy"); + static Hash PlayerDeadTopic = ABI::EventEncoder::encodeSignature("PlayerDead"); + json jsonRpcResponse; + jsonRpcResponse["id"] = response.at("id"); + jsonRpcResponse["jsonrpc"] = "2.0"; + jsonRpcResponse["result"] = json::array(); + try { + // For every object within the logs, we need to construct its respective JSON object and insert it into the result array + for (const auto& log : response.at("result")) { + // We need to replace the lastProcessedBlock based on the latest provided by the logs we requested + uint64_t blockNumber = Utils::fromBigEndian(Hex::toBytes(log.at("blockNumber").get())); + if (blockNumber > this->lastProcessedBlock) { + this->lastProcessedBlock = blockNumber; + } + json eventUpdate; + // Now we just need to separate the logs by the topics previously defined + // We dont need to double check anything because we are interested in ALL logs + // and our requested is correctly built to only get logs from the BTV contract + Hash logTopic = Hash(Hex::toBytes(log.at("topics").at(0).get())); + if (logTopic == PlayerMovedTopic) { + auto data = ABI::Decoder::decodeData(Hex::toBytes(log.at("data").get())); + const auto& [playerId, x, y, z] = data; + eventUpdate = { + {"method", "PlayerMoved"}, + {"playerId", playerId}, + {"x", x}, + {"y", y}, + {"z", z} + }; + } + if (logTopic == PlayerLoginTopic) { + auto data = ABI::Decoder::decodeData(Hex::toBytes(log.at("data").get())); + const auto& [playerId, x, y, z] = data; + eventUpdate = { + {"method", "PlayerLogin"}, + {"playerId", playerId}, + {"x", x}, + {"y", y}, + {"z", z} + }; + } + if (logTopic == PlayerLogoutTopic) { + auto data = ABI::Decoder::decodeData(Hex::toBytes(log.at("data").get())); + const auto& [playerId] = data; + eventUpdate = { + {"method", "PlayerLogout"}, + {"playerId", playerId} + }; + } + if (logTopic == BlockChangedTopic) { + auto data = ABI::Decoder::decodeData(Hex::toBytes(log.at("data").get())); + const auto& [playerId, x, y, z, blockType, timestamp] = data; + eventUpdate = { + {"method", "BlockChanged"}, + {"playerId", playerId}, + {"x", x}, + {"y", y}, + {"z", z}, + {"blockType", blockType}, + {"timestamp", timestamp} + }; + // We also need to update the world! + std::unique_lock lock(this->worldMutex_); + auto block = this->world_.getBlock(BTVUtils::WorldBlockPos{x, y, z}); + block->setBlockType(static_cast(blockType)); + block->setPlacer(playerId); + block->setModificationTimestamp(timestamp); + } + if (logTopic == ClaimedEnergyTopic) { + auto data = ABI::Decoder::decodeData(Hex::toBytes(log.at("data").get())); + const auto& [playerId, value] = data; + eventUpdate = { + {"method", "ClaimedEnergy"}, + {"playerId", playerId}, + {"value", value.str()} // VALUE IS A STRING BECAUSE JSON CANNOT SUPPORT UINT256_T!!! PAY ATTENTION TO THIS + }; + } + if (logTopic == PlayerDeadTopic) { + auto data = ABI::Decoder::decodeData(Hex::toBytes(log.at("data").get())); + const auto& [playerId] = data; + eventUpdate = { + {"method", "PlayerDead"}, + {"playerId", playerId} + }; + } + jsonRpcResponse["result"].push_back(eventUpdate); + } + } catch (std::exception &e) { + Printer::safePrint("Error while processing response: " + std::string(e.what()) + " with message " + reqBody); + } + // Now we need to broadcast the update object to all players! + this->broadcastTooAllPlayers(jsonRpcResponse); + // Lmao lets request the logs again! + // We wait at least 100ms + std::this_thread::sleep_until(lastTimeResponded + std::chrono::milliseconds(100)); + lastTimeResponded = std::chrono::system_clock::now(); + this->httpClient_.makeHTTPRequest(makeRequestMethod("eth_getLogs", json::array({ + { + {"address", btvContractAddress_.hex(true)}, + {"fromBlock", Hex::fromBytes(Utils::uintToBytes(this->lastProcessedBlock), true).forRPC()}, + {"toBlock", "latest"} + } + })).dump()); + } + void Manager::registerPlayer(const uint64_t& id, std::weak_ptr session) { + this->players_.insert({id, session}); + } + void Manager::removePlayer(const uint64_t& id) { + this->players_.erase(id); + } + void Manager::handlePlayerRequest(std::weak_ptr session, const std::string& msg) { + // Post this to the io_context + this->ioc_.post([this, session, msg]() { + try { + json j = json::parse(msg); + if (!j.contains("method")) { + throw std::runtime_error("Method not found"); + } + if (!j.at("method").is_string()) { + throw std::runtime_error("Method is not a string"); + } + if (j.at("method").get() == "getChunks") { + if (!j.contains("params")) { + throw std::runtime_error("Params not found"); + } + if (!j.at("params").is_array()) { + throw std::runtime_error("Params is not an array"); + } + json response = { + {"jsonrpc", "2.0"}, + {"id", j.at("id")}, + {"result", json::array()} + }; + for (const auto& param : j.at("params")) { + if (!param.contains("x") || !param.contains("y")) { + throw std::runtime_error("Param does not contain x or y"); + } + if (!param.at("x").is_number_integer() || !param.at("y").is_number_integer()) { + throw std::runtime_error("Param x or y is not an integer"); + } + int32_t x = param.at("x").get(); + int32_t y = param.at("y").get(); + if (x < -32 || x > 31 || y < -32 || y > 31) { + throw std::runtime_error("Invalid x or y"); + } + std::shared_lock lock(this->worldMutex_); + BTVUtils::Chunk chunk = *this->world_.getChunk({x, y}); + json chunkJson = { + {"x", x}, + {"y", y}, + {"data", Hex::fromBytes(chunk.serialize(), true).get()} + }; + response["result"].push_back(chunkJson); + } + session.lock()->write(response.dump()); + } else { + throw std::runtime_error("Method not allowed"); + } + } catch (std::exception &e) { + Printer::safePrint("Error while processing player request: " + std::string(e.what()) + " with message " + msg + " with size " + std::to_string(msg.size()) + " disconnecting player"); + if (auto realSession = session.lock()) { + realSession->stop(); + this->players_.erase(realSession->getId()); + } + } + }); + } + void Manager::start() { + // To properly start, we need to start querying the blockchain for the logs from BTV contract + // For that, we need to initialize the HTTP client. + Printer::safePrint("Are you COMPLETELY sure that the blockchain is NOT moving?"); + std::string answer; + std::cin >> answer; + this->loadWorld(); + + this->httpClient_.connect(); + this->server_.setup(); + // Now we need to initialize the thread vector that will be executing the io_context + std::vector threads; + threads.reserve(7); // 7 because the main thread will also be running the io_context + for (int i = 0; i < 7; i++) { + threads.emplace_back([this]() { + Printer::safePrint("Running io_context"); + this->ioc_.run(); + Printer::safePrint("io_context has stopped"); + }); + } + // Before running the io_context in the main thread, we MUST start requesting for the logs! Otherwise it will be a bad time for us + Printer::safePrint("Making the first request for the logs"); + this->httpClient_.makeHTTPRequest(makeRequestMethod("eth_getLogs", json::array({ + { + {"address", btvContractAddress_.hex(true)}, + {"fromBlock", Hex::fromBytes(Utils::uintToBytes(this->lastProcessedBlock), true).forRPC()}, + {"toBlock", "latest"} + } + })).dump()); + Printer::safePrint("Request sent"); + // Now we need to run the io_context on the main thread + this->ioc_.run(); + Printer::safePrint("Joining all other threads"); + for (auto& thread : threads) { + thread.join(); + } + Printer::safePrint("Manager is successfully shutting down"); + } + + void Manager::loadWorld() { + Printer::safePrint("Connecting to the blockchain..."); + HTTPSyncClient client("149.112.84.202", "8095"); + client.connect(); + Printer::safePrint("Connected"); + // We need to request ALL the chunks from the blockchain + // That means a range (x, y) from (-32, -32) to (31, 31) + auto now = std::chrono::system_clock::now(); + + for (int x = -32; x < 32; x++) { + Printer::safePrint("Requesting chunks for x = " + std::to_string(x) + " total Y: 64"); + json requestArr = json::array(); + for (int y = -32; y < 32; y++) { + requestArr.push_back(buildGetChunkRequest(x, y, y + 32)); + } + auto response = client.makeHTTPRequest(requestArr.dump()); + + json chunkRequestResponse = json::parse(response); + // Create a range of -32 to 31 numbers (number -> bool) so we can check if ALL + // chunks were successfully deserialized + std::map chunksReceived; + for (int y = -32; y < 32; y++) { + chunksReceived[y] = false; + } + assert(chunksReceived.size() == 64); + assert(chunkRequestResponse.is_array()); + assert(chunkRequestResponse.size() == 64); + for (const auto& chunkResponse : chunkRequestResponse) { + uint64_t chunkId = chunkResponse["id"].get(); + auto data = ABI::Decoder::decodeData(Hex::toBytes(chunkResponse["result"].get())); + const auto& chunkData = std::get<0>(data); + *this->world_.getChunk({x, chunkId - 32}) = BTVUtils::Chunk::deserialize(chunkData); + if (this->world_.getChunk({x, chunkId - 32})->serialize() != chunkData) { + Printer::safePrint("Chunk (" + std::to_string(x) + ", " + std::to_string(chunkId - 32) + ") does not match!"); + throw std::runtime_error("Chunk does not match!"); + } + chunksReceived.at(chunkId - 32) = true; + } + for (const auto& [y, received] : chunksReceived) { + if (!received) { + Printer::safePrint("Chunk (" + std::to_string(x) + ", " + std::to_string(y) + ") was not received"); + throw std::runtime_error("Chunk was not received"); + } + } + } + auto after = std::chrono::system_clock::now(); + Printer::safePrint("Time taken to request all 4096 chunks: " + std::to_string(std::chrono::duration_cast(after - now).count()) + "ms"); + Printer::safePrint("Getting the latest block from the network"); + auto latestBlock = client.makeHTTPRequest(makeRequestMethod("eth_blockNumber", json::array()).dump()); + // Result will have the number value hex encoded + this->lastProcessedBlock = Utils::fromBigEndian(Hex::toBytes(json::parse(latestBlock)["result"].get())); + Utils::safePrint("Latest block: " + std::to_string(this->lastProcessedBlock)); + client.close(); + } + + void Manager::broadcastTooAllPlayers(const json &msg) { + std::string message = msg.dump(); + this->players_.visit_all([message](auto& player) { + if (auto session = player.second.lock()) { + Printer::safePrint("Broadcast to: " + std::to_string(player.first)); + session->write(message); + } + }); + // Clear up the map from bad objects! + this->players_.erase_if([](auto& player) { + return player.second.expired(); + }); + } + + + json Manager::buildGetChunkRequest(const int32_t& x, const int32_t& y, const uint64_t& id) { + Functor getChunksFunctor = ABI::FunctorEncoder::encode("getChunk"); + Bytes data; + Utils::appendBytes(data, UintConv::uint32ToBytes(getChunksFunctor.value)); + Utils::appendBytes(data, ABI::Encoder::encodeData(x, y)); + + json req = { + {"to", btvContractAddress_.hex(true)}, + {"data", Hex::fromBytes(data, true).get()} + }; + return makeRequestMethod("eth_call", + json::array( {req}), id + ); + } +} + diff --git a/src/bins/btv-server/src/manager.h b/src/bins/btv-server/src/manager.h new file mode 100644 index 00000000..2f65ed07 --- /dev/null +++ b/src/bins/btv-server/src/manager.h @@ -0,0 +1,50 @@ +#ifndef BTVSERVER_MANAGER_H +#define BTVSERVER_MANAGER_H + + +#include "websocketserver.h" +#include "../../../contract/templates/btvcommon.h" +#include "websocketsession.h" +#include "httpclient.h" +#include + +namespace BTVServer { + class Manager { + private: + /** + * World class + * - 1024x1024 area => 64x64 chunks + * - Each chunk is 16x64x16 + * - chunk coords in range [-32..31] + */ + BTVUtils::World world_; + net::io_context ioc_; + WebsocketServer server_; + HTTPSemiSyncClient httpClient_; + Address btvContractAddress_ = Address(Hex::toBytes("0x30C37F6B1d6321C4398238525046c604C7b26150")); + std::shared_mutex worldMutex_; + boost::concurrent_flat_map> players_; + uint64_t lastProcessedBlock = 0; + + + public: + Manager(); + ~Manager(); + + void handleHTTPResponse(const std::string& reqBody); + void registerPlayer(const uint64_t& playerId, std::weak_ptr session); + void removePlayer(const uint64_t& playerId); + void handlePlayerRequest(std::weak_ptr session, const std::string& msg); + void start(); + void loadWorld(); + void broadcastTooAllPlayers(const json& msg); + json buildGetChunkRequest(const int32_t& x, const int32_t& y, const uint64_t& id = 1); + }; + + +} + + + + +#endif // BTVSERVER_MANAGER_H \ No newline at end of file diff --git a/src/bins/btv-server/src/socketlistener.cpp b/src/bins/btv-server/src/socketlistener.cpp new file mode 100644 index 00000000..0f65cc4e --- /dev/null +++ b/src/bins/btv-server/src/socketlistener.cpp @@ -0,0 +1,62 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "socketlistener.h" + +namespace BTVServer { + SocketListener::SocketListener( + net::io_context& ioc, const tcp::endpoint& ep, Manager& manager + ) : ioc_(ioc), acc_(net::make_strand(ioc)), manager_(manager) + { + beast::error_code ec; + this->acc_.open(ep.protocol(), ec); // Open the acceptor + if (ec) { fail("SocketListener", ec, "Failed to open the acceptor"); return; } + this->acc_.set_option(net::socket_base::reuse_address(true), ec); // Allow address reuse + if (ec) { fail("SocketListener", ec, "Failed to set address reuse"); return; } + this->acc_.bind(ep, ec); // Bind to the server address + if (ec) { fail("SocketListener", ec, "Failed to bind to server address"); return; } + this->acc_.listen(net::socket_base::max_listen_connections, ec); // Start listening for connections + if (ec) { fail("SocketListener", ec, "Failed to start listening"); return; } + } + + void SocketListener::setup() { + Printer::safePrint("Starting HTTP Listener at: " + this->acc_.local_endpoint().address().to_string() + ":" + std::to_string(this->acc_.local_endpoint().port())); + this->do_accept(); // Start accepting connections + } + + + void SocketListener::do_accept() { + this->acc_.async_accept(net::make_strand(this->ioc_), beast::bind_front_handler( + &SocketListener::on_accept, this + )); + } + + void SocketListener::on_accept(beast::error_code ec, tcp::socket sock) { + if (ec) { + fail("SocketListener", ec, "Failed to accept connection"); + } else { + std::make_shared( + std::move(sock), this->manager_ + )->start(); // Create the http session and run it + } + this->do_accept(); // Accept another connection + } + + void SocketListener::start() { + net::dispatch(this->acc_.get_executor(), beast::bind_front_handler( + &SocketListener::setup, this + )); + } + + void SocketListener::close() { + boost::system::error_code ec; + this->acc_.close(ec); + if (ec) { + fail("SocketListener", ec, "Failed to close the acceptor"); + } + } +} \ No newline at end of file diff --git a/src/bins/btv-server/src/socketlistener.h b/src/bins/btv-server/src/socketlistener.h new file mode 100644 index 00000000..5e2709ee --- /dev/null +++ b/src/bins/btv-server/src/socketlistener.h @@ -0,0 +1,58 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef SOCKETLISTENER_H +#define SOCKETLISTENER_H + +#include "websocketsession.h" + +namespace BTVServer { + class Manager; + /// Class for listening to, accepting and dispatching incoming connections/sessions. + class SocketListener { + private: + Manager& manager_; ///< Reference to the faucet manager. + /// Provides core I/O functionality. + net::io_context& ioc_; + + /// Accepts incoming connections. + tcp::acceptor acc_; + + /// Start the Listener itself + void setup(); + + /// Accept an incoming connection from the endpoint. The new connection gets its own strand. + void do_accept(); + + /** + * Callback for do_accept(). + * Automatically listens to another session when finished dispatching. + * @param ec The error code to parse. + * @param sock The socket to use for creating the HTTP session. + */ + void on_accept(beast::error_code ec, tcp::socket sock); + + public: + /** + * Constructor. + * @param ioc Reference to the core I/O functionality object. + * @param ep The endpoint (host and port) to listen to. + * @param docroot Reference pointer to the root directory of the endpoint. + * @param state Reference pointer to the blockchain's state. + * @param storage Reference pointer to the blockchain's storage. + * @param p2p Reference pointer to the P2P connection manager. + * @param options Reference pointer to the options singleton. + */ + SocketListener( + net::io_context& ioc, const tcp::endpoint& ep, Manager& manager + ); + + void start(); ///< Start accepting incoming connections. + void close(); ///< Stop accepting incoming connections. + }; +} +#endif // SOCKETLISTENER_H \ No newline at end of file diff --git a/src/bins/btv-server/src/utils.cpp b/src/bins/btv-server/src/utils.cpp new file mode 100644 index 00000000..632c5c1e --- /dev/null +++ b/src/bins/btv-server/src/utils.cpp @@ -0,0 +1,7 @@ +#include "utils.h" + +namespace BTVServer { + void fail(const std::string& class_, boost::system::error_code ec, const std::string& what) { + Printer::safePrint(class_ + "::fail: " + what + ": " + ec.what()); + } +} \ No newline at end of file diff --git a/src/bins/btv-server/src/utils.h b/src/bins/btv-server/src/utils.h new file mode 100644 index 00000000..c80cee27 --- /dev/null +++ b/src/bins/btv-server/src/utils.h @@ -0,0 +1,78 @@ +#ifndef BTVSERVER_UTILS_H +#define BTVSERVER_UTILS_H + +#include "../../../libs/json.hpp" +#include "../../../utils/strings.h" +#include +#include +#include +#include +#include + +#include "utils/utils.h" +#include "utils/tx.h" +#include + +namespace BTVServer { + + void fail(const std::string& class_, boost::system::error_code ec, const std::string& what); + + template + json makeRequestMethod(const std::string& method, const T& params, const uint64_t& id = 1) { + return json({ + {"jsonrpc", "2.0"}, + {"id", id}, + {"method", method}, + {"params", params} + }); + } + + class Printer { + private: + std::mutex printMutex; + std::unique_ptr> printQueue; + std::future printerFuture; + bool run = true; + + Printer() { + printerFuture = std::async(std::launch::async, &Printer::print, this); + } + + ~Printer() { + run = false; + printerFuture.get(); + } + + void print() { + while(run) { + std::unique_ptr> toPrint; + { + std::lock_guard lock(this->printMutex); + if (this->printQueue == nullptr) { + // Do absolutely nothing + } else { + toPrint = std::move(this->printQueue); + this->printQueue = nullptr; + } + } + if (toPrint != nullptr) { + for (const auto& str : *toPrint) { + std::cout << str << std::endl; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } + public: + static void safePrint (std::string&& str) { + static Printer printer; + std::lock_guard lock(printer.printMutex); + if (printer.printQueue == nullptr) { + printer.printQueue = std::make_unique>(); + } + printer.printQueue->emplace_back(std::move(str)); + } + }; +}; + +#endif // BTVSERVER_UTILS_H \ No newline at end of file diff --git a/src/bins/btv-server/src/websocketserver.cpp b/src/bins/btv-server/src/websocketserver.cpp new file mode 100644 index 00000000..d017c5b1 --- /dev/null +++ b/src/bins/btv-server/src/websocketserver.cpp @@ -0,0 +1,26 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "websocketserver.h" + +namespace BTVServer { + bool WebsocketServer::setup() { + // Setup tells the listener to start listening on the port + Printer::safePrint("Websocket Server Setup"); + this->listener_.start(); + Printer::safePrint("Websocket Server Setup: DONE"); + return true; + } + + void WebsocketServer::close() { + // Close tells the listener to stop listening on the port + Printer::safePrint("Websocket Server Close"); + this->listener_.close(); + Printer::safePrint("Websocket Server Close: DONE"); + } + +} \ No newline at end of file diff --git a/src/bins/btv-server/src/websocketserver.h b/src/bins/btv-server/src/websocketserver.h new file mode 100644 index 00000000..50b22b9c --- /dev/null +++ b/src/bins/btv-server/src/websocketserver.h @@ -0,0 +1,59 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef WEBSOCKETSERVER_H +#define WEBSOCKETSERVER_H + +#include "socketlistener.h" + + +namespace BTVServer { + /// Abstraction of an Websocket server. + class Manager; + class WebsocketServer { + private: + Manager& manager_; ///< Reference to the faucet manager. + + /// Provides core I/O functionality ({x} = max threads the object can use). + net::io_context& ioc_; + + /// Pointer to the Websocket listener. + SocketListener listener_; + + /// The endpoint where the server is running. + tcp::endpoint tcpEndpoint_; + + public: + /// The run function (effectively starts the server). + bool setup(); + /** + * Constructor. Does NOT automatically start the server. + * @param state Reference pointer to the blockchain's state. + * @param storage Reference pointer to the blockchain's storage. + * @param p2p Reference pointer to the P2P connection manager. + * @param options Reference pointer to the options singleton. + */ + WebsocketServer(Manager& manager, net::io_context& ioc, const tcp::endpoint& endpoint) : + manager_(manager), + ioc_(ioc), + listener_(ioc, endpoint, manager) + { + std::cout << "Constructing at port: " << endpoint.port() << std::endl; + } + + /** + * Destructor. + * Automatically stops the server. + */ + ~WebsocketServer() { } + + void close(); + + }; +} + +#endif // WEBSOCKETSERVER_H \ No newline at end of file diff --git a/src/bins/btv-server/src/websocketsession.cpp b/src/bins/btv-server/src/websocketsession.cpp new file mode 100644 index 00000000..29c2daab --- /dev/null +++ b/src/bins/btv-server/src/websocketsession.cpp @@ -0,0 +1,105 @@ +#include "websocketsession.h" +#include "manager.h" + + +namespace BTVServer { + WebsocketSession::WebsocketSession(tcp::socket&& socket, Manager& manager) + : manager_(manager), ws_(std::move(socket)), strand_(ws_.get_executor()) { + auto rand = Utils::randBytes(8); + std::memcpy(&id_, rand.data(), 8); + } + + WebsocketSession::~WebsocketSession() { + if (this->registered_) { + this->manager_.removePlayer(this->id_); + } + } + void WebsocketSession::doAccept() { + // Accept the websocket handshake + ws_.async_accept(beast::bind_front_handler(&WebsocketSession::onAccept, shared_from_this())); + } + + void WebsocketSession::onAccept(beast::error_code ec) { + if (ec) { + this->onError(); + return fail("WebsocketSession", ec, "accept"); + } + // Register the websocket session into the manager + this->registered_ = true; + this->manager_.registerPlayer(this->id_, weak_from_this()); + // Start reading messages from the server + this->doRead(); + } + + void WebsocketSession::doRead() { + // Read a message into our buffer + ws_.async_read(buffer_, beast::bind_front_handler(&WebsocketSession::onRead, shared_from_this())); + } + + void WebsocketSession::onRead(beast::error_code ec, std::size_t bytes_transferred) { + if (ec) { + this->onError(); + return fail("WebsocketSession", ec, "read"); + } + + // Send to manager + manager_.handlePlayerRequest( + weak_from_this(), boost::beast::buffers_to_string(buffer_.data()) + ); + // Clear the buffer + buffer_.consume(buffer_.size()); + // Read again + doRead(); + } + + void WebsocketSession::onWrite(beast::error_code ec, std::size_t bytes_transferred) { + if (ec) { + return fail("WebsocketSession", ec, "write"); + } + std::unique_lock lock(writeQueueMutex_); + if (writeQueue_.empty()) { + writeMsg_.reset(); + } else { + writeMsg_ = std::move(writeQueue_.front()); + writeQueue_.pop_front(); + ws_.async_write(net::buffer(*writeMsg_), beast::bind_front_handler(&WebsocketSession::onWrite, shared_from_this())); + } + } + + void WebsocketSession::onError() { + // If it is NOT closed, close it, and set closed_ to true + if (!this->closed_.exchange(true)) { + Printer::safePrint("Closing the websocket session"); + ws_.async_close(websocket::close_code::normal, beast::bind_front_handler(&WebsocketSession::onClose, shared_from_this())); + } + } + + void WebsocketSession::onClose(beast::error_code ec) { + if (ec) { + return fail("WebsocketSession", ec, "close"); + } + // Do nothing + } + + void WebsocketSession::write(const std::string& msg) { + // Send the message + auto messagePtr = std::make_unique(msg); + std::unique_lock lock(writeQueueMutex_); + if (this->writeMsg_ == nullptr) { + this->writeMsg_ = std::move(messagePtr); + ws_.async_write(net::buffer(*this->writeMsg_), beast::bind_front_handler(&WebsocketSession::onWrite, shared_from_this())); + } else { + writeQueue_.push_back(std::move(messagePtr)); + } + } + + void WebsocketSession::stop() { + // Close the WebSocket connection + ws_.async_close(websocket::close_code::normal, beast::bind_front_handler(&WebsocketSession::onClose, shared_from_this())); + } + + void WebsocketSession::start() { + // Accept the websocket handshake + doAccept(); + } +} \ No newline at end of file diff --git a/src/bins/btv-server/src/websocketsession.h b/src/bins/btv-server/src/websocketsession.h new file mode 100644 index 00000000..e4dbf963 --- /dev/null +++ b/src/bins/btv-server/src/websocketsession.h @@ -0,0 +1,57 @@ +#ifndef WEBSOCKETSESSION_H +#define WEBSOCKETSESSION_H +#include +#include +#include +#include +#include +#include "utils.h" + +namespace beast = boost::beast; // from +namespace http = beast::http; // from +namespace websocket = beast::websocket; // from +namespace net = boost::asio; // from +using tcp = boost::asio::ip::tcp; // from + +// Forward declaration +namespace BTVServer { + class Manager; + class WebsocketSession : public std::enable_shared_from_this { + private: + Manager& manager_; + beast::websocket::stream ws_; + beast::flat_buffer buffer_; // Must persist between reads + net::strand strand_; // Strand to post write operations to + std::atomic_bool closed_ = false; + std::atomic_bool registered_ = false; + std::unique_ptr writeMsg_; + std::deque> writeQueue_; + std::mutex writeQueueMutex_; + uint64_t id_ = 0; + + void doAccept(); + void onAccept(beast::error_code ec); + void doRead(); + void onRead(beast::error_code ec, std::size_t bytes_transferred); + void onWrite(beast::error_code ec, std::size_t bytes_transferred); + void onClose(beast::error_code ec); + void onError(); + + public: + WebsocketSession(tcp::socket&& ioc, Manager& manager); + ~WebsocketSession(); + + void write(const std::string& msg); + void stop(); + void start(); + const uint64_t& getId() { + return id_; + } + }; +} + + + + + +#endif // WEBSOCKETSESSION_H \ No newline at end of file diff --git a/src/bins/contractabigenerator/CMakeLists.txt b/src/bins/contractabigenerator/CMakeLists.txt new file mode 100644 index 00000000..542fc16a --- /dev/null +++ b/src/bins/contractabigenerator/CMakeLists.txt @@ -0,0 +1,16 @@ +# Compile and link the ABI generator executable +add_executable(contractabigenerator "main.cpp") + +add_dependencies(contractabigenerator bdk_lib) + +target_include_directories(contractabigenerator PRIVATE + bdk_lib ${OPENSSL_INCLUDE_DIR} ${ETHASH_INCLUDE_DIR} ${KECCAK_INCLUDE_DIR} + ${SPEEDB_INCLUDE_DIR} ${SECP256K1_INCLUDE_DIR} +) + +target_link_libraries(contractabigenerator + bdk_lib ${SPEEDB_LIBRARY} ${SNAPPY_LIBRARY} ${Boost_LIBRARIES} + ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${SECP256K1_LIBRARY} + ${ETHASH_LIBRARY} ${KECCAK_LIBRARY} +) + diff --git a/src/main-contract-abi.cpp b/src/bins/contractabigenerator/main.cpp similarity index 82% rename from src/main-contract-abi.cpp rename to src/bins/contractabigenerator/main.cpp index 09243383..8ab18d33 100644 --- a/src/main-contract-abi.cpp +++ b/src/bins/contractabigenerator/main.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. diff --git a/src/bins/faucet-api/CMakeLists.txt b/src/bins/faucet-api/CMakeLists.txt new file mode 100644 index 00000000..2fa88de3 --- /dev/null +++ b/src/bins/faucet-api/CMakeLists.txt @@ -0,0 +1,67 @@ +if (BUILD_FAUCET) + add_library(rollup_faucet_lib STATIC + ${CMAKE_SOURCE_DIR}/src/bins/faucet-api/src/jsonrpc/encoding.h + ${CMAKE_SOURCE_DIR}/src/bins/faucet-api/src/jsonrpc/decoding.h + ${CMAKE_SOURCE_DIR}/src/bins/faucet-api/src/jsonrpc/encoding.cpp + ${CMAKE_SOURCE_DIR}/src/bins/faucet-api/src/jsonrpc/decoding.cpp + ${CMAKE_SOURCE_DIR}/src/bins/faucet-api/src/httplistener.h + ${CMAKE_SOURCE_DIR}/src/bins/faucet-api/src/httpparser.h + ${CMAKE_SOURCE_DIR}/src/bins/faucet-api/src/httpserver.h + ${CMAKE_SOURCE_DIR}/src/bins/faucet-api/src/httpsession.h + ${CMAKE_SOURCE_DIR}/src/bins/faucet-api/src/faucetmanager.h + ${CMAKE_SOURCE_DIR}/src/bins/faucet-api/src/httplistener.cpp + ${CMAKE_SOURCE_DIR}/src/bins/faucet-api/src/httpparser.cpp + ${CMAKE_SOURCE_DIR}/src/bins/faucet-api/src/httpserver.cpp + ${CMAKE_SOURCE_DIR}/src/bins/faucet-api/src/httpsession.cpp + ${CMAKE_SOURCE_DIR}/src/bins/faucet-api/src/faucetmanager.cpp + ) + + target_include_directories(rollup_faucet_lib PRIVATE + ${CMAKE_SOURCE_DIR}/include ${OPENSSL_INCLUDE_DIR} bdk_lib + ${ETHASH_INCLUDE_DIR} ${KECCAK_INCLUDE_DIR} + ${SPEEDB_INCLUDE_DIR} ${SECP256K1_INCLUDE_DIR} + ) + + target_link_libraries(rollup_faucet_lib PRIVATE bdk_lib + ${CRYPTOPP_LIBRARIES} ${SCRYPT_LIBRARY} ${SECP256K1_LIBRARY} + ${ETHASH_LIBRARY} ${KECCAK_LIBRARY} ${SPEEDB_LIBRARY} + ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} bdk_lib + ) + + # Compile and link the faucet-api executable + add_executable(faucet-api "main.cpp") + + add_dependencies(faucet-api bdk_lib rollup_faucet_lib) + + target_include_directories(faucet-api PRIVATE + bdk_lib rollup_faucet_lib ${OPENSSL_INCLUDE_DIR} + ${ETHASH_INCLUDE_DIR} ${KECCAK_INCLUDE_DIR} + ${SPEEDB_INCLUDE_DIR} ${SECP256K1_INCLUDE_DIR} + ) + + target_link_libraries(faucet-api + bdk_lib rollup_faucet_lib + ${SPEEDB_LIBRARY} ${SNAPPY_LIBRARY} ${Boost_LIBRARIES} + ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${SECP256K1_LIBRARY} + ${ETHASH_LIBRARY} ${KECCAK_LIBRARY} + ) + + # Compile and link the faucet-api executable + add_executable(faucet-tester "main-tester.cpp") + + add_dependencies(faucet-tester bdk_lib rollup_faucet_lib) + + target_include_directories(faucet-tester PRIVATE + bdk_lib rollup_faucet_lib ${OPENSSL_INCLUDE_DIR} + ${ETHASH_INCLUDE_DIR} ${KECCAK_INCLUDE_DIR} + ${SPEEDB_INCLUDE_DIR} ${SECP256K1_INCLUDE_DIR} + ) + + target_link_libraries(faucet-tester + bdk_lib rollup_faucet_lib + ${SPEEDB_LIBRARY} ${SNAPPY_LIBRARY} ${Boost_LIBRARIES} + ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${SECP256K1_LIBRARY} + ${ETHASH_LIBRARY} ${KECCAK_LIBRARY} + ) +endif() + diff --git a/src/bins/faucet-api/main-tester.cpp b/src/bins/faucet-api/main-tester.cpp new file mode 100644 index 00000000..674ce95e --- /dev/null +++ b/src/bins/faucet-api/main-tester.cpp @@ -0,0 +1,97 @@ +#include "src/faucetmanager.h" +#include + +// This is a "simulator", it will request the number of iterations to start banging the faucet endpoint +// The HTTP endpoint (for HTTP client) (IP:PORT) +int main() { + std::vector faucetWorkers; + std::pair httpEndpoint; + uint64_t iterations; + + std::cout << "Welcome to the faucet API provider tester" << std::endl; + std::cout << "This API provider is designed to generate random accounts and request funds from the faucet" << std::endl; + std::cout << "It will dump the privkeys to the faucettester.txt" << std::endl; + + std::cout << "Please provide the HTTP endpoint (IP:PORT) (empty for default: 127.0.0.1:28888): " << std::endl; + std::string httpEndpointStr; + std::getline(std::cin, httpEndpointStr); + if (httpEndpointStr.empty()) { + httpEndpoint = std::make_pair(net::ip::address_v4::from_string("127.0.0.1"), 28888); + } else { + std::vector parts; + boost::split(parts, httpEndpointStr, boost::is_any_of(":")); + if (parts.size() != 2) { + throw DynamicException("Invalid HTTP endpoint"); + } + try { + httpEndpoint = std::make_pair(net::ip::address_v4::from_string(parts[0]), std::stoul(parts[1])); + } catch (const std::exception& e) { + throw DynamicException("Invalid HTTP endpoint"); + } + } + + // Ask for a iteration quantity to start banging the faucet endpoint + std::cout << "Please type the number of iterations to start banging the faucet endpoint (empty for default: 25000): " << std::endl; + std::string iterationsStr; + std::getline(std::cin, iterationsStr); + if (iterationsStr.empty()) { + iterations = 25000; + } else { + for (const auto& c : iterationsStr) { + if (!std::isdigit(c)) { + throw DynamicException("Invalid iterations"); + } + } + iterations = std::stoull(iterationsStr); + } + + std::cout << "Creating worker accounts..." << std::endl; + + for (uint64_t i = 0; i < iterations; i++) { + faucetWorkers.emplace_back(PrivKey(Utils::randBytes(32))); + } + + std::cout << "Worker accounts created size: " << faucetWorkers.size() << std::endl; + std::cout << "Dumping privkeys to faucettester.txt" << std::endl; + std::ofstream file("faucettester.txt"); + for (const auto& worker : faucetWorkers) { + file << worker.privKey.hex(true) << std::endl; + } + file.close(); + + std::cout << "Creating the requests..." << std::endl; + std::vector requests; + for (const auto& worker : faucetWorkers) { + requests.push_back(Faucet::Manager::makeDripToAddress(worker.address)); + } + + std::cout << "Requests created size: " << requests.size() << std::endl; + std::cout << "Creating HTTP client..." << std::endl; + + HTTPSyncClient client(httpEndpoint.first.to_string(), std::to_string(httpEndpoint.second)); + + + client.connect(); + + std::cout << "Type anything to start banging the faucet endpoint" << std::endl; + std::string dummy; + std::getline(std::cin, dummy); + + + for (uint64_t i = 0 ; i < requests.size(); i++) { + if (i % 100 == 0) { + std::cout << "Iteration: " << i << std::endl; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); /// Sleep for 1ms to avoid spamming the endpoint too much lol + std::string response = client.makeHTTPRequest(requests[i]); + json j = json::parse(response); + if (!j.contains("result")) { + std::cout << "Error: " << j.dump(2) << std::endl; + } + if (j["result"] != "0x1") { + std::cout << "Error: " << j.dump(2) << std::endl; + } + } + + return 0; +} \ No newline at end of file diff --git a/src/bins/faucet-api/main.cpp b/src/bins/faucet-api/main.cpp new file mode 100644 index 00000000..a194d9a3 --- /dev/null +++ b/src/bins/faucet-api/main.cpp @@ -0,0 +1,113 @@ +#include "src/faucetmanager.h" +#include +// In order to construct the faucet manager, need to load the following: +// const std::vector& faucetWorkers, +// const uint64_t& chainId, +// const std::pair& httpEndpoint, +// const uint16_t& port +// For that we ask the user: +// A file path to a list of private keyssss (one hex per line) +// The chain ID +// The HTTP endpoint (for HTTP client) (IP:PORT) +// The port for the server +int main() { + Log::logToCout = true; + std::vector faucetWorkers; + uint64_t chainId; + std::pair httpEndpoint; + uint16_t port; + + + + std::cout << "Welcome to the faucet API provider" << std::endl; + std::cout << "This API provider is designed to load a list of keys from a file and provide a faucet service" << std::endl; + std::cout << "Using the keys provided to sign transactions" << std::endl; + + std::cout << "Please type the file path to the list of private keys (emtpy for default: \"privkeys.txt\"): " << std::endl; + std::string filePath; + std::getline(std::cin, filePath); + if (filePath.empty()) { + filePath = "privkeys.txt"; + } + if (!std::filesystem::is_regular_file(filePath)) { + throw DynamicException("Invalid file path for private keys"); + } + + std::ifstream file(filePath); + std::string line; + while (std::getline(file, line)) { + Bytes key = Hex::toBytes(line); + if (key.size() != 32) { + throw DynamicException("Invalid private key"); + } + faucetWorkers.push_back(WorkerAccount(PrivKey(key))); + } + + std::cout << "Please provide the chain Id (empty for default: 808080): " << std::endl; + std::string chainIdStr; + std::getline(std::cin, chainIdStr); + + if (chainIdStr.empty()) { + chainId = 808080; + } else { + for (const auto& c : chainIdStr) { + if (!std::isdigit(c)) { + throw DynamicException("Invalid chain Id"); + } + } + chainId = std::stoull(chainIdStr); + } + + std::cout << "Please provide the HTTP endpoint (IP:PORT) (empty for default: 127.0.0.1:8090): " << std::endl; + std::string httpEndpointStr; + std::getline(std::cin, httpEndpointStr); + if (httpEndpointStr.empty()) { + httpEndpoint = std::make_pair(net::ip::address_v4::from_string("127.0.0.1"), 8090); + } else { + std::vector parts; + boost::split(parts, httpEndpointStr, boost::is_any_of(":")); + if (parts.size() != 2) { + throw DynamicException("Invalid HTTP endpoint"); + } + try { + httpEndpoint = std::make_pair(net::ip::address_v4::from_string(parts[0]), std::stoul(parts[1])); + } catch (const std::exception& e) { + throw DynamicException("Invalid HTTP endpoint"); + } + } + + std::cout << "Please provide the port for the server (empty for default: 28888): " << std::endl; + std::string portStr; + std::getline(std::cin, portStr); + if (portStr.empty()) { + port = 28888; + } else { + for (const auto& c : portStr) { + if (!std::isdigit(c)) { + throw DynamicException("Invalid port"); + } + } + port = std::stoull(portStr); + } + + std::cout << "Loaded: " << faucetWorkers.size() << " PrivKeys" << std::endl; + std::cout << "ChainID: " << chainId << std::endl; + std::cout << "HTTP endpoint: " << httpEndpoint.first << ":" << httpEndpoint.second << std::endl; + std::cout << "Port: " << port << std::endl; + std::cout << "Please type anything to start the faucet" << std::endl; + std::string start; + std::getline(std::cin, start); + + Faucet::Manager manager(faucetWorkers, chainId, httpEndpoint, port); + manager.setup(); + manager.run(); + + + + + + + + + return 0; +} \ No newline at end of file diff --git a/src/bins/faucet-api/src/faucetmanager.cpp b/src/bins/faucet-api/src/faucetmanager.cpp new file mode 100644 index 00000000..7864edb5 --- /dev/null +++ b/src/bins/faucet-api/src/faucetmanager.cpp @@ -0,0 +1,173 @@ +#include "faucetmanager.h" +template +std::string makeRequestMethod(const std::string& method, const T& params) { + return json({ + {"jsonrpc", "2.0"}, + {"id", 1}, + {"method", method}, + {"params", params} + }).dump(); +} + + +namespace Faucet { + bool FaucetWorker::run() { + bool log = true; + while(!this->stop_) { + try { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::unique_ptr> dripQueue; + { + std::unique_lock lock(this->manager_.dripMutex_); + if (this->manager_.dripQueue_ == nullptr) { + if (log) { + Utils::safePrint("No more addresses to drip to, sleeping for 100ms"); + log = false; + } + continue; + } + log = true; + dripQueue = std::move(this->manager_.dripQueue_); + // If the dripQueue is bigger than the number of accounts + // We can only process the amount of accounts available in the Manager::faucetWorkers_.size() + // Meaning the remaining accounts needs to be replaced back into the queue. + if (dripQueue->size() > this->manager_.faucetWorkers_.size()) { + this->manager_.dripQueue_ = std::make_unique>(dripQueue->begin() + this->manager_.faucetWorkers_.size(), dripQueue->end()); + // Resize the dripQueue to the size of the number of accounts + dripQueue->resize(this->manager_.faucetWorkers_.size()); + } else { + this->manager_.dripQueue_ = nullptr; + } + Utils::safePrint("Dripping to " + std::to_string(dripQueue->size()) + " addresses"); + } + + std::vector sendTxPackets; + std::vector> sendTxHashes; + for (uint64_t i = 0; i < dripQueue->size(); ++i) { + const auto& address = dripQueue->at(i); + Utils::safePrint("Dripping to address: " + address.hex(true).get()); + sendTxPackets.emplace_back(this->manager_.createTransactions( + this->manager_.faucetWorkers_[i], + 1000000000000000000, + this->manager_.chainId_, + address + )); + + } + + Utils::safePrint("Sending " + std::to_string(sendTxPackets.size()) + " faucet transactions to the network"); + + for (auto& tx : sendTxPackets) { + std::this_thread::sleep_for(std::chrono::microseconds(3)); + auto response = this->client_.makeHTTPRequest(tx); + auto json = json::parse(response); + if (json.contains("error")) { + throw std::runtime_error("Error while sending transactions: sent: " + tx + " received: " + json.dump()); + } + sendTxHashes.emplace_back(Hex::toBytes(json["result"].get()), false); + } + + Utils::safePrint("Confirming " + std::to_string(sendTxHashes.size()) + " faucet transactions to the network"); + + for (uint64_t i = 0; i < sendTxHashes.size(); ++i) { + while (sendTxHashes[i].second == false) { + std::this_thread::sleep_for(std::chrono::microseconds(3)); + auto response = this->client_.makeHTTPRequest(makeRequestMethod("eth_getTransactionReceipt", json::array({sendTxHashes[i].first.hex(true).get()}))); + auto json = json::parse(response); + if (json.contains("error")) { + throw std::runtime_error("Error while confirming transactions: sent: " + sendTxHashes[i].first.hex(true).get() + " received: " + json.dump()); + } + if (json["result"].is_null()) { + continue; + } + sendTxHashes[i].second = true; + this->manager_.faucetWorkers_[i].nonce += 1; + } + } + // Update nonce + } catch (std::exception& e) { + LOGERRORP(std::string("Error while processing dripToAddress: ") + e.what()); + } + } + return true; + } + + void FaucetWorker::start() { + this->stop_ = false; + if (this->runFuture_.valid()) { + throw std::runtime_error("FaucetWorker already running"); + } + this->runFuture_ = std::async(std::launch::async, &FaucetWorker::run, this); + } + + void FaucetWorker::stop() { + if (!this->runFuture_.valid()) { + throw std::runtime_error("FaucetWorker not running"); + } + this->stop_ = true; + this->runFuture_.get(); + } + + + + std::string Manager::makeDripToAddress(const Address& address) { + return makeRequestMethod("dripToAddress", json::array({address.hex(true).get()})); + } + + void Manager::setup() { + std::cout << "Setting up the faucet manager" << std::endl; + std::cout << "Requesting nonces from the network" << std::endl; + + for (auto& worker : this->faucetWorkers_) { + HTTPSyncClient client(this->httpEndpoint_.first.to_string(), std::to_string(this->httpEndpoint_.second)); + client.connect(); + auto response = client.makeHTTPRequest(makeRequestMethod("eth_getTransactionCount", json::array({worker.address.hex(true).get(), "latest"}))); + auto json = json::parse(response); + if (json.contains("error")) { + throw std::runtime_error("Error while getting nonce: " + response); + } + worker.nonce = Hex(json["result"].get()).getUint(); + } + std::cout << "Nonces received!" << std::endl; + } + + void Manager::run() { + std::cout << "Running faucet service..." << std::endl; + this->faucetWorker_.start(); + this->server_.run(); + } + + + std::string Manager::createTransactions(WorkerAccount& account, + const uint256_t& txNativeBalance, + const uint64_t& chainId, + const Address& to) { + return makeRequestMethod("eth_sendRawTransaction", + json::array({Hex::fromBytes( + TxBlock( + to, + account.address, + {}, + chainId, + account.nonce, + txNativeBalance, + 1000000000, + 1000000000, + 21000, + account.privKey).rlpSerialize() + ,true).forRPC()})); + } + + void Manager::processDripToAddress(const Address& address) { + // Firstly, lock the current state and check if existed, then grab the current worker account and move the index. + std::unique_lock lock(this->dripMutex_); + if (this->dripQueue_ == nullptr) { + this->dripQueue_ = std::make_unique>(); + } + this->dripQueue_->emplace_back(address); + } + + void Manager::dripToAddress(const Address& address) { + this->threadPool_.push_task(&Manager::processDripToAddress, this, address); + } +} \ No newline at end of file diff --git a/src/bins/faucet-api/src/faucetmanager.h b/src/bins/faucet-api/src/faucetmanager.h new file mode 100644 index 00000000..7b628b8f --- /dev/null +++ b/src/bins/faucet-api/src/faucetmanager.h @@ -0,0 +1,162 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef FAUCETMANAGER_H +#define FAUCETMANAGER_H + +#include "httpserver.h" +#include "net/http/httpclient.h" +#include "utils/utils.h" +#include "utils/tx.h" +#include "net/http/httpclient.h" +#include +#include "libs/BS_thread_pool_light.hpp" +#include +#include +#include "utils/safehash.h" + +/// Helper struct that abstracts a worker account. +struct WorkerAccount { + const PrivKey privKey; ///< Private key of the account. + const Address address; ///< Address of the account. + uint256_t nonce; ///< Current nonce of the account. + std::mutex inUse_; ///< Mutex for when the account is in use. + + /** + * Constructor. + * @param privKey Private key of the account. + */ + explicit WorkerAccount (const PrivKey& privKey) : privKey(privKey), address(Secp256k1::toAddress(Secp256k1::toUPub(privKey))), nonce(0) {} + + /// Copy constructor. + WorkerAccount(const WorkerAccount& other) : privKey(other.privKey), address(other.address), nonce(other.nonce) {} +}; + +/// Namespace for faucet-related functionalities. +namespace Faucet { + // Forward declaration. + class Manager; + + /** + * Helper worker class for the faucet. + * Consumes Manager::dripQueue_, setting it to nullptr after copying it. + * Locks dripMutex_ to get the next list to drip to. + */ + class FaucetWorker { + private: + Manager& manager_; ///< Reference to the manager. + HTTPSyncClient client_; ///< Reference to the HTTP sync client. + std::future runFuture_; ///< Future for the run function so we know when it should stop. + std::atomic stop_ = false; ///< Flag that tells the worker to stop. + bool run(); ///< Start the worker loop. + + public: + /** + * Constructor. + * @param manager Reference to the manager. + * @param httpEndpoint The endpoint to operate on. + */ + FaucetWorker(Manager& manager, const std::pair& httpEndpoint) + : manager_(manager), client_(httpEndpoint.first.to_string(), std::to_string(httpEndpoint.second)) + { this->client_.connect(); } + + /// Destructor. + ~FaucetWorker() { this->client_.close(); this->stop(); } + + FaucetWorker(const FaucetWorker& other) = delete; ///< Copy constructor (deleted, Rule of Zero). + FaucetWorker& operator=(const FaucetWorker& other) = delete; ///< Copy assignment operator (deleted, Rule of Zero). + FaucetWorker(FaucetWorker&& other) = delete; ///< Move constructor (deleted, Rule of Zero). + FaucetWorker& operator=(FaucetWorker&& other) = delete; ///< Move assignment operator (deleted, Rule of Zero). + + void start(); ///< Start the worker. + void stop(); ///< Stop the worker. + }; + + /// Faucet manager class. + class Manager { + private: + FaucetWorker faucetWorker_; ///< Worker object. + BS::thread_pool_light threadPool_; ///< Thread pool. + std::vector faucetWorkers_; ///< List of worker objects. + const uint64_t chainId_; ///< CHain ID that the faucet is operating on. + HTTPServer server_; ///< HTTP server object. + const std::pair httpEndpoint_; ///< HTTP endpoint to be used for the client + const uint16_t port_; ///< Port to be used for the server + std::mutex dripMutex_; ///< Mutex for managing read/write access to the drip list. + std::unique_ptr> dripQueue_; ///< List of drip addresses to iterate on. + std::mutex lastIndexMutex_; ///< Mutex for managing read/write access to the last index. + uint64_t lastIndex_ = 0; ///< Last index. + std::shared_mutex accountsMutex_; ///< Mutex for managing read/write access to the accounts list. + std::unordered_set accounts_; ///< List of accounts. + + public: + + /** + * Constructor. + * @param faucetWorkers List of faucet worker objects. + * @param chainId Chain ID that the faucet will operate on. + * @param httpEndpoint Endpoint that the faucet will iterate on. + * @param port Port that the faucet will operate on. + */ + Manager( + const std::vector& faucetWorkers, + const uint64_t& chainId, + const std::pair& httpEndpoint, + const uint16_t& port + ) : faucetWorkers_(faucetWorkers), + chainId_(chainId), + httpEndpoint_(httpEndpoint), + port_(port), + server_(port, *this), + threadPool_(8), + faucetWorker_(*this, httpEndpoint) {} + + Manager(const Manager& other) = delete; ///< Copy constructor (deleted, Rule of Zero). + Manager& operator=(const Manager& other) = delete; ///< Copy assignment operator (deleted, Rule of Zero). + Manager(Manager&& other) = delete; ///< Move constructor (deleted, Rule of Zero). + Manager& operator=(Manager&& other) = delete; ///< Move assignment operator (deleted, Rule of Zero). + + /** + * Request a drip to a given address. + * @param address The address to drip into. + * @return A string containing the result of the drip request. + */ + static std::string makeDripToAddress(const Address& address); + + /** + * Make a new "send" transaction (eth_sendRawTransaction). + * @param account The account to be used. + * @param txNativeBalance The transaction native balance to be used. + * @param chainId The chain ID to be used. + * @param to The address to send the transaction(s) to. + * @return The resulting json string of the eth_sendRawTransaction operation. + */ + static std::string createTransactions( + WorkerAccount& account, const uint256_t& txNativeBalance, + const uint64_t& chainId, const Address& to + ); + + void setup(); ///< Setup the faucet. + void run(); ///< Run the faucet. + + /** + * Process the next drip request in the queue for a given address. + * @param address The address to process the drip request of. + */ + void processDripToAddress(const Address& address); + + /** + * Execute the drip request to a given address. + * @param address The address to execute the drip on. + */ + void dripToAddress(const Address& address); + + friend class FaucetWorker; + }; +}; + +#endif // FAUCETMANAGER_H diff --git a/src/bins/faucet-api/src/httplistener.cpp b/src/bins/faucet-api/src/httplistener.cpp new file mode 100644 index 00000000..c634e2ca --- /dev/null +++ b/src/bins/faucet-api/src/httplistener.cpp @@ -0,0 +1,48 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "httplistener.h" + +namespace Faucet { + HTTPListener::HTTPListener( + net::io_context& ioc, tcp::endpoint ep, const std::shared_ptr& docroot, Manager& faucet + ) : ioc_(ioc), acc_(net::make_strand(ioc)), docroot_(docroot), faucet_(faucet) + { + beast::error_code ec; + this->acc_.open(ep.protocol(), ec); // Open the acceptor + if (ec) { fail("HTTPListener", __func__, ec, "Failed to open the acceptor"); return; } + this->acc_.set_option(net::socket_base::reuse_address(true), ec); // Allow address reuse + if (ec) { fail("HTTPListener", __func__, ec, "Failed to set address reuse"); return; } + this->acc_.bind(ep, ec); // Bind to the server address + if (ec) { fail("HTTPListener", __func__, ec, "Failed to bind to server address"); return; } + this->acc_.listen(net::socket_base::max_listen_connections, ec); // Start listening for connections + if (ec) { fail("HTTPListener", __func__, ec, "Failed to start listening"); return; } + } + + void HTTPListener::do_accept() { + this->acc_.async_accept(net::make_strand(this->ioc_), beast::bind_front_handler( + &HTTPListener::on_accept, this->shared_from_this() + )); + } + + void HTTPListener::on_accept(beast::error_code ec, tcp::socket sock) { + if (ec) { + fail("HTTPListener", __func__, ec, "Failed to accept connection"); + } else { + std::make_shared( + std::move(sock), this->docroot_, this->faucet_ + )->start(); // Create the http session and run it + } + this->do_accept(); // Accept another connection + } + + void HTTPListener::start() { + net::dispatch(this->acc_.get_executor(), beast::bind_front_handler( + &HTTPListener::do_accept, this->shared_from_this() + )); + } +} diff --git a/src/bins/faucet-api/src/httplistener.h b/src/bins/faucet-api/src/httplistener.h new file mode 100644 index 00000000..3c9c8d59 --- /dev/null +++ b/src/bins/faucet-api/src/httplistener.h @@ -0,0 +1,51 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef HTTPLISTENER_H +#define HTTPLISTENER_H + +#include "httpparser.h" +#include "httpsession.h" + +/// Namespace for faucet-related functionalities. +namespace Faucet { + class Manager; // Forward declaration. + + /// Class for listening to, accepting and dispatching incoming connections/sessions. + class HTTPListener : public std::enable_shared_from_this { + private: + Manager& faucet_; ///< Reference to the faucet manager. + net::io_context& ioc_; ///< Provides core I/O functionality. + tcp::acceptor acc_; ///< Accepts incoming connections. + const std::shared_ptr docroot_; ///< Pointer to the root directory of the endpoint. + + void do_accept(); ///< Accept an incoming connection from the endpoint. The new connection gets its own strand. + + /** + * Callback for do_accept(). + * Automatically listens to another session when finished dispatching. + * @param ec The error code to parse. + * @param sock The socket to use for creating the HTTP session. + */ + void on_accept(beast::error_code ec, tcp::socket sock); + + public: + /** + * Constructor. + * @param ioc Reference to the core I/O functionality object. + * @param ep The endpoint (host and port) to listen to. + * @param docroot Reference pointer to the root directory of the endpoint. + * @param faucet Reference to the faucet manager. + */ + HTTPListener( + net::io_context& ioc, tcp::endpoint ep, const std::shared_ptr& docroot, Manager& faucet + ); + + void start(); ///< Start accepting incoming connections. + }; +} +#endif // HTTPLISTENER_H diff --git a/src/bins/faucet-api/src/httpparser.cpp b/src/bins/faucet-api/src/httpparser.cpp new file mode 100644 index 00000000..01297d9b --- /dev/null +++ b/src/bins/faucet-api/src/httpparser.cpp @@ -0,0 +1,61 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "httpparser.h" + +namespace Faucet { + std::string parseJsonRpcRequest( + const std::string& body, Manager& faucet + ) { + json ret; + uint64_t id = 0; + try { + json request = json::parse(body); + if (!JsonRPC::Decoding::checkJsonRPCSpec(request)) { + ret["error"]["code"] = -32600; + ret["error"]["message"] = "Invalid request - does not conform to JSON-RPC 2.0 spec"; + return ret.dump(); + } + + auto RequestMethod = JsonRPC::Decoding::getMethod(request); + switch (RequestMethod) { + case JsonRPC::Methods::invalid: + Utils::safePrint("INVALID METHOD: " + request["method"].get()); + ret["error"]["code"] = -32601; + ret["error"]["message"] = "Method not found"; + break; + case JsonRPC::Methods::dripToAddress: + JsonRPC::Decoding::dripToAddress(request, faucet); + ret = JsonRPC::Encoding::dripToAddress(); + break; + default: + ret["error"]["code"] = -32601; + ret["error"]["message"] = "Method not found"; + break; + } + if (request["id"].is_string()) { + ret["id"] = request["id"].get(); + } else if (request["id"].is_number()) { + ret["id"] = request["id"].get(); + } else if(request["id"].is_null()) { + ret["id"] = nullptr; + } else { + throw DynamicException("Invalid id type"); + } + } catch (std::exception &e) { + json error; + error["id"] = id; + error["jsonrpc"] = 2.0; + error["error"]["code"] = -32603; + error["error"]["message"] = "Internal error: " + std::string(e.what()); + return error.dump(); + } + // Set back to the original id + return ret.dump(); + } +} + diff --git a/src/bins/faucet-api/src/httpparser.h b/src/bins/faucet-api/src/httpparser.h new file mode 100644 index 00000000..8d0cbcc6 --- /dev/null +++ b/src/bins/faucet-api/src/httpparser.h @@ -0,0 +1,163 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef HTTPPARSER_H +#define HTTPPARSER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../utils/utils.h" +#include "../utils/options.h" +#include "jsonrpc/methods.h" +#include "jsonrpc/decoding.h" +#include "jsonrpc/encoding.h" + +namespace beast = boost::beast; // from +namespace http = beast::http; // from +namespace websocket = beast::websocket; // from +namespace net = boost::asio; // from +using tcp = boost::asio::ip::tcp; // from + +// It is preferable to use forward declarations here. +// The parser functions never access any of these members, only passes them around. +namespace Faucet { + class Manager; + /** + * Parse a JSON-RPC request into a JSON-RPC response, handling all requests and errors. + * @param body The request string. + * @param faucet Reference to the faucet manager. + * @return The response string. + */ + std::string parseJsonRpcRequest( + const std::string& body, Manager& faucet + ); + + /** + * Produce an HTTP response for a given request. + * The type of the response object depends on the contents of the request, + * so the interface requires the caller to pass a generic lambda to receive the response. + * @param docroot The root directory of the endpoint. + * @param req The request to handle. + * @param send TODO: we're missing details on this, Allocator, Body, the function itself and where it's used + * @param faucet Reference to the faucet manager. + */ + template void handle_request( + [[maybe_unused]] beast::string_view docroot, + http::request>&& req, + Send&& send, Manager& faucet + ) { + // Returns a bad request response + const auto bad_request = [&req](beast::string_view why){ + http::response res{http::status::bad_request, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body() = std::string(why); + res.prepare_payload(); + return res; + }; + + // Returns a not found response + const auto not_found = [&req](beast::string_view target){ + http::response res{http::status::not_found, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body() = "The resource '" + std::string(target) + "' was not found."; + res.prepare_payload(); + return res; + }; + + // Returns a server error response + const auto server_error = [&req](beast::string_view what) { + http::response res{http::status::internal_server_error, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body() = "An error occurred: '" + std::string(what) + "'"; + res.prepare_payload(); + return res; + }; + + // Make sure we can handle the method + if (req.method() != http::verb::post && req.method() != http::verb::options) + return send(bad_request("Unknown HTTP-method")); + + // Request path must be absolute and not contain ".." + if ( + req.target().empty() || req.target()[0] != '/' || + req.target().find("..") != beast::string_view::npos + ) return send(bad_request("Illegal request-target")); + + // Respond to OPTIONS, Metamask requests it + if (req.method() == http::verb::options) { + http::response res{http::status::ok, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::access_control_allow_origin, "*"); + res.set(http::field::access_control_allow_methods, "POST, GET"); + res.set(http::field::access_control_allow_headers, "content-type"); + res.set(http::field::accept_encoding, "deflate"); + res.set(http::field::accept_language, "en-US"); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); + } + Utils::safePrint("HTTP Request: " + req.body()); + std::string request = req.body(); + std::string answer = parseJsonRpcRequest( + request, faucet + ); + + http::response res{http::status::ok, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::access_control_allow_origin, "*"); + res.set(http::field::access_control_allow_methods, "POST, GET"); + res.set(http::field::access_control_allow_headers, "content-type"); + res.set(http::field::content_type, "application/json"); + res.set(http::field::connection, "keep-alive"); + res.set(http::field::strict_transport_security, "max-age=0"); + res.set(http::field::vary, "Origin"); + res.set(http::field::access_control_allow_credentials, "true"); + res.body() = answer; + res.keep_alive(req.keep_alive()); + res.prepare_payload(); + return send(std::move(res)); + } +} + +#endif // HTTPPARSER_H diff --git a/src/bins/faucet-api/src/httpserver.cpp b/src/bins/faucet-api/src/httpserver.cpp new file mode 100644 index 00000000..168356e1 --- /dev/null +++ b/src/bins/faucet-api/src/httpserver.cpp @@ -0,0 +1,33 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "httpserver.h" +namespace Faucet { + bool HTTPServer::run() { + // Create and launch a listening port + const boost::asio::ip::address address = net::ip::make_address("0.0.0.0"); + auto docroot = std::make_shared("."); + this->listener_ = std::make_shared( + this->ioc_, tcp::endpoint{address, this->port_}, docroot, this->faucet_ + ); + this->listener_->start(); + + // Run the I/O service on the requested number of threads (4) + std::vector v; + v.reserve(4 - 1); + for (int i = 4 - 1; i > 0; i--) v.emplace_back([&]{ this->ioc_.run(); }); + LOGINFO(std::string("HTTP Server Started at port: ") + std::to_string(port_)); + this->ioc_.run(); + + // If we get here, it means we got a SIGINT or SIGTERM. Block until all the threads exit + for (std::thread& t : v) t.join(); + LOGINFO("HTTP Server Stopped"); + return true; + } + +} + diff --git a/src/bins/faucet-api/src/httpserver.h b/src/bins/faucet-api/src/httpserver.h new file mode 100644 index 00000000..525ce6df --- /dev/null +++ b/src/bins/faucet-api/src/httpserver.h @@ -0,0 +1,60 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef HTTPSERVER_H +#define HTTPSERVER_H + +#include "httpparser.h" +#include "httplistener.h" + +/// Namespace for faucet-related functionalities. +namespace Faucet { + class Manager; // Forward declaration. + + /// Abstraction of an HTTP server. + class HTTPServer { + private: + Manager& faucet_; ///< Reference to the faucet manager. + + /// Provides core I/O functionality ({x} = max threads the object can use). + net::io_context ioc_{4}; + + /// Pointer to the HTTP listener. + std::shared_ptr listener_; + + /// The port where the server is running. + const unsigned short port_; + + + /// Future for the run function so we know when it should stop. + std::future runFuture_; + + public: + /// The run function (effectively starts the server). + bool run(); + + /** + * Constructor. Does NOT automatically start the server. + * @param port The port where the server runs. + * @param faucet Reference to the faucet manager. + */ + HTTPServer(const uint16_t& port, Manager& faucet) : port_(port), faucet_(faucet) { + std::cout << "Starting at port: " << port_ << std::endl; + } + + /// Destructor. Automatically stops the server. + ~HTTPServer() {} + + /** + * Check if the server is currently active and running. + * @return `true` if the server is running, `false` otherwise. + */ + bool running() const { return this->runFuture_.valid(); } + }; +} + +#endif // HTTPSERVER_H diff --git a/src/bins/faucet-api/src/httpsession.cpp b/src/bins/faucet-api/src/httpsession.cpp new file mode 100644 index 00000000..510943ff --- /dev/null +++ b/src/bins/faucet-api/src/httpsession.cpp @@ -0,0 +1,94 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "httpsession.h" + +namespace Faucet { + HTTPQueue::HTTPQueue(HTTPSession& session) : session_(session) { + assert(this->limit_ > 0); + this->items_.reserve(this->limit_); + } + + bool HTTPQueue::full() const { return this->items_.size() >= this->limit_; } + + bool HTTPQueue::on_write() { + BOOST_ASSERT(!this->items_.empty()); + bool wasFull = this->full(); + this->items_.erase(this->items_.begin()); + if (!this->items_.empty()) (*this->items_.front())(); + return wasFull; + } + + template void HTTPQueue::operator()( + http::message&& msg + ) { + // This holds a work item + struct work_impl : work { + HTTPSession& session; + http::message msg; // This msg is internal + work_impl(HTTPSession& session, http::message&& msg) + : session(session), msg(std::move(msg)) {} + void operator()() override { + http::async_write( + session.stream_, msg, beast::bind_front_handler( + &HTTPSession::on_write, session.shared_from_this(), msg.need_eof() + ) + ); + } + }; + + // Allocate and store the work, and if there was no previous work, start this one + this->items_.push_back(boost::make_unique(this->session_, std::move(msg))); // This msg is from the header + if (this->items_.size() == 1) (*this->items_.front())(); + } + + void HTTPSession::do_read() { + this->parser_.emplace(); // Construct a new parser for each message + this->parser_->body_limit(512000); // Apply a reasonable limit to body size in bytes to prevent abuse + // Read a request using the parser-oriented interface + http::async_read(this->stream_, this->buf_, *this->parser_, beast::bind_front_handler( + &HTTPSession::on_read, this->shared_from_this() + )); + } + + void HTTPSession::on_read(beast::error_code ec, std::size_t bytes) { + boost::ignore_unused(bytes); + // This means the other side closed the connection + if (ec == http::error::end_of_stream) return this->do_close(); + if (ec) return fail("HTTPSession", __func__, ec, "Failed to close connection"); + // Send the response + handle_request( + *this->docroot_, this->parser_->release(), this->queue_, this->faucet_ + ); + // If queue still has free space, try to pipeline another request + if (!this->queue_.full()) this->do_read(); + } + + void HTTPSession::on_write(bool close, beast::error_code ec, std::size_t bytes) { + boost::ignore_unused(bytes); + if (ec) return fail("HTTPSession", __func__, ec, "Failed to write to buffer"); + // This means we should close the connection, usually because the + // response indicated the "Connection: close" semantic + if (close) return this->do_close(); + // Inform the queue that a write was completed and read another request + if (this->queue_.on_write()) this->do_read(); + } + + void HTTPSession::do_close() { + // Send a TCP shutdown + beast::error_code ec; + this->stream_.socket().shutdown(tcp::socket::shutdown_send, ec); + // At this point the connection is closed gracefully + } + + void HTTPSession::start() { + net::dispatch(this->stream_.get_executor(), beast::bind_front_handler( + &HTTPSession::do_read, this->shared_from_this() + )); + } +} + diff --git a/src/bins/faucet-api/src/httpsession.h b/src/bins/faucet-api/src/httpsession.h new file mode 100644 index 00000000..b0424a6e --- /dev/null +++ b/src/bins/faucet-api/src/httpsession.h @@ -0,0 +1,126 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef HTTPSESSION_H +#define HTTPSESSION_H + +#include "httpparser.h" + +namespace Faucet { + // Forward declarations. + class Manager; + class HTTPSession; // HTTPQueue depends on HTTPSession and vice-versa + + /// Heler class used for HTTP pipelining. + class HTTPQueue { + private: + /// Type-erased, saved work item. + struct work { + virtual ~work() = default; ///< Default destructor. + virtual void operator()() = 0; ///< Default call operator. + }; + + unsigned int limit_ = 8; ///< Maximum number of responses to queue. + HTTPSession& session_; ///< Reference to the HTTP session that is handling the queue. + std::vector> items_; ///< Array of pointers to work structs. + + public: + /** + * Constructor. + * @param session Reference to the HTTP session that will handle the queue. + */ + explicit HTTPQueue(HTTPSession& session); + + /** + * Check if the queue limit was hit. + * @return `true` if queue is full, `false` otherwise. + */ + bool full() const; + + /** + * Callback for when a message is sent. + * @return `true` if the caller should read a message, `false` otherwise. + */ + bool on_write(); + + /** + * Call operator. + * Called by the HTTP handler to send a response. + * @param msg The message to send as a response. + */ + template void operator()( + http::message&& msg + ); + }; + + /// Class that handles an HTTP connection session. + class HTTPSession : public std::enable_shared_from_this { + private: + Manager& faucet_; ///< Reference pointer to the faucet singleton. + /// TCP/IP stream socket. + beast::tcp_stream stream_; + + /// Internal buffer to read and write from. + beast::flat_buffer buf_; + + /// Pointer to the root directory of the endpoint. + std::shared_ptr docroot_; + + /// Queue object that the session is responsible for. + HTTPQueue queue_; + + /** + * HTTP/1 parser for producing a request message. + * The parser is stored in an optional container so we can construct it + * from scratch at the beginning of each new message. + */ + boost::optional> parser_; + + /// Read whatever is on the internal buffer. + void do_read(); + + /** + * Callback for do_read(). + * Tries to pipeline another request if the queue isn't full. + * @param ec The error code to parse. + * @param bytes The number of read bytes. + */ + void on_read(beast::error_code ec, std::size_t bytes); + + /** + * Callback for when HTTPQueue writes something. + * Automatically reads another request. + * @param close If `true`, calls do_close() at the end. + * @param ec The error code to parse. + * @param bytes The number of written bytes. + */ + void on_write(bool close, beast::error_code ec, std::size_t bytes); + + /// Send a TCP shutdown and close the connection. + void do_close(); + + public: + /** + * Constructor. + * @param sock The socket to take ownership of. + * @param docroot Reference pointer to the root directory of the endpoint. + * @param faucet Reference to the faucet manager. + */ + HTTPSession(tcp::socket&& sock, + const std::shared_ptr& docroot, Manager& faucet + ) : stream_(std::move(sock)), docroot_(docroot), queue_(*this), faucet_(faucet) + { + stream_.expires_never(); + } + + /// Start the HTTP session. + void start(); + + friend class HTTPQueue; + }; +} +#endif // HTTPSESSION_H diff --git a/src/bins/faucet-api/src/jsonrpc/decoding.cpp b/src/bins/faucet-api/src/jsonrpc/decoding.cpp new file mode 100644 index 00000000..c4cf0e9b --- /dev/null +++ b/src/bins/faucet-api/src/jsonrpc/decoding.cpp @@ -0,0 +1,59 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "decoding.h" +#include "../faucetmanager.h" + +namespace Faucet { +namespace JsonRPC::Decoding { + + bool checkJsonRPCSpec(const json& request) { + try { + // "jsonrpc": "2.0" is a MUST + if (!request.contains("jsonrpc")) return false; + if (request["jsonrpc"].get() != "2.0") return false; + + // "method" is a MUST + if (!request.contains("method")) return false; + + // Params MUST be Object or Array. + if ( + request.contains("params") && (!request["params"].is_object() && !request["params"].is_array()) + ) return false; + + return true; + } catch (std::exception& e) { + SLOGERROR(std::string("Error while checking json RPC spec: ") + e.what()); + throw DynamicException("Error while checking json RPC spec: " + std::string(e.what())); + } + } + + Methods getMethod(const json& request) { + try { + const std::string& method = request["method"].get(); + auto it = methodsLookupTable.find(method); + if (it == methodsLookupTable.end()) return Methods::invalid; + return it->second; + } catch (std::exception& e) { + SLOGERROR(std::string("Error while getting method: ") + e.what()); + throw DynamicException("Error while checking json RPC spec: " + std::string(e.what())); + } + } + // https://www.jsonrpc.org/specification + void dripToAddress(const json& request, Manager& faucet) { + static const std::regex addFilter("^0x[0-9,a-f,A-F]{40}$"); + try { + const auto address = request["params"].at(0).get(); + if (!std::regex_match(address, addFilter)) throw DynamicException("Invalid address hex"); + faucet.dripToAddress(Address(Hex::toBytes(address))); + } catch (std::exception& e) { + SLOGERROR(std::string("Error while decoding dripToAddress: ") + e.what()); + throw DynamicException("Error while decoding dripToAddress: " + std::string(e.what())); + } + } +} +} diff --git a/src/bins/faucet-api/src/jsonrpc/decoding.h b/src/bins/faucet-api/src/jsonrpc/decoding.h new file mode 100644 index 00000000..beb387a8 --- /dev/null +++ b/src/bins/faucet-api/src/jsonrpc/decoding.h @@ -0,0 +1,51 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef JSONRPC_DECODING_H +#define JSONRPC_DECODING_H + +#include + +#include "utils/utils.h" + +#include "methods.h" + +// Forward declarations. +class Storage; + +namespace Faucet { + class Manager; + /** + * Namespace for decoding JSON-RPC data. + * All functions require a JSON object that is the request itself to be operated on. + */ + namespace JsonRPC::Decoding { + /** + * Helper function to get the method of the JSON-RPC request. + * @param request The request object. + * @return The method inside the request, or `invalid` if the method is not found. + */ + Methods getMethod(const json& request); + + /** + * Helper function to check if a given JSON-RPC request is valid. + * Does NOT check if the method called is valid, only if the request follows JSON-RPC 2.0 spec. + * @param request The request object. + * @return `true` if request is valid, `false` otherwise. + */ + bool checkJsonRPCSpec(const json& request); + + /** + * Helper function to check if a given JSON-RPC request is valid. + * Does NOT check if the method called is valid, only if the request follows JSON-RPC 2.0 spec. + * @param request The request object. + * @param faucet Reference to the faucet manager. + */ + void dripToAddress(const json& request, Manager& faucet); + } +} +#endif /// JSONRPC_DECODING_H diff --git a/src/bins/faucet-api/src/jsonrpc/encoding.cpp b/src/bins/faucet-api/src/jsonrpc/encoding.cpp new file mode 100644 index 00000000..3f4ae504 --- /dev/null +++ b/src/bins/faucet-api/src/jsonrpc/encoding.cpp @@ -0,0 +1,20 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "encoding.h" + +namespace Faucet { + namespace JsonRPC::Encoding { + json dripToAddress() { + json ret; + ret["jsonrpc"] = 2.0; + ret["result"] = "0x1"; + return ret; + } + } +} + diff --git a/src/bins/faucet-api/src/jsonrpc/encoding.h b/src/bins/faucet-api/src/jsonrpc/encoding.h new file mode 100644 index 00000000..fcd1a1ad --- /dev/null +++ b/src/bins/faucet-api/src/jsonrpc/encoding.h @@ -0,0 +1,24 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef JSONRPC_ENCODING_H +#define JSONRPC_ENCODING_H + +#include "utils/utils.h" + +namespace Faucet { + /// Namespace for encoding JSON-RPC data. + namespace JsonRPC::Encoding { + /** + * Encode a "dripToAddress" request. + * @return The formed request. + */ + json dripToAddress(); + } +} + +#endif // JSONRPC_ENCODING_H diff --git a/src/bins/faucet-api/src/jsonrpc/methods.h b/src/bins/faucet-api/src/jsonrpc/methods.h new file mode 100644 index 00000000..e550c9c9 --- /dev/null +++ b/src/bins/faucet-api/src/jsonrpc/methods.h @@ -0,0 +1,48 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef JSONRPC_METHODS_H +#define JSONRPC_METHODS_H + +#include + +/** + * Namespace for Ethereum's JSON-RPC Specification. + * + * See the following links for more info: + * + * https://ethereum.org/pt/developers/docs/apis/json-rpc/ (Most updated) + * + * https://ethereum.github.io/execution-apis/api-documentation/ (Has regex for the methods) + * + * https://eips.ethereum.org/EIPS/eip-1474#error-codes (Respective error codes) + */ + +namespace Faucet { + namespace JsonRPC { + /** + * Enum with all known methods for Ethereum's JSON-RPC. + * + * Check the following list for reference (`COMMAND === IMPLEMENTATION STATUS`): + * + * ``` + * invalid ==================================== N/A + * dripToAddress ============================== N/A + * ``` + */ + enum Methods { + invalid, + dripToAddress + }; + + /// Lookup table for the implemented methods. + inline extern const boost::unordered_flat_map methodsLookupTable = { + { "dripToAddress", dripToAddress }, + }; + } +} +#endif // JSONRPC_METHODS_H diff --git a/src/bins/network-sim/CMakeLists.txt b/src/bins/network-sim/CMakeLists.txt new file mode 100644 index 00000000..488f4bda --- /dev/null +++ b/src/bins/network-sim/CMakeLists.txt @@ -0,0 +1,43 @@ +if (BUILD_NETWORK_SIM) + add_library(network_sim_lib STATIC + ${CMAKE_SOURCE_DIR}/src/bins/network-sim/src/common.h + ${CMAKE_SOURCE_DIR}/src/bins/network-sim/src/httpclient.h + ${CMAKE_SOURCE_DIR}/src/bins/network-sim/src/networksimulator.h + ${CMAKE_SOURCE_DIR}/src/bins/network-sim/src/simulatorworker.h + ${CMAKE_SOURCE_DIR}/src/bins/network-sim/src/common.cpp + ${CMAKE_SOURCE_DIR}/src/bins/network-sim/src/httpclient.cpp + ${CMAKE_SOURCE_DIR}/src/bins/network-sim/src/networksimulator.cpp + ${CMAKE_SOURCE_DIR}/src/bins/network-sim/src/simulatorworker.cpp + ) + + target_include_directories(network_sim_lib PRIVATE + ${CMAKE_SOURCE_DIR}/include ${OPENSSL_INCLUDE_DIR} + ${ETHASH_INCLUDE_DIR} ${KECCAK_INCLUDE_DIR} + ${SPEEDB_INCLUDE_DIR} ${SECP256K1_INCLUDE_DIR} + ) + + target_link_libraries(network_sim_lib PRIVATE bdk_lib + ${CRYPTOPP_LIBRARIES} ${SCRYPT_LIBRARY} ${SECP256K1_LIBRARY} + ${ETHASH_LIBRARY} ${KECCAK_LIBRARY} ${SPEEDB_LIBRARY} + ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} + ) + + # Compile and link the ABI generator executable + add_executable(network-sim "main.cpp") + + add_dependencies(network-sim bdk_lib network_sim_lib) + + target_include_directories(network-sim PRIVATE + bdk_lib network_sim_lib ${OPENSSL_INCLUDE_DIR} + ${ETHASH_INCLUDE_DIR} ${KECCAK_INCLUDE_DIR} + ${SPEEDB_INCLUDE_DIR} ${SECP256K1_INCLUDE_DIR} + ) + + target_link_libraries(network-sim + bdk_lib network_sim_lib + ${SPEEDB_LIBRARY} ${SNAPPY_LIBRARY} ${Boost_LIBRARIES} + ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${SECP256K1_LIBRARY} + ${ETHASH_LIBRARY} ${KECCAK_LIBRARY} + ) +endif() + diff --git a/src/bins/network-sim/main.cpp b/src/bins/network-sim/main.cpp new file mode 100644 index 00000000..4dd09c3c --- /dev/null +++ b/src/bins/network-sim/main.cpp @@ -0,0 +1,222 @@ +#include "src/networksimulator.h" +#include + +/** + * BDK Network Simulator + * Built to stress and test the capabilities of the BDK network + * It requires a running instance BDK network to connect to (AIO-setup.sh is recommended for local instances) + * It works as following: + * 1. The simulator will setup a given number of accounts (Packet size * Worker size) + * with a given amount of native tokens using the chain owner private key. + * 2. The simulator will then create X workers where each worker + * will send a "packet" containing one transaction from each account to the chain owner, given that worker HTTP port + * containing a given amount of native tokens. + * 3. Each worker will follow the following cycle: + * - Create a packet containing a transaction from each account to the chain owner + * - Send the packet containing the transactions through the respective worker HTTP port + * - Wait for the packet to be confirmed by the network + * + * 4. The simulator will count and print the following + * - Packet creation time + * - Packet send time + * - Packet confirmation time + * - Total time + * + * When executing the simulator, it will ask for the following parameters: + * 1. Chain owner PrivKey: The private key of the chain owner + * 2. Chain ID: The ID of the chain to connect to (for transactions) + * 3. Packet size: The number of transactions to send in each packet (also the number of accounts to create) + * 4. Packet count: The number of packets to send + * 5. Init Native balance (wei): The amount of native tokens to send to each account + * 6. Tx Native balance (wei): The amount of native tokens to send in each transaction + * 7. Worker threads: The number of worker threads to use to create/send the packets + * 8. HTTP IP:PORT: The IP and port of each worker HTTP server + * + */ + +int main() { + /// Initial params. + PrivKey chainOwnerPrivKey(Hex::toBytes("0xe89ef6409c467285bcae9f80ab1cfeb3487cfe61ab28fb7d36443e1daa0c2867")); + uint64_t chainId = 808080; + uint64_t packetSize = 5000; + uint64_t packetCount = 10000; + uint256_t initNativeBalance = uint256_t("100000000000000000000000"); // (100000.00) + uint256_t txNativeBalance = uint256_t("1000000000000"); // (0.000001) + uint64_t workerThreads = 1; + std::vector> httpEndpoints { }; + + std::cout << "Welcome to the BDK Network Simulator" << std::endl; + std::cout << "This simulator is designed to test and stress the live network capabilities of BDK" << std::endl; + std::cout << "Please see the source code comments for more information on how configure and use this simulator" << std::endl; + + std::cout << "Please type the chain owner private key, nothing for default: " << std::endl;; + std::string chainOwnerPrivKeyStr; + std::getline(std::cin, chainOwnerPrivKeyStr); + + if (!chainOwnerPrivKeyStr.empty()) { + static const std::regex hashFilter("^0x[0-9,a-f,A-F]{64}$"); + if (!std::regex_match(chainOwnerPrivKeyStr, hashFilter)) { + std::cout << "Invalid private key" << std::endl; + return 1; + } + chainOwnerPrivKey = PrivKey(Hex::toBytes(chainOwnerPrivKeyStr)); + }; + + std::cout << "Please provide the chain Id: nothing for default (808080)" << std::endl; + std::string chainIdStr; + std::getline(std::cin, chainIdStr); + if (!chainIdStr.empty()) + { + for (const auto& c : chainIdStr) { + if (!std::isdigit(c)) { + std::cout << "Invalid chain Id" << std::endl; + return 1; + } + } + chainId = std::stoull(chainIdStr); + } + + std::cout << "Please provide the packet size: nothing for default (5000)" << std::endl; + std::string packetSizeStr; + std::getline(std::cin, packetSizeStr); + if (!packetSizeStr.empty()) + { + for (const auto& c : packetSizeStr) { + if (!std::isdigit(c)) { + std::cout << "Invalid packet size" << std::endl; + return 1; + } + } + packetSize = std::stoull(packetSizeStr); + if (packetSize > 100000) { + std::cout << "Packet size is too large" << std::endl; + return 1; + } + } + + std::cout << "Please provide a packet count: nothing for default (10000)" << std::endl; + std::string packetCountStr; + std::getline(std::cin, packetCountStr); + if (!packetCountStr.empty()) + { + for (const auto& c : packetCountStr) { + if (!std::isdigit(c)) { + std::cout << "Invalid packet count" << std::endl; + return 1; + } + } + packetCount = std::stoull(packetCountStr); + } + + + std::cout << "Please provide the initial native balance (wei): nothing for default (100000000000000000000000)" << std::endl;; + std::string initNativeBalanceStr; + std::getline(std::cin, initNativeBalanceStr); + if (!initNativeBalanceStr.empty()) + { + for (const auto& c : initNativeBalanceStr) { + if (!std::isdigit(c)) { + std::cout << "Invalid initial native balance" << std::endl; + return 1; + } + } + initNativeBalance = uint256_t(initNativeBalanceStr); + } + + std::cout << "Please provide the transaction native balance (wei): nothing for default (1000000000000)" << std::endl;; + std::string txNativeBalanceStr; + std::getline(std::cin, txNativeBalanceStr); + // Check if txNativeBalanceStr is a number + if (!txNativeBalanceStr.empty()) + { + for (const auto& c : txNativeBalanceStr) { + if (!std::isdigit(c)) { + std::cout << "Invalid transaction native balance" << std::endl; + return 1; + } + } + txNativeBalance = uint256_t(txNativeBalanceStr); + } + + std::cout << "Please provide the number of worker threads: nothing for default (1)" << std::endl; + std::string workerThreadsStr; + std::getline(std::cin, workerThreadsStr); + if (!workerThreadsStr.empty()) + { + for (const auto& c : workerThreadsStr) { + if (!std::isdigit(c)) { + std::cout << "Invalid worker threads" << std::endl; + return 1; + } + } + workerThreads = std::stoull(workerThreadsStr); + } + + for (uint64_t i = 0; i < workerThreads; i++) { + std::cout << "Please provide the HTTP IP:PORT for worker " << i << ": (format: \"IP:PORT\", nothing for default (127.0.0.1, 8090))" << std::endl; + std::string httpEndpointStr; + std::getline(std::cin, httpEndpointStr); + if (httpEndpointStr.empty()) { + httpEndpoints.push_back(std::make_pair(net::ip::address_v4::from_string("127.0.0.1"), 8090)); + } else { + std::vector parts; + boost::split(parts, httpEndpointStr, boost::is_any_of(":")); + if (parts.size() != 2) { + std::cout << "Invalid HTTP endpoint" << std::endl; + return 1; + } + boost::system::error_code ec; + auto ip = net::ip::address_v4::from_string(parts[0], ec); + if (ec) { + throw std::string("Invalid IP address: " + ec.message()); + } + + for (const char& c : parts[1]) { + if (!std::isdigit(c)) { + std::cout << "Invalid port" << std::endl; + return 1; + } + } + + uint64_t port = std::stoull(parts[1]); + if (port > 65535) { + std::cout << "Invalid port" << std::endl; + return 1; + } + httpEndpoints.emplace_back(ip, port); + } + } + + + std::cout << std::endl << std::endl << std::endl; + std::cout << "Starting the BDK Network Simulator" << std::endl; + std::cout << "Chain owner private key: " << chainOwnerPrivKey.hex(true) << std::endl; + std::cout << "Chain ID: " << chainId << std::endl; + std::cout << "Packet size: " << packetSize << std::endl; + std::cout << "Packet count: " << packetCount << std::endl; + std::cout << "Initial native balance (wei): " << initNativeBalance.str() << std::endl; + std::cout << "Transaction native balance (wei): " << txNativeBalance.str() << std::endl; + std::cout << "Worker threads: " << workerThreads << std::endl; + std::cout << "HTTP endpoints: " << std::endl; + for (const auto& endpoint : httpEndpoints) { + std::cout << " " << std::get<0>(endpoint).to_string() << ":" << std::get<1>(endpoint) << std::endl; + } + + NetworkSimulator simulator( + chainOwnerPrivKey, + chainId, + packetSize, + packetCount, + initNativeBalance, + txNativeBalance, + workerThreads, + httpEndpoints + ); + + simulator.setup(); + std::cout << "Press anything to start the simulation..." << std::endl; + std::cin.get(); + simulator.run(); + + return 0; +} \ No newline at end of file diff --git a/src/bins/network-sim/src/common.cpp b/src/bins/network-sim/src/common.cpp new file mode 100644 index 00000000..a00ac065 --- /dev/null +++ b/src/bins/network-sim/src/common.cpp @@ -0,0 +1 @@ +// Nothing here... yet \ No newline at end of file diff --git a/src/bins/network-sim/src/common.h b/src/bins/network-sim/src/common.h new file mode 100644 index 00000000..0961e8b8 --- /dev/null +++ b/src/bins/network-sim/src/common.h @@ -0,0 +1,38 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef COMMON_H +#define COMMON_H + +#include "../../../utils/utils.h" +#include "../../../utils/tx.h" + +/// Helper struct for a worker account. +struct WorkerAccount { + const PrivKey privKey; ///< The account's private key. + const Address address; ///< The account's address. + uint64_t nonce; ///< The account's nonce. + + /** + * Constructor. + * @param privKey Private key of the account. + */ + explicit WorkerAccount (const PrivKey& privKey) + : privKey(privKey), address(Secp256k1::toAddress(Secp256k1::toUPub(privKey))), nonce(0) {} +}; + +/** + * Create a JSON request method. + * @param method The method to create. + * @param The params of the method. + * @return The serialized JSON request. + */ +template std::string makeRequestMethod(const std::string& method, const T& params) { + return json({ {"jsonrpc", "2.0"}, {"id", 1}, {"method", method}, {"params", params} }).dump(); +} + +#endif // COMMON_H diff --git a/src/bins/network-sim/src/httpclient.cpp b/src/bins/network-sim/src/httpclient.cpp new file mode 100644 index 00000000..76c3cbae --- /dev/null +++ b/src/bins/network-sim/src/httpclient.cpp @@ -0,0 +1,69 @@ +#include "httpclient.h" + +HTTPSyncClient::HTTPSyncClient(const std::string& host, const std::string& port) + : host(host), port(port), resolver(ioc), stream(ioc) { + this->connect(); +} + +HTTPSyncClient::~HTTPSyncClient() { + this->close(); +} + +void HTTPSyncClient::connect() { + boost::system::error_code ec; + auto const results = resolver.resolve(host, port, ec); + if (ec) { + throw std::runtime_error("Error while resolving the HTTP Client: " + ec.message()); + } + stream.connect(results, ec); + if (ec) { + throw std::runtime_error("Error while connecting the HTTP Client: " + ec.message()); + } +} + +void HTTPSyncClient::close() { + boost::system::error_code ec; + stream.socket().shutdown(tcp::socket::shutdown_both, ec); + if (ec) { + throw std::runtime_error("Error while closing the HTTP Client: " + ec.message()); + } +} + +std::string HTTPSyncClient::makeHTTPRequest( + const std::string& reqBody +) { + namespace http = boost::beast::http; // from + + boost::system::error_code ec; + // Set up an HTTP POST/GET request message + http::request req{ http::verb::post , "/", 11}; + + req.set(http::field::host, host); + req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + req.set(http::field::accept, "application/json"); + req.set(http::field::content_type, "application/json"); + req.body() = reqBody; + req.prepare_payload(); + + // Send the HTTP request to the remote host + http::write(stream, req, ec); + if (ec) { + throw std::runtime_error("Error while writing the HTTP request: " + ec.message()); + } + + boost::beast::flat_buffer buffer; + // Declare a container to hold the response + http::response res; + + // Receive the HTTP response + http::read(stream, buffer, res, ec); + if (ec) { + throw std::runtime_error("Error while reading the HTTP response: " + ec.message() + " " + std::to_string(ec.value())); + } + + // Write only the body answer to output + return { + boost::asio::buffers_begin(res.body().data()), + boost::asio::buffers_end(res.body().data()) + }; +} \ No newline at end of file diff --git a/src/bins/network-sim/src/httpclient.h b/src/bins/network-sim/src/httpclient.h new file mode 100644 index 00000000..28da7b0c --- /dev/null +++ b/src/bins/network-sim/src/httpclient.h @@ -0,0 +1,59 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef HTTPCLIENT_H +#define HTTPCLIENT_H + +#include +#include +#include +#include +#include +#include + +namespace beast = boost::beast; // from +namespace http = beast::http; // from +namespace websocket = beast::websocket; // from +namespace net = boost::asio; // from +using tcp = boost::asio::ip::tcp; // from + +/// Class for an HTTP sync client. +class HTTPSyncClient { + private: + const std::string host; ///< The host the client will connect to. + const std::string port; ///< The port the client will connect to. + boost::asio::io_context ioc; ///< I/O context object for the client. + tcp::resolver resolver; ///< TCP/IP resolver. + beast::tcp_stream stream; ///< Buffer stream. + + public: + /** + * Constructor. + * @param host The host the client will connect to. + * @param port The port the client will connect to. + */ + HTTPSyncClient(const std::string& host, const std::string& port); + + ~HTTPSyncClient(); ///< Destructor. + HTTPSyncClient(const HTTPSyncClient&) = delete; ///< Copy constructor (deleted, Rule of Zero). + HTTPSyncClient& operator=(const HTTPSyncClient&) = delete; ///< Copy assignment operator. (deleted, Rule of Zero). + HTTPSyncClient(HTTPSyncClient&&) = delete; ///< Move constructor. (deleted, Rule of Zero). + HTTPSyncClient& operator=(HTTPSyncClient&&) = delete; ///< Move assignment operator. (deleted, Rule of Zero). + + void connect(); ///< Start the connection. + + void close(); ///< Close the connection. + + /** + * Perform an HTTP request to the endpoint. + * @param reqBody The request body. + * @return A string containing the result of the request. + */ + std::string makeHTTPRequest(const std::string& reqBody); +}; + +#endif // HTTPCLIENT_H diff --git a/src/bins/network-sim/src/networksimulator.cpp b/src/bins/network-sim/src/networksimulator.cpp new file mode 100644 index 00000000..fed20e24 --- /dev/null +++ b/src/bins/network-sim/src/networksimulator.cpp @@ -0,0 +1,172 @@ +#include "networksimulator.h" + +NetworkSimulator::NetworkSimulator( + const PrivKey& chainOwnerPrivKey, + const uint64_t& chainId, + const uint64_t& packetSize, + const uint64_t& packetCount, + const uint256_t& initNativeBalance, + const uint256_t& txNativeBalance, + const uint64_t& workerThreads, + const std::vector>& httpEndpoints +) : chainOwnerPrivKey_(chainOwnerPrivKey), + chainId_(chainId), + packetSize_(packetSize), + packetCount_(packetCount), + initNativeBalance_(initNativeBalance), + txNativeBalance_(txNativeBalance), + workerThreads_(workerThreads), + httpEndpoints_(httpEndpoints) {} + + +void NetworkSimulator::setup() { + std::cout << "Setting up the network simulator..." << std::endl; + std::cout << "Creating accounts for each worker..." << std::endl; + for (uint64_t i = 0; i < workerThreads_; i++) { + std::vector accounts; + for (uint64_t j = 0; j < packetSize_; j++) { + Utils::randBytes(32); + accounts.emplace_back(PrivKey(Utils::randBytes(32))); + } + accounts_.emplace_back(accounts); + } + + std::cout << "Accounts created!" << std::endl; + + std::cout << "Creating the necessary transactions from the chain owner to the workers accounts..." << std::endl; + std::vector> packets; + + // Using only the chainOwnerAccount for now is extremely inefficient as only one transaction (one nonce) can be created at a time from each account. + // We must prepare packets in multiples of 2 using the accounts_ themselves. + // For this we need to calculate the balance for each packet (initNativeBalance_ * packetSize_ * workerThreads_) and then + // Half the value for each packet + // Example: + // Init balance: 1000000000000000000 + // 100 Total accounts + // First packet TxValue: 1000000000000000000: ChainOwner -> Account001 + // Second Packet TxValue: 500000000000000000: ChainOwner -> Account002 | Account001 -> Account003 + // Third Packet: TxValue: 250000000000000000: ChainOwner -> Account004 | Account001 -> Account005 | Account002 -> Account006 | Account003 -> Account007 + // Fourth Packet: TxValue: 125000000000000000: ChainOwner -> Account008 | Account001 -> Account009 | Account002 -> Account010 | Account003 -> Account011 + // | Account004 -> Account012 | Account005 -> Account013 | Account006 -> Account014 | Account007 -> Account015 + // And so on... Until we fill all 100 accounts + + { + // Reference wrapper so we can modify the nonce of the accounts within the accounts_ vector + // each of the vector inside accounts_ will be passed to a SimulatorWorker instance when run() is called + std::vector> allAccounts; + for (auto& accounts : accounts_) { + for (auto& account : accounts) { + allAccounts.emplace_back(account); + } + } + std::vector chainOwnerAccount = { WorkerAccount(this->chainOwnerPrivKey_) }; + uint256_t txValue = initNativeBalance_ * packetSize_ * workerThreads_; + std::vector> createTxAccounts; + createTxAccounts.emplace_back(chainOwnerAccount[0]); + + uint64_t currentTo = 0; + while (createTxAccounts.size() < allAccounts.size()) { + std::vector packet; + auto currentCreateTxAccounts = createTxAccounts; + for (auto& account : currentCreateTxAccounts) { + if (currentTo >= allAccounts.size()) { + break; + } + packet.emplace_back( + makeRequestMethod("eth_sendRawTransaction", + json::array({Hex::fromBytes( + TxBlock( + allAccounts[currentTo].get().address, + account.get().address, + {}, + 808080, + account.get().nonce, + txValue, + 1000000000, + 1000000000, + 21000, + account.get().privKey).rlpSerialize() + ,true).forRPC()}))); + createTxAccounts.emplace_back(allAccounts[currentTo]); + ++account.get().nonce; + ++currentTo; + } + txValue = txValue / 2; + packets.emplace_back(packet); + } + } + + std::cout << "Transactions created!" << std::endl; + std::cout << "Sending the transactions to the HTTP endpoints..." << std::endl; + HTTPSyncClient client( + httpEndpoints_[0].first.to_string(), + std::to_string(httpEndpoints_[0].second) + ); + uint64_t totalPackets = 0; + + for (const auto& packet : packets) { + totalPackets += packet.size(); + } + + std::cout << "Sending setup transactions (total of " << totalPackets << " txs)..." << std::endl; + for (const auto& packet : packets) { + auto startTime = std::chrono::high_resolution_clock::now(); + std::cout << "Sending " << packet.size() << " txs..." << std::endl; + auto packetHash = SimulatorWorker::sendTransactions(packet, client); + std::this_thread::sleep_for(std::chrono::microseconds(500)); + std::cout << "Confirming " << packetHash.size() << " txs..." << std::endl; + SimulatorWorker::confirmTransactions(packetHash, client); + auto endTime = std::chrono::high_resolution_clock::now(); + std::cout << "Time taken: " << std::chrono::duration_cast(endTime - startTime).count() << " ms" << std::endl; + } + std::cout << "Setup complete! Dumping privkeys to privkeys.txt" << std::endl; + // Write to "privkeys.txt" file one line per hex private key + std::ofstream privKeysFile("privkeys.txt"); + for (auto& accounts : accounts_) { + for (auto& account : accounts) { + privKeysFile << account.privKey.hex() << std::endl; + } + } + privKeysFile.close(); +} + +void NetworkSimulator::run() { + std::vector> workers; + std::cout << "Creating worker threads..." << std::endl; + for (uint64_t i = 0; i < workerThreads_; i++) { + workers.emplace_back(std::make_unique( + httpEndpoints_[i], + accounts_[i], + chainId_, + txNativeBalance_ + ) + ); + } + + std::cout << "Starting worker threads..." << std::endl; + for (auto& worker : workers) { + worker->run(); + } + + uint64_t totalSentPackets = 0; + while (totalSentPackets < this->packetCount_) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + uint64_t totalTransactionCreationTime = 0; + uint64_t totalTransactionSendTime = 0; + uint64_t totalTransactionConfirmTime = 0; + for (uint64_t i = 0; i < workerThreads_; i++) { + std::cout << "Worker " << i << " - Create: " << workers[i]->getCreateTransactionTime() << " ms, Send: " << workers[i]->getSendTransactionTime() << " ms, Confirm: " << workers[i]->getConfirmTransactionTime() << " ms" << std::endl; + totalTransactionCreationTime += workers[i]->getCreateTransactionTime(); + totalTransactionSendTime += workers[i]->getSendTransactionTime(); + totalTransactionConfirmTime += workers[i]->getConfirmTransactionTime(); + totalSentPackets += workers[i]->getTotalSentPackets(); + } + std::cout << "Average - Create: " << totalTransactionCreationTime / workerThreads_ << " ms, Send: " << totalTransactionSendTime / workerThreads_ << " ms, Confirm: " << totalTransactionConfirmTime / workerThreads_ << " ms" << std::endl; + std::cout << "Total transactions sent: " << totalSentPackets * packetSize_ << std::endl; + } + + std::cout << "Packet count reached! Stopping worker threads..." << std::endl; + for (auto& worker : workers) { + worker->stop(); + } +} \ No newline at end of file diff --git a/src/bins/network-sim/src/networksimulator.h b/src/bins/network-sim/src/networksimulator.h new file mode 100644 index 00000000..f69d44c7 --- /dev/null +++ b/src/bins/network-sim/src/networksimulator.h @@ -0,0 +1,60 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef NETWORKSIMULATOR_H +#define NETWORKSIMULATOR_H + +#include "httpclient.h" +#include "common.h" +#include "simulatorworker.h" + +/// Network Simulator class. Used to manage and coordinate the workers on the network simulator. +class NetworkSimulator { + private: + const PrivKey chainOwnerPrivKey_; ///< Private key of the chain owner address. + const uint64_t chainId_; ///< Chain ID of the network. + const uint64_t packetSize_; ///< Network packet size. + const uint64_t packetCount_; ///< Network packet count. + const uint256_t initNativeBalance_; ///< Initial native balance. + const uint256_t txNativeBalance_; ///< Transaction native balance. + const uint64_t workerThreads_; ///< Number of worker threads. + const std::vector> httpEndpoints_; ///< List of active endpoints in the network. + std::vector> accounts_; ///< Vector of accounts for each worker + + public: + /** + * Constructor. + * @param chainOwnerPrivKey Private key of the chain owner address. + * @param chainId Chain ID of the network. + * @param packetSize Network packet size. + * @param packetCount Network packet count. + * @param initNativeBalance Initial native balance. + * @param txNativeBalance Transaction native balance. + * @param workerThreads Number of worker threads. + * @param httpEndpoints List of active endpoints in the network. + */ + NetworkSimulator( + const PrivKey& chainOwnerPrivKey, + const uint64_t& chainId, + const uint64_t& packetSize, + const uint64_t& packetCount, + const uint256_t& initNativeBalance, + const uint256_t& txNativeBalance, + const uint64_t& workerThreads, + const std::vector>& httpEndpoints + ); + + NetworkSimulator(const NetworkSimulator& other) = delete; ///< Copy constructor (deleted, Rule of Zero) + NetworkSimulator(NetworkSimulator&& other) = delete; ///< Move constructor (deleted, Rule of Zero) + NetworkSimulator& operator=(const NetworkSimulator& other) = delete; ///< Copy assignment operator (deleted, Rule of Zero) + NetworkSimulator& operator=(NetworkSimulator&& other) = delete; ///< Move assignment operator (deleted, Rule of Zero) + + void setup(); ///< Setup the network simulator. + void run(); ///< Run the network simulator. +}; + +#endif // NETWORKSIMULATOR_H diff --git a/src/bins/network-sim/src/simulatorworker.cpp b/src/bins/network-sim/src/simulatorworker.cpp new file mode 100644 index 00000000..37e1eeee --- /dev/null +++ b/src/bins/network-sim/src/simulatorworker.cpp @@ -0,0 +1,114 @@ +#include "simulatorworker.h" + + + +SimulatorWorker::SimulatorWorker( + const std::pair& httpEndpoint, + const std::vector& accounts, const uint64_t& chainId, const uint256_t& txNativeBalance +) : client_(httpEndpoint.first.to_string(), std::to_string(httpEndpoint.second)), + httpEndpoint_(httpEndpoint), + accounts_(accounts), + chainId_(chainId), + txNativeBalance_(txNativeBalance) {} + + +/// Create a number of transactions based on the number of accounts +std::vector SimulatorWorker::createTransactions(std::vector& accounts, + const uint256_t& txNativeBalance, + const uint64_t& chainId, + const Address& to) { + std::vector packet; + for (auto& account : accounts) { + packet.emplace_back( + makeRequestMethod("eth_sendRawTransaction", + json::array({Hex::fromBytes( + TxBlock( + to, + account.address, + {}, + chainId, + account.nonce, + txNativeBalance, + 1000000000, + 1000000000, + 21000, + account.privKey).rlpSerialize() + ,true).forRPC()}))); + ++account.nonce; + } + return packet; +} + +/// Send transactions to the HTTP endpoint +std::vector> SimulatorWorker::sendTransactions(const std::vector& packet, + HTTPSyncClient& client) { + std::vector> packetHashes; + for (auto& tx : packet) { + auto response = client.makeHTTPRequest(tx); + auto json = json::parse(response); + if (json.contains("error")) { + throw std::runtime_error("Error while sending transactions: sent: " + tx + " received: " + json.dump()); + } + packetHashes.emplace_back(Hex::toBytes(json["result"].get()), false); + } + return packetHashes; +} + +/// Wait for all transactions to be confirmed +void SimulatorWorker::confirmTransactions(std::vector>& packetHashes, + HTTPSyncClient& client) { + for (auto& tx : packetHashes) { + while (tx.second == false) + { + std::this_thread::sleep_for(std::chrono::microseconds(100)); + auto response = client.makeHTTPRequest(makeRequestMethod("eth_getTransactionReceipt", json::array({tx.first.hex(true).get() }))); + auto json = json::parse(response); + if (json.contains("error")) { + throw std::runtime_error("Error while confirming transactions: sent: " + tx.first.hex(true).get() + " received: " + json.dump()); + } + if (json["result"].is_null()) { + tx.second = false; + } else { + if (json["result"]["status"] == "0x1") { + tx.second = true; + } else { + throw std::runtime_error("Error while confirming transactions: sent: " + tx.first.hex(true).get() + " received: " + json.dump()); + } + } + } + } +} + +void SimulatorWorker::work() { + while (!this->stop_) + { + auto createTxStartTime = std::chrono::high_resolution_clock::now(); + this->packet_ = createTransactions(this->accounts_, this->txNativeBalance_, this->chainId_, this->accounts_[0].address); + auto createTxEndTime = std::chrono::high_resolution_clock::now(); + this->createTransactionTime_ = std::chrono::duration_cast(createTxEndTime - createTxStartTime).count(); + auto sendTxStartTime = std::chrono::high_resolution_clock::now(); + this->packetHashes_ = sendTransactions(this->packet_, this->client_); + auto sendTxEndTime = std::chrono::high_resolution_clock::now(); + this->sendTransactionTime_ = std::chrono::duration_cast(sendTxEndTime - sendTxStartTime).count(); + auto confirmTxStartTime = std::chrono::high_resolution_clock::now(); + confirmTransactions(this->packetHashes_, this->client_); + auto confirmTxEndTime = std::chrono::high_resolution_clock::now(); + this->confirmTransactionTime_ = std::chrono::duration_cast(confirmTxEndTime - confirmTxStartTime).count(); + this->packet_.clear(); + this->packetHashes_.clear(); + ++totalSentPackets_; + } +} + +void SimulatorWorker::stop() { + this->stop_ = true; + this->workerThread_.join(); +} + +void SimulatorWorker::run() { + this->stop_ = false; + this->confirmTransactionTime_ = 0; + this->createTransactionTime_ = 0; + this->sendTransactionTime_ = 0; + this->workerThread_ = std::thread(&SimulatorWorker::work, this); +} diff --git a/src/bins/network-sim/src/simulatorworker.h b/src/bins/network-sim/src/simulatorworker.h new file mode 100644 index 00000000..bda937c0 --- /dev/null +++ b/src/bins/network-sim/src/simulatorworker.h @@ -0,0 +1,95 @@ +#ifndef SIMULATORWORKER_H +#define SIMULATORWORKER_H + +#include "httpclient.h" +#include "common.h" +#include + +/** + * Worker implementation for the network simulator + * Constructor automatically connects to the HTTP endpoint + * Execution pattern when running the worker: + * 1 - Create accounts_.size() transactions with the packet_ size + * 2 - Send all transactions from packet_ to the HTTP endpoint and store the hashes in packetHashes_ + * 3 - Wait for all transactions to be confirmed + * 4 - Empty the packet_ and packetHashes_ vectors and repeat + */ +class SimulatorWorker { + private: + HTTPSyncClient client_; ///< HTTP sync client object. + const std::pair httpEndpoint_; ///< HTTP endpoint to be used. + std::vector accounts_; ///< List of accounts to be used when creating transactions. + const uint64_t chainId_; ///< Chain ID to be used when creating transactions. + const uint256_t txNativeBalance_; ///< Balance to be used when creating transactions. + std::vector packet_; ///< Transactions (serialized) to be sent. + std::vector> packetHashes_; ///< Hashes of the transactions and their confirmation status. + std::atomic createTransactionTime_; ///< Maximum time spent to create a transaction. + std::atomic sendTransactionTime_; ///< Maximum time spent to send a transaction. + std::atomic confirmTransactionTime_; ///< Maximum time spent to confirm a transaction. + std::atomic totalSentPackets_; ///< Total number of packets sent in the network simulator. + std::atomic stop_; ///< Flag to stop the network simulator. + std::thread workerThread_; ///< Thread to run the network simulator worker. + + void work(); ///< Worker function for the network simulator. + + public: + /** + * Constructor. + * @param httpEndpoint HTTP endpoint to be used. + * @param accounts List of accounts to be used when creating transactions. + * @param chainId Chain ID to be used when creating transactions. + * @param txNativeBalance Balance to be used when creating transactions. + */ + SimulatorWorker( + const std::pair& httpEndpoint, + const std::vector& accounts, const uint64_t& chainId, const uint256_t& txNativeBalance + ); + + SimulatorWorker(const SimulatorWorker& other) = delete; ///< Copy constructor (deleted, Rule of Zero) + SimulatorWorker(SimulatorWorker&& other) = delete; ///< Move constructor (deleted, Rule of Zero) + SimulatorWorker& operator=(const SimulatorWorker& other) = delete; ///< Copy assignment operator (deleted, Rule of Zero) + SimulatorWorker& operator=(SimulatorWorker&& other) = delete; ///< Move assignment operator (deleted, Rule of Zero) + + void run(); ///< Run loop as described in the class description. + void stop(); ///< Stop the worker + + /** + * Create a number of transactions based on the number of accounts. + * @param accounts List of accounts to be used when creating transactions. + * @param chainId Chain ID to be used when creating transactions. + * @param txNativeBalance Balance to be used when creating transactions. + * @param to The address to be used when creating the transactions. + */ + static std::vector createTransactions( + std::vector& accounts, const uint256_t& txNativeBalance, + const uint64_t& chainId, const Address& to + ); + + /** + * Send transactions to the HTTP endpoint + * @param packet The packet to send. + * @param client Reference to the HTTP sync client. + */ + static std::vector> sendTransactions( + const std::vector& packet, HTTPSyncClient& client + ); + + /** + * Wait for all transactions to be confirmed. + * @param packetHashes The list of packet hashes to confirm. + * @param client Reference to the HTTP sync client. + */ + static void confirmTransactions( + std::vector>& packetHashes, HTTPSyncClient& client + ); + + ///@{ + /** Getter. */ + inline uint64_t getCreateTransactionTime() const { return createTransactionTime_; } + inline uint64_t getSendTransactionTime() const { return sendTransactionTime_; } + inline uint64_t getConfirmTransactionTime() const { return confirmTransactionTime_; } + inline uint64_t getTotalSentPackets() const { return totalSentPackets_; } + ///@} +}; + +#endif // SIMULATORWORKER_H diff --git a/src/bins/networkdeployer/CMakeLists.txt b/src/bins/networkdeployer/CMakeLists.txt new file mode 100644 index 00000000..73564631 --- /dev/null +++ b/src/bins/networkdeployer/CMakeLists.txt @@ -0,0 +1,16 @@ +# Compile and link the ABI generator executable +add_executable(networkdeployer "main.cpp") + +add_dependencies(networkdeployer bdk_lib) + +target_include_directories(networkdeployer PRIVATE + bdk_lib ${OPENSSL_INCLUDE_DIR} ${ETHASH_INCLUDE_DIR} ${KECCAK_INCLUDE_DIR} + ${SPEEDB_INCLUDE_DIR} ${SECP256K1_INCLUDE_DIR} +) + +target_link_libraries(networkdeployer + bdk_lib ${SPEEDB_LIBRARY} ${SNAPPY_LIBRARY} ${Boost_LIBRARIES} + ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${SECP256K1_LIBRARY} + ${ETHASH_LIBRARY} ${KECCAK_LIBRARY} +) + diff --git a/src/networkdeployer.cpp b/src/bins/networkdeployer/main.cpp similarity index 71% rename from src/networkdeployer.cpp rename to src/bins/networkdeployer/main.cpp index b7560d31..a52bcfed 100644 --- a/src/networkdeployer.cpp +++ b/src/bins/networkdeployer/main.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -14,8 +14,9 @@ int main() { std::string deployerScriptPath = std::filesystem::current_path().string() + std::string("/scripts/AIO-setup.sh"); std::string scriptCommand = deployerScriptPath + " --only-deploy"; const char *cmd = scriptCommand.c_str(); - int result = system(cmd); - if (result != 0) std::cerr << "Script execution failed with error code " << result << std::endl; + if (int result = system(cmd); result != 0) { + std::cerr << "Script execution failed with error code " << result << std::endl; + } return 0; } diff --git a/src/bytes/cast.h b/src/bytes/cast.h new file mode 100644 index 00000000..086cf140 --- /dev/null +++ b/src/bytes/cast.h @@ -0,0 +1,30 @@ +#ifndef BDK_BYTES_CAST_H +#define BDK_BYTES_CAST_H + +#include +#include "range.h" + +namespace bytes { + +/** + * Casts a range of bytes from one type to another. + * + * @param src the input bytes to be cast + * @param dest the destiny bytes container + * @return the destiny bytes container + * @throws invalid argument exception on size incompatibility + */ +template +constexpr R cast(const Range auto& src, R dest = R()) { + if (std::ranges::size(src) != std::ranges::size(dest)) { + throw std::invalid_argument("incompatible sizes for casting"); + } + + std::ranges::copy(src, std::ranges::begin(dest)); + + return dest; +} + +} // namespace bytes + +#endif // BDK_BYTES_CAST_H diff --git a/src/bytes/hex.h b/src/bytes/hex.h new file mode 100644 index 00000000..6f898539 --- /dev/null +++ b/src/bytes/hex.h @@ -0,0 +1,52 @@ +#ifndef BDK_BYTES_HEX_H +#define BDK_BYTES_HEX_H + +#include +#include +#include "range.h" +#include "initializer.h" +#include "utils/hex.h" + +namespace bytes { + +/** + * Creates a sized initializer from a hex representation. The initializer will + * fill the container data with by converting the hexadecimal representation + * into binary data. + * @param str the hex string + * @return the sized initializer + */ +constexpr SizedInitializer auto hex(std::string_view str) { + if (str.size() >= 2 && str.starts_with("0x")) { + str = str.substr(2); + } + + if (str.size() % 2) { + throw std::invalid_argument("the length of hex string is required to be even number"); + } + + return makeInitializer(str.size() / 2, [str] (Byte* dest) { + const auto value = [] (char c) -> Byte { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else + throw std::invalid_argument("character '" + std::to_string(c) + "' is invalid in hex string"); + }; + + auto it = str.begin(); + + while (it != str.end()) { + const Byte l = value(*it++); + const Byte r = value(*it++); + *dest++ = (l << 4) | r; + } + }); +} + +} // namespace bytes + +#endif // BDK_BYTES_HEX_H diff --git a/src/bytes/initializer.h b/src/bytes/initializer.h new file mode 100644 index 00000000..507eed05 --- /dev/null +++ b/src/bytes/initializer.h @@ -0,0 +1,103 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef BYTES_INITIALIZER_H +#define BYTES_INITIALIZER_H + +#include +#include "view.h" // range.h -> ranges -> concepts + +/// Namespace for bytes-related functionalities. +namespace bytes { + /// The concept of an initializer. It should be able to initialize any sized span of bytes. + template concept Initializer = requires (const T& a) { a.to(std::declval()); }; + + /// The concept of an initializer with a specific size target. It should be able to only initialize a fixed-size span of bytes. + template concept SizedInitializer = Initializer && requires (const T& a) { + a.to(std::declval()); + { a.size() } -> std::convertible_to; + }; + + /// The initializer class. + template F> class BasicInitializer { + private: + F func_; ///< The internal function to use for initializing. + + public: + /** + * Constructor. + * @param func The internal function to use for initializing. + */ + constexpr explicit BasicInitializer(F func) : func_(std::move(func)) {} + + /** + * Invoke the internal function with a given span of bytes. + * @param span The span of bytes to use for invoking. + */ + constexpr void to(Span span) const { std::invoke(func_, span); } + }; + + /// The initializer class with a specific size target. + template F> class BasicSizedInitializer { + private: + F func_; ///< The internal function to use for initializing. + std::size_t size_; ///< The fixed size of the span. + + public: + /** + * Constructor. + * @param func The internal function to use for initializing. + * @param size The fixed size of the span. + */ + constexpr BasicSizedInitializer(F func, std::size_t size) : func_(std::move(func)), size_(size) {} + + /// Get the size of the span. + constexpr size_t size() const { return size_; } + + /** + * Invoke the internal function with a given span of bytes. + * @param dest A pointer to a given byte that will be used for invoking. + */ + constexpr void to(Byte* dest) const { std::invoke(func_, dest); } + + /** + * Overload of to() that uses a byte span instead of a single byte. + * @param dest The span of bytes to use for invoking. + * @throw std::invalid_argument if sizes do not match. + */ + constexpr void to(Span dest) const { + if (dest.size() != size()) [[unlikely]] { + throw std::invalid_argument(std::string("span size (") + std::to_string(dest.size()) + + ") incompatible with initializer size (" + std::to_string(size()) + ")"); + } + to(dest.data()); + } + }; + + /** + * Helper templated function to initialize a bytes span. + * @tparam T The internal type of the span. + * @param func The initializer function. + * @return The respective basic initializer object. + */ + template constexpr Initializer auto makeInitializer(T func) { + return BasicInitializer(std::move(func)); + } + + /** + * Helper templated function to initialize a fixed-size bytes span. + * @tparam T The internal type of the span. + * @param size The fixed size of the bytes span. + * @param func The initializer function. + * @return The respective basic initializer object. + */ + template constexpr SizedInitializer auto makeInitializer(std::size_t size, T func) { + return BasicSizedInitializer(std::move(func), size); + } +} // namespace bytes + +#endif // BYTES_INITIALIZER_H diff --git a/src/bytes/join.h b/src/bytes/join.h new file mode 100644 index 00000000..185ea6c1 --- /dev/null +++ b/src/bytes/join.h @@ -0,0 +1,63 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef BYTES_JOIN_H +#define BYTES_JOIN_H + +#include "initializer.h" // view.h -> range.h + +namespace bytes { + +namespace detail { + +std::size_t joinedSize(const SizedInitializer auto& arg) { + return arg.size(); +} + +std::size_t joinedSize(const DataRange auto& arg) { + return std::ranges::size(arg); +} + +std::size_t joinedSize(const auto& arg, const auto&... args) { + return joinedSize(arg) + joinedSize(args...); +} + +Byte* joinImpl(Byte *dest, const SizedInitializer auto& init) { + init.to(dest); + return dest + init.size(); +} + +Byte* joinImpl(Byte *dest, const DataRange auto& range) { + std::memcpy(dest, std::ranges::data(range), std::ranges::size(range)); + return dest + std::ranges::size(range); +} + +Byte* joinImpl(Byte *dest, const auto& arg, const auto&... args) { + return joinImpl(joinImpl(dest, arg), args...); +} + +} // namespace detail + + /** + * Join several raw byte strings into one. + * @tparam Ts The raw byte strings' type. + * @param args One or more raw byte strings. + * @return A fixed-size initializer with the result of the concatenation. + */ + template SizedInitializer auto join(Ts&&... args) { + const size_t size = detail::joinedSize(args...); + + auto func = [args_ = std::tuple(std::forward(args)...)] (Byte *dest) { + std::apply(detail::joinImpl, std::tuple_cat(std::make_tuple(dest), std::tuple(args_))); + }; + + return makeInitializer(size, std::move(func)); +} + +} // namespace bytes + +#endif // BYTES_JOIN_H diff --git a/src/bytes/random.h b/src/bytes/random.h new file mode 100644 index 00000000..25f64fd0 --- /dev/null +++ b/src/bytes/random.h @@ -0,0 +1,40 @@ +#ifndef BDK_BYTES_RANDOM_H +#define BDK_BYTES_RANDOM_H + +#include "range.h" +#include "initializer.h" + +#include + +namespace bytes { + +/** + * Creates an initializer of random bytes. + * The number of generated bytes exactly matches the size of the target bytes range. + * + * Examples: + * Hash hash = bytes::random(); // generates 32 random bytes + * Address addr = bytes::random(); // generates 20 random bytes + * + * @return a random bytes initializer + */ +constexpr Initializer auto random() { + return makeInitializer([] (Span span) { + ::RAND_bytes(span.data(), span.size()); + }); +} + +/** + * Creates an random bytes initializer of the given size. + * + * @return a sized initializer of random bytes + */ +constexpr SizedInitializer auto random(size_t size) { + return makeInitializer(size, [size] (Byte* ptr) { + ::RAND_bytes(ptr, size); + }); +} + +} // namespace bytes + +#endif // BDK_BYTES_RANDOM_H diff --git a/src/bytes/range.h b/src/bytes/range.h new file mode 100644 index 00000000..4d4bfa8c --- /dev/null +++ b/src/bytes/range.h @@ -0,0 +1,49 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef BYTES_RANGE_H +#define BYTES_RANGE_H + +#include +#include "utils/bytes.h" + +namespace bytes { + /** + * Concept of a bytes iterator. + */ + template + concept Iterator = std::input_or_output_iterator && std::same_as, Byte>; + + /** + * Concept of a bytes contiguous bytes iterator. + */ + template + concept DataIterator = Iterator && std::contiguous_iterator; + + /** + * The concept of a range of bytes. + */ + template + concept Range = std::ranges::range && std::same_as, Byte>; + + /** + * The concept of a sized and contiguous range of bytes. This one is more interesting because + * the copying of data can be defined as std::memcpy or std::memmove calls, which are very fast! + */ + template + concept DataRange = Range && std::ranges::contiguous_range && std::ranges::sized_range; + + /** + * A data range that is also borrowed, which means that such range can be taken by value and + * pointers and iterators of it can be stored/returned without the danger of dangling. An example + * of a BorrowedDataRange is the std::span, because it does not owns the data. + */ + template + concept BorrowedDataRange = DataRange && std::ranges::borrowed_range; +} // namespace bytes + +#endif // BYTES_RANGE_H diff --git a/src/bytes/view.h b/src/bytes/view.h new file mode 100644 index 00000000..f2b7f373 --- /dev/null +++ b/src/bytes/view.h @@ -0,0 +1,57 @@ +#ifndef BYTES_VIEW_H +#define BYTES_VIEW_H + +#include "range.h" // ranges -> span +#include "utils/view.h" + +namespace bytes { + +using Span = std::span; + +/** + * Creates a view from the given data range. It needs to be Borrowed + * because otherwise we would be allowing the creating of a dangling + * view of bytes. + * + * @param r the target data range to be viewed + * @return a view object of the bytes +*/ +template +constexpr View view(R&& r) { return View(std::forward(r)); } + +/** + * Creates a span from the given data range. It needs to be Borrowed + * because otherwise we would be allowing the creating of a dangling + * span of bytes. + * + * @param r the target data range to construct the span + * @return a span object of the bytes +*/ +template +constexpr Span span(R&& r) { return Span(std::forward(r)); } + +/** + * Overload for creating a bytes view from a char string. It's useful + * because a std::string_view is a contiguous and sized range, but the + * element type is a char, not a Byte (a.k.a unsigned char). + * + * @param str the target string + * @return a bytes view of the string bytes +*/ +inline View view(std::string_view str) { + return View(reinterpret_cast(str.data()), str.size()); +} + +/** + * Overload for creating a bytes span from a given string reference. + * + * @param str the target string + * @return a bytes span of the string bytes +*/ +inline Span span(std::string& str) { + return Span(reinterpret_cast(str.data()), str.size()); +} + +} // namespace bytes + +#endif // BYTES_VIEW_H diff --git a/src/contract/CMakeLists.txt b/src/contract/CMakeLists.txt index 998403bb..3fdcd33d 100644 --- a/src/contract/CMakeLists.txt +++ b/src/contract/CMakeLists.txt @@ -1,35 +1,53 @@ set(CONTRACT_HEADERS ${CMAKE_SOURCE_DIR}/src/contract/abi.h ${CMAKE_SOURCE_DIR}/src/contract/contract.h - ${CMAKE_SOURCE_DIR}/src/contract/contractcalllogger.h + ${CMAKE_SOURCE_DIR}/src/contract/contractstack.h + ${CMAKE_SOURCE_DIR}/src/contract/contracthost.h ${CMAKE_SOURCE_DIR}/src/contract/contractfactory.h ${CMAKE_SOURCE_DIR}/src/contract/contractmanager.h ${CMAKE_SOURCE_DIR}/src/contract/customcontracts.h ${CMAKE_SOURCE_DIR}/src/contract/dynamiccontract.h + ${CMAKE_SOURCE_DIR}/src/contract/calltracer.h ${CMAKE_SOURCE_DIR}/src/contract/event.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/ownable.h ${CMAKE_SOURCE_DIR}/src/contract/variables/reentrancyguard.h ${CMAKE_SOURCE_DIR}/src/contract/variables/safeaddress.h ${CMAKE_SOURCE_DIR}/src/contract/variables/safearray.h ${CMAKE_SOURCE_DIR}/src/contract/variables/safebase.h ${CMAKE_SOURCE_DIR}/src/contract/variables/safebool.h + ${CMAKE_SOURCE_DIR}/src/contract/variables/safebytes.h ${CMAKE_SOURCE_DIR}/src/contract/variables/safestring.h ${CMAKE_SOURCE_DIR}/src/contract/variables/safeuint.h ${CMAKE_SOURCE_DIR}/src/contract/variables/safeunorderedmap.h ${CMAKE_SOURCE_DIR}/src/contract/variables/safevector.h ${CMAKE_SOURCE_DIR}/src/contract/variables/safetuple.h - ${CMAKE_SOURCE_DIR}/src/contract/templates/erc20.h - ${CMAKE_SOURCE_DIR}/src/contract/templates/erc721.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/standards/erc20.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/standards/erc721.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/standards/erc721uristorage.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/erc721test.h ${CMAKE_SOURCE_DIR}/src/contract/templates/erc20wrapper.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/mintableerc20.h ${CMAKE_SOURCE_DIR}/src/contract/templates/nativewrapper.h ${CMAKE_SOURCE_DIR}/src/contract/templates/simplecontract.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/snailtracer.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/snailtraceroptimized.h ${CMAKE_SOURCE_DIR}/src/contract/templates/dexv2/dexv2factory.h ${CMAKE_SOURCE_DIR}/src/contract/templates/dexv2/dexv2library.h ${CMAKE_SOURCE_DIR}/src/contract/templates/dexv2/dexv2pair.h ${CMAKE_SOURCE_DIR}/src/contract/templates/dexv2/dexv2router02.h ${CMAKE_SOURCE_DIR}/src/contract/templates/dexv2/uq112x112.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/orderbook/orderbook.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/pebble.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/buildthevoid.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/btvplayer.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/btvcommon.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/btvenergy.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/btvproposals.h ${CMAKE_SOURCE_DIR}/src/contract/templates/throwtestA.h ${CMAKE_SOURCE_DIR}/src/contract/templates/throwtestB.h ${CMAKE_SOURCE_DIR}/src/contract/templates/throwtestC.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/testThrowVars.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/randomnesstest.h PARENT_SCOPE ) @@ -37,22 +55,49 @@ set(CONTRACT_SOURCES ${CMAKE_SOURCE_DIR}/src/contract/abi.cpp ${CMAKE_SOURCE_DIR}/src/contract/contract.cpp ${CMAKE_SOURCE_DIR}/src/contract/contractfactory.cpp + ${CMAKE_SOURCE_DIR}/src/contract/contractstack.cpp + ${CMAKE_SOURCE_DIR}/src/contract/contracthost.cpp ${CMAKE_SOURCE_DIR}/src/contract/contractmanager.cpp - ${CMAKE_SOURCE_DIR}/src/contract/contractcalllogger.cpp ${CMAKE_SOURCE_DIR}/src/contract/dynamiccontract.cpp + ${CMAKE_SOURCE_DIR}/src/contract/trace/call.cpp ${CMAKE_SOURCE_DIR}/src/contract/event.cpp - ${CMAKE_SOURCE_DIR}/src/contract/templates/erc20.cpp - ${CMAKE_SOURCE_DIR}/src/contract/templates/erc721.cpp + ${CMAKE_SOURCE_DIR}/src/contract/blockobservers.cpp + ${CMAKE_SOURCE_DIR}/src/contract/common.cpp + ${CMAKE_SOURCE_DIR}/src/contract/executioncontext.cpp + ${CMAKE_SOURCE_DIR}/src/contract/evmcontractexecutor.cpp + ${CMAKE_SOURCE_DIR}/src/contract/precompiles/ecrecover.cpp + ${CMAKE_SOURCE_DIR}/src/contract/precompiles/sha256.cpp + ${CMAKE_SOURCE_DIR}/src/contract/precompiles/ripemd160.cpp + ${CMAKE_SOURCE_DIR}/src/contract/precompiles/blake2f.cpp + ${CMAKE_SOURCE_DIR}/src/contract/precompiles/modexp.cpp + ${CMAKE_SOURCE_DIR}/src/contract/precompiledcontractexecutor.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/ownable.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/standards/erc20.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/standards/erc721.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/standards/erc721uristorage.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/mintableerc20.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/erc721test.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/erc20wrapper.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/nativewrapper.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/simplecontract.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/snailtracer.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/snailtraceroptimized.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/dexv2/dexv2factory.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/dexv2/dexv2library.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/dexv2/dexv2pair.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/dexv2/dexv2router02.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/orderbook/orderbook.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/pebble.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/buildthevoid.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/btvplayer.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/btvcommon.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/btvenergy.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/btvproposals.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/throwtestA.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/throwtestB.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/throwtestC.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/testThrowVars.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/randomnesstest.cpp PARENT_SCOPE ) diff --git a/src/contract/abi.cpp b/src/contract/abi.cpp index 9d48a049..319c1dd8 100644 --- a/src/contract/abi.cpp +++ b/src/contract/abi.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -31,17 +31,38 @@ Bytes ABI::Encoder::encodeInt(const int256_t& num) { return ret; } -uint256_t ABI::Decoder::decodeUint(const BytesArrView &bytes, uint64_t &index) { +uint256_t ABI::Decoder::decodeUint(const View &bytes, uint64_t &index) { if (index + 32 > bytes.size()) throw std::length_error("Data too short for uint256"); - uint256_t result = Utils::bytesToUint256(bytes.subspan(index, 32)); + uint256_t result = UintConv::bytesToUint256(bytes.subspan(index, 32)); index += 32; return result; } -int256_t ABI::Decoder::decodeInt(const BytesArrView& bytes, uint64_t& index) { +int256_t ABI::Decoder::decodeInt(const View& bytes, uint64_t& index) { if (index + 32 > bytes.size()) throw std::length_error("Data too short for int256"); - int256_t result = Utils::bytesToInt256(bytes.subspan(index, 32)); + int256_t result = IntConv::bytesToInt256(bytes.subspan(index, 32)); index += 32; return result; } +Bytes ABI::Encoder::encodeError(std::string_view reason) { + return Utils::makeBytes(bytes::join( + Hex::toBytes("0x08c379a0"), // Function selector for "Error(string)" + ABI::Encoder::encodeData(reason) + )); +} + +std::string ABI::Decoder::decodeError(View data) { + Utils::safePrint("decodeError with: " + Hex::fromBytes(data).get()); + // Make sure that the data is long enough to contain the error signature and the string length + if (data.size() < 4) { + return ""; + } + // Make sure that the function selector matches the "Error(string)" signature + Functor functor {.value = UintConv::bytesToUint32(data.subspan(0, 4)) }; + if (functor.value != 147028384) { + Utils::safePrint("decodeError functor: " + functor.hex().get() + " expected: " + Functor(147028384).hex().get()); + return ""; + } + return std::get<0>(ABI::Decoder::decodeData(data.subspan(4), 0)); +} diff --git a/src/contract/abi.h b/src/contract/abi.h index 03806ee2..935b22e2 100644 --- a/src/contract/abi.h +++ b/src/contract/abi.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,12 +8,10 @@ See the LICENSE.txt file in the project root for more information. #ifndef ABI_H #define ABI_H -#include -#include - -#include "../utils/hex.h" -#include "../libs/json.hpp" -#include "../utils/utils.h" +#include "../utils/intconv.h" +#include "../utils/uintconv.h" +#include "../utils/strconv.h" +#include "../utils/utils.h" // FunctionTypes, libs/json.hpp -> string /// Namespace for Solidity ABI-related operations. namespace ABI { @@ -31,9 +29,11 @@ namespace ABI { std::string type; ///< Type of the method. }; - /// Struct that contains the data for a contract event. - /// args are encoded with ABI::FunctorEncoder::listArgumentTypesV. - /// Follow same rules as MethodDescription, but has a extra bool for indexed args. + /** + * Struct that contains the data for a contract event. + * Args are encoded with ABI::FunctorEncoder::listArgumentTypesV. + * Follows the same rules as MethodDescription, but has an extra bool for indexed args. + */ struct EventDescription { std::string name; ///< Name of the event. std::vector> args; ///< List of tuples of event arg types, names and indexed flag. @@ -49,6 +49,9 @@ namespace ABI { /// Forward declaration for std::vector. template struct isTupleOfDynamicTypes>; + /// Forward delcaration for std::array + template struct isTupleOfDynamicTypes>; + /// Type trait to check if T is a std::vector (defaults to false for types without args). template struct isVector : std::false_type {}; @@ -58,6 +61,15 @@ namespace ABI { /// Helper variable template for is_vector. template inline constexpr bool isVectorV = isVector::value; + /// Type trait to check if T is a std::array (defaults to false for types without args). + template struct isArray : std::false_type {}; + + /// Type trait to check if T is a std::array (defaults to true for types with args). + template struct isArray> : std::true_type {}; + + /// Helper variable template for is_array. + template inline constexpr bool isArrayV = isArray::value; + /// vectorElementType trait to get the element type of a vector. template struct vectorElementType {}; @@ -69,6 +81,17 @@ namespace ABI { /// Helper alias template for vector_element_type. template using vectorElementTypeT = typename vectorElementType::type; + /// arrayElementType trait to get the element type of a arrayu. + template struct arrayElementType {}; + + /// Getter for the element type of a array. + template struct arrayElementType> { + using type = typename std::array::value_type; ///< The element type of the aray. + }; + + /// Helper alias template for array_element_type. + template using arrayElementTypeT = typename arrayElementType::type; + /// Helper to check if a type is a std::tuple (defaults to false for types without args). template struct isTuple : std::false_type {}; @@ -88,11 +111,11 @@ namespace ABI { */ template constexpr bool isDynamic() { if constexpr ( - std::is_same_v || - std::is_same_v || - std::is_same_v || false + std::is_same_v || std::is_same_v> || + std::is_same_v || std::is_same_v || false ) return true; if constexpr (isVectorV) return true; + if constexpr(isArrayV) return true; if constexpr (isTupleOfDynamicTypes::value) return true; return false; } @@ -123,6 +146,14 @@ namespace ABI { static constexpr bool value = isTupleOfDynamicTypes::value; ///< For every type in T, check if it is dynamic. If it is, return true. }; + /** + * Check if a std::array contain a tuple of dynamic types. + * @tparam T Any type. + */ + template struct isTupleOfDynamicTypes> { + static constexpr bool value = isTupleOfDynamicTypes::value; ///< For every type in T, check if it is dynamic. If it is, return true. + }; + /** * Calculate the total next offset of a given tuple type. * @tparam T The tuple type to calculate from. @@ -247,9 +278,13 @@ namespace ABI { template<> struct TypeName { static std::string get() { return "bool"; }}; template<> struct TypeName { static std::string get() { return "bytes"; }}; template<> struct TypeName { static std::string get() { return "string"; }}; + template<> struct TypeName { static std::string get() { return "bytes32"; }}; + + template struct TypeName> { static std::string get() { return "bytes" + std::to_string(N); } }; + /// Enum types are encoded as uint8_t template - struct TypeName>> { + requires std::is_enum_v struct TypeName { static std::string get() { return TypeName::get(); } @@ -295,6 +330,15 @@ namespace ABI { static std::string get() { return TypeName::get() + "[]"; } }; + /** + * TypeName specialization for std::array. + * @tparam T The vector type. + */ + template struct TypeName> { + /// Get the type name. + static std::string get() { return TypeName::get() + "[]"; } + }; + /** * List the argument types in a string, comma separated. Uses () for tuples and [] for arrays. * Example: listArgumentTypes, std::vector>() will result in "int,string,(int,int),string[]". @@ -365,8 +409,11 @@ namespace ABI { * @param funcSig The function signature (name). */ template static Functor encode(const std::string& funcSig) { + Functor ret; std::string fullSig = funcSig + "(" + listArgumentTypes() + ")"; - return Utils::sha3(Utils::create_view_span(fullSig)).view_const(0, 4); + auto hash = Utils::sha3(Utils::create_view_span(fullSig)); + ret.value = UintConv::bytesToUint32(hash.view(0,4)); + return ret; } }; // namespace FunctorEncoder @@ -391,101 +438,133 @@ namespace ABI { template struct TypeEncoder { static Bytes encode(const T&) { - static_assert(always_false, "TypeName specialization for this type is not defined"); + static_assert(std::is_same_v, "TypeName specialization for this type is not defined"); return Bytes(); } }; - /// Specialization for default solidity types - template <> struct TypeEncoder
{ static Bytes encode(const Address& add) { return Utils::padLeftBytes(add.get(), 32); }}; - template <> struct TypeEncoder { static Bytes encode(const bool& b) { return Utils::padLeftBytes((b ? Bytes{0x01} : Bytes{0x00}), 32); }}; + // Specialization for default solidity types + template <> struct TypeEncoder
{ static Bytes encode(const Address& add) { return StrConv::padLeftBytes(add, 32); }}; + template <> struct TypeEncoder { static Bytes encode(const bool& b) { return StrConv::padLeftBytes((b ? Bytes{0x01} : Bytes{0x00}), 32); }}; + template <> struct TypeEncoder { static Bytes encode(const Hash& hash) { return hash.asBytes(); }; }; template <> struct TypeEncoder { static Bytes encode(const Bytes& bytes) { int pad = 0; do { pad += 32; } while (pad < bytes.size()); - Bytes len = Utils::padLeftBytes(Utils::uintToBytes(bytes.size()), 32); - Bytes data = Utils::padRightBytes(bytes, pad); + Bytes len = StrConv::padLeftBytes(Utils::uintToBytes(bytes.size()), 32); + Bytes data = StrConv::padRightBytes(bytes, pad); len.reserve(len.size() + data.size()); len.insert(len.end(), std::make_move_iterator(data.begin()), std::make_move_iterator(data.end())); return len; } }; + + template struct TypeEncoder> { + static_assert(N <= 32); + + static Bytes encode(const FixedBytes& bytes) { + Bytes result(32); + std::ranges::copy(bytes, result.begin()); + return result; + } + }; + template <> struct TypeEncoder { static Bytes encode(const std::string& str) { - BytesArrView bytes = Utils::create_view_span(str); + View bytes = Utils::create_view_span(str); + int pad = 0; + do { pad += 32; } while (pad < bytes.size()); + Bytes len = StrConv::padLeftBytes(Utils::uintToBytes(bytes.size()), 32); + Bytes data = StrConv::padRightBytes(bytes, pad); + len.reserve(len.size() + data.size()); + len.insert(len.end(), std::make_move_iterator(data.begin()), std::make_move_iterator(data.end())); + return len; + } + }; + + template <> struct TypeEncoder { + static Bytes encode(std::string_view str) { + View bytes = Utils::create_view_span(str); int pad = 0; do { pad += 32; } while (pad < bytes.size()); - Bytes len = Utils::padLeftBytes(Utils::uintToBytes(bytes.size()), 32); - Bytes data = Utils::padRightBytes(bytes, pad); + Bytes len = StrConv::padLeftBytes(Utils::uintToBytes(bytes.size()), 32); + Bytes data = StrConv::padRightBytes(bytes, pad); len.reserve(len.size() + data.size()); len.insert(len.end(), std::make_move_iterator(data.begin()), std::make_move_iterator(data.end())); return len; } }; - /// Specializations for int types (int8_t, int16_t, int24_t, ..., int256_t) - /// Takes advantage of std::enable_if_t to check if the type is a or int. + // Specializations for int types (int8_t, int16_t, int24_t, ..., int256_t) + // Takes advantage of requires to check if the type is a or int. template - struct TypeEncoder || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v>> { + requires std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v + struct TypeEncoder { static Bytes encode(const T& i) { return encodeInt(i); } }; - /// Specialization for uint types (uint8_t, uint16_t, uint24_t, ..., uint256_t) - /// Takes advantage of std::enable_if_t to check if the type is a or uint. + // Specialization for uint types (uint8_t, uint16_t, uint24_t, ..., uint256_t) + // Takes advantage of requires to check if the type is a or uint. template - struct TypeEncoder || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v>> { + requires std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v + struct TypeEncoder { static Bytes encode(const T& i) { return encodeUint(i); } }; - /// Specialization for enum types + // Specialization for enum types template - struct TypeEncoder>> { + requires std::is_enum_v + struct TypeEncoder { static Bytes encode(const T& i) { return encodeUint(static_cast(i)); } }; - /// Forward declaration of TypeEncode> so TypeEncoder> can see it. + // Forward declaration of TypeEncode> so TypeEncoder> can see it. template struct TypeEncoder> { static Bytes encode(const std::vector& v); }; - /// Specialization for std::tuple + // Forward declaration of TypeEncode> so TypeEncoder> can see it. + template struct TypeEncoder> { + static Bytes encode(const std::array& v); + }; + + // Specialization for std::tuple template struct TypeEncoder> { static Bytes encode(const std::tuple& t) { Bytes result; Bytes dynamicBytes; uint64_t nextOffset = calculateTotalOffset(); - std::apply([&](const auto&... args) { - auto encodeItem = [&](auto&& item) { - using ItemType = std::decay_t; + std::apply([&result, &dynamicBytes, &nextOffset](const auto&... args) { + auto encodeItem = [&](const ItemType& item) { if (isDynamic()) { Bytes packed = TypeEncoder::encode(item); - append(result, Utils::padLeftBytes(Utils::uintToBytes(nextOffset), 32)); + append(result, StrConv::padLeftBytes(Utils::uintToBytes(nextOffset), 32)); nextOffset += packed.size(); dynamicBytes.insert(dynamicBytes.end(), packed.begin(), packed.end()); } else { @@ -499,7 +578,16 @@ namespace ABI { } }; - /// Specialization for std::vector + // Specialization for std::pair + template + struct TypeEncoder> { + static Bytes encode(const std::pair& p) { + using Tuple = std::tuple; + return TypeEncoder::encode(Tuple(p.first, p.second)); + } + }; + + // Specialization for std::vector template Bytes TypeEncoder>::encode(const std::vector& v) { Bytes result; @@ -511,26 +599,58 @@ namespace ABI { // Encode each item within the vector for (const auto& t : v) { - append(dynamicOffSets, Utils::uint256ToBytes(nextOffset)); + append(dynamicOffSets, UintConv::uint256ToBytes(nextOffset)); Bytes dynamicBytes = TypeEncoder::encode(t); // We're calling the encode function specialized for the T type. nextOffset += dynamicBytes.size(); dynamicData.insert(dynamicData.end(), dynamicBytes.begin(), dynamicBytes.end()); } // Add the array length, dynamic offsets and dynamic data - append(result, Utils::padLeftBytes(Utils::uintToBytes(v.size()), 32)); + append(result, StrConv::padLeftBytes(Utils::uintToBytes(v.size()), 32)); append(result, dynamicOffSets); result.insert(result.end(), dynamicData.begin(), dynamicData.end()); return result; } else { // Add array length and append - append(result, Utils::padLeftBytes(Utils::uintToBytes(v.size()), 32)); + append(result, StrConv::padLeftBytes(Utils::uintToBytes(v.size()), 32)); + for (const auto& t : v) append(result, TypeEncoder::encode(t)); + return result; + } + }; + + // Specialization for std::array + template + Bytes TypeEncoder>::encode(const std::array& v) { + Bytes result; + uint64_t nextOffset = 32 * v.size(); // First 32 bytes are the length of the dynamic array + if constexpr (isDynamic()) { + // If the array is dynamic, we need to account the offsets of each tuple + Bytes dynamicData; + Bytes dynamicOffSets; + + // Encode each item within the array + for (const auto& t : v) { + append(dynamicOffSets, UintConv::uint256ToBytes(nextOffset)); + Bytes dynamicBytes = TypeEncoder::encode(t); // We're calling the encode function specialized for the T type. + nextOffset += dynamicBytes.size(); + dynamicData.insert(dynamicData.end(), dynamicBytes.begin(), dynamicBytes.end()); + } + + // Add the array length, dynamic offsets and dynamic data + append(result, StrConv::padLeftBytes(Utils::uintToBytes(v.size()), 32)); + append(result, dynamicOffSets); + result.insert(result.end(), dynamicData.begin(), dynamicData.end()); + return result; + } else { + // Add array length and append + append(result, StrConv::padLeftBytes(Utils::uintToBytes(v.size()), 32)); for (const auto& t : v) append(result, TypeEncoder::encode(t)); return result; } }; ///@endcond + /** * The main encode function. Use this one. * @tparam T Any supported ABI type (first one). @@ -543,11 +663,10 @@ namespace ABI { Bytes result; uint64_t nextOffset = calculateTotalOffset(); Bytes dynamicBytes; - auto encodeItem = [&](auto&& item) { - using ItemType = std::decay_t; + auto encodeItem = [&](const ItemType& item) { if constexpr (isDynamic()) { Bytes packed = TypeEncoder::encode(item); - append(result, Utils::padLeftBytes(Utils::uintToBytes(nextOffset), 32)); + append(result, StrConv::padLeftBytes(Utils::uintToBytes(nextOffset), 32)); nextOffset += packed.size(); dynamicBytes.insert(dynamicBytes.end(), packed.begin(), packed.end()); } else append(result, TypeEncoder::encode(item)); @@ -557,6 +676,14 @@ namespace ABI { result.insert(result.end(), dynamicBytes.begin(), dynamicBytes.end()); return result; } + + // Template overload for empty tuples or no arguments + template + requires (sizeof...(Ts) == 0) + Bytes encodeData(const std::tuple& tup) { + return {}; + } + Bytes encodeError(std::string_view reason); }; // namespace Encoder /** @@ -578,89 +705,93 @@ namespace ABI { template struct TypeEncoder { static Bytes encode(const T&) { - static_assert(always_false, "TypeName specialization for this type is not defined"); + static_assert(std::is_same_v, "TypeName specialization for this type is not defined"); return Bytes(); } }; - /// Specialization for default solidity types + // Specialization for default solidity types template <> struct TypeEncoder
{ static Bytes encode(const Address& add) { return Encoder::TypeEncoder
::encode(add); }}; template <> struct TypeEncoder { static Bytes encode(const bool& b) { return Encoder::TypeEncoder::encode(b); }}; template <> struct TypeEncoder { static Bytes encode(const Bytes& bytes) { - /// Almost the same as ABI::Encoder::encode, but without the padding. + // Almost the same as ABI::Encoder::encode, but without the padding. int pad = 0; do { pad += 32; } while (pad < bytes.size()); - return Utils::padRightBytes(bytes, pad); + return StrConv::padRightBytes(bytes, pad); } }; template <> struct TypeEncoder { static Bytes encode(const std::string& str) { - BytesArrView bytes = Utils::create_view_span(str); + View bytes = Utils::create_view_span(str); int pad = 0; do { pad += 32; } while (pad < bytes.size()); - return Utils::padRightBytes(bytes, pad); + return StrConv::padRightBytes(bytes, pad); } }; - /// Specializations for int types (int8_t, int16_t, int24_t, ..., int256_t) - /// Takes advantage of std::enable_if_t to check if the type is a or int. + // Specializations for int types (int8_t, int16_t, int24_t, ..., int256_t) + // Takes advantage of requires to check if the type is a or int. template - struct TypeEncoder || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v>> { + requires std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v + struct TypeEncoder { static Bytes encode(const T& i) { return ABI::Encoder::encodeInt(i); } }; - /// Specialization for uint types (uint8_t, uint16_t, uint24_t, ..., uint256_t) - /// Takes advantage of std::enable_if_t to check if the type is a or uint. + // Specialization for uint types (uint8_t, uint16_t, uint24_t, ..., uint256_t) + // Takes advantage of requires to check if the type is a or uint. template - struct TypeEncoder || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v>> { + requires std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v + struct TypeEncoder { static Bytes encode(const T& i) { return ABI::Encoder::encodeUint(i); } }; - /// Specialization for enum types - template struct TypeEncoder>> { - static Bytes encode(const T& i) { - return ABI::Encoder::encodeUint(static_cast(i)); - } + // Specialization for enum types + template requires std::is_enum_v struct TypeEncoder { + static Bytes encode(const T& i) { return ABI::Encoder::encodeUint(static_cast(i)); } }; - /// Forward declaration of TypeEncode> so TypeEncoder> can see it. + // Forward declaration of TypeEncode> so TypeEncoder> can see it. template struct TypeEncoder> { static Bytes encode(const std::vector& v); }; + // Forward declaration of TypeEncode> so TypeEncoder> can see it. + template struct TypeEncoder> { + static Bytes encode(const std::array& v); + }; + /// Specialization for std::tuple template struct TypeEncoder> { static Bytes encode(const std::tuple& t) { Bytes result; - std::apply([&](const auto&... args) { - auto encodeItem = [&](auto&& item) { - using ItemType = std::decay_t; + std::apply([&result](const auto&... args) { + auto encodeItem = [&](const ItemType& item) { append(result, TypeEncoder::encode(item)); }; (encodeItem(args), ...); @@ -669,15 +800,19 @@ namespace ABI { } }; - /// Specialization for std::vector - template - Bytes TypeEncoder>::encode(const std::vector& v) { + // Specialization for std::vector + template Bytes TypeEncoder>::encode(const std::vector& v) { Bytes result; - for (const T& item : v) { - append(result, TypeEncoder::encode(item)); - } + for (const T& item : v) append(result, TypeEncoder::encode(item)); + return result; + }; + // Specialization for std::array + template Bytes TypeEncoder>::encode(const std::array& v) { + Bytes result; + for (const T& item : v) append(result, TypeEncoder::encode(item)); return result; }; + ///@endcond /** @@ -694,10 +829,10 @@ namespace ABI { return Utils::sha3(Utils::create_view_span(item)); } Bytes result = TypeEncoder::encode(item); - if constexpr (isTuple::value || isVector::value) { + if constexpr (isTuple::value || isVector::value || isArray::value) { return Utils::sha3(result); // If it is a dynamic type, hash the encoded result. } - return result; + return Hash(result); } /// Similar to ABI::Encoder::Encode, but instead takes std::tuple as input. @@ -706,18 +841,17 @@ namespace ABI { Bytes result; uint64_t nextOffset = calculateTotalOffset(); Bytes dynamicBytes; - auto encodeItem = [&](auto&& item) { - using EventParamType = std::decay_t; + auto encodeItem = [&](const EventParamType& item) { if constexpr (EventParamType::isIndexed) return; using ItemType = std::decay_t; if constexpr (isDynamic()) { Bytes packed = ABI::Encoder::TypeEncoder::encode(item.value); - append(result, Utils::padLeftBytes(Utils::uintToBytes(nextOffset), 32)); + append(result, StrConv::padLeftBytes(Utils::uintToBytes(nextOffset), 32)); nextOffset += packed.size(); dynamicBytes.insert(dynamicBytes.end(), packed.begin(), packed.end()); } else append(result, ABI::Encoder::TypeEncoder::encode(item.value)); }; - std::apply([&](const auto&... args) { + std::apply([&encodeItem](const auto&... args) { (encodeItem(args), ...); }, params); @@ -736,7 +870,7 @@ namespace ABI { * @return The decoded data. * @throw std::length_error if data is too short for the type. */ - uint256_t decodeUint(const BytesArrView& bytes, uint64_t& index); + uint256_t decodeUint(const View& bytes, uint64_t& index); /** * Decode an int256. @@ -745,21 +879,20 @@ namespace ABI { * @return The decoded data. * @throw std::length_error if data is too short for the type. */ - int256_t decodeInt(const BytesArrView& bytes, uint64_t& index); + int256_t decodeInt(const View& bytes, uint64_t& index); /// @cond - /// General template for bytes to type decoding - template - struct TypeDecoder { - static T decode(const BytesArrView&, const uint64_t&) { + // General template for bytes to type decoding + template struct TypeDecoder { + static T decode(const View&, const uint64_t&) { static_assert(always_false, "TypeName specialization for this type is not defined"); return T(); } }; - /// Specialization for default solidity types + // Specialization for default solidity types template <> struct TypeDecoder
{ - static Address decode(const BytesArrView& bytes, uint64_t& index) { + static Address decode(const View& bytes, uint64_t& index) { if (index + 32 > bytes.size()) throw std::length_error("Data too short for address"); auto result = Address(bytes.subspan(index + 12, 20)); index += 32; @@ -767,8 +900,17 @@ namespace ABI { } }; + template <> struct TypeDecoder { + static Hash decode(const View& bytes, uint64_t& index) { + if (index + 32 > bytes.size()) { throw std::length_error("Data too short for hash"); } + auto result = Hash(bytes.subspan(index, 32)); + index += 32; + return result; + } + }; + template <> struct TypeDecoder { - static bool decode(const BytesArrView& bytes, uint64_t& index) { + static bool decode(const View& bytes, uint64_t& index) { if (index + 32 > bytes.size()) throw std::length_error("Data too short for bool"); bool result = (bytes[index + 31] == 0x01); index += 32; @@ -777,7 +919,7 @@ namespace ABI { }; template <> struct TypeDecoder { - static Bytes decode(const BytesArrView& bytes, uint64_t& index) { + static Bytes decode(const View& bytes, uint64_t& index) { if (index + 32 > bytes.size()) throw std::length_error("Data too short for bytes"); Bytes tmp(bytes.begin() + index, bytes.begin() + index + 32); uint64_t bytesStart = Utils::fromBigEndian(tmp); @@ -799,8 +941,21 @@ namespace ABI { } }; + template struct TypeDecoder> { + static_assert(N <= 32); + + static FixedBytes decode(const View& bytes, uint64_t& index) { + if (index + 32 > bytes.size()) + throw std::length_error("Data too short for bytes"); + + FixedBytes result; + std::ranges::copy(bytes.subspan(index, N), result.begin()); + return result; + } + }; + template <> struct TypeDecoder { - static std::string decode(const BytesArrView& bytes, uint64_t& index) { + static std::string decode(const View& bytes, uint64_t& index) { if (index + 32 > bytes.size()) throw std::length_error("Data too short for string 1"); std::string tmp(bytes.begin() + index, bytes.begin() + index + 32); uint64_t bytesStart = Utils::fromBigEndian(tmp); @@ -822,56 +977,61 @@ namespace ABI { } }; - /// Specializations for int types (int8_t, int16_t, int24_t, ..., int256_t) - /// Takes advantage of std::enable_if_t to check if the type is a or int. + // Specializations for int types (int8_t, int16_t, int24_t, ..., int256_t) + // Takes advantage of requires to check if the type is a or int. template - struct TypeDecoder || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v>> { - static T decode(const BytesArrView& bytes, uint64_t& index) { + requires std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v + struct TypeDecoder { + static T decode(const View& bytes, uint64_t& index) { return static_cast(decodeInt(bytes, index)); } }; - /// Specialization for uint types (uint8_t, uint16_t, uint24_t, ..., uint256_t) - /// Takes advantage of std::enable_if_t to check if the type is a or uint. + // Specialization for uint types (uint8_t, uint16_t, uint24_t, ..., uint256_t) + // Takes advantage of requires to check if the type is a or uint. template - struct TypeDecoder || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v>> { - static T decode(const BytesArrView& bytes, uint64_t& index) { + requires std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v + struct TypeDecoder { + static T decode(const View& bytes, uint64_t& index) { return static_cast(decodeUint(bytes, index)); } }; - /// Specialization for enum types - template - struct TypeDecoder>> { - static T decode(const BytesArrView& bytes, uint64_t& index) { + // Specialization for enum types + template requires std::is_enum_v struct TypeDecoder { + static T decode(const View& bytes, uint64_t& index) { return static_cast(decodeUint(bytes, index)); } }; - /// Forward declaration of TypeDecode> so TypeDecoder> can see it. - template - struct TypeDecoder>> { - static T decode(const BytesArrView& bytes, uint64_t& index); + // Forward declaration of TypeDecode> so TypeDecoder> can see it. + template requires isVectorV struct TypeDecoder { + static T decode(const View& bytes, uint64_t& index); + }; + + // Forward declaration of TypeDecode> so TypeDecoder> can see it. + template requires isArrayV struct TypeDecoder { + static T decode(const View& bytes, uint64_t& index); }; /** @@ -883,8 +1043,7 @@ namespace ABI { * @param index The point on the encoded string to start decoding. * @param ret The tuple object to "return". Needs to be a reference and created outside the function due to recursion. */ - template - void decodeTuple(const BytesArrView& bytes, uint64_t& index, TupleLike& ret) { + template void decodeTuple(const View& bytes, uint64_t& index, TupleLike& ret) { if constexpr (I < std::tuple_size_v) { using SelectedType = typename std::tuple_element::type; std::get(ret) = TypeDecoder::decode(bytes, index); @@ -892,10 +1051,9 @@ namespace ABI { } } - /// Specialization for std::tuple - template - struct TypeDecoder::value>> { - static T decode(const BytesArrView& bytes, uint64_t& index) { + // Specialization for std::tuple + template requires isTuple::value struct TypeDecoder { + static T decode(const View& bytes, uint64_t& index) { T ret; if constexpr (isTupleOfDynamicTypes::value) { if (index + 32 > bytes.size()) throw std::length_error("Data too short for tuple of dynamic types"); @@ -913,12 +1071,11 @@ namespace ABI { } }; - /// Specialization for std::vector - template - T TypeDecoder>>::decode(const BytesArrView& bytes, uint64_t& index) { + template requires isVectorV T TypeDecoder::decode(const View& bytes, uint64_t& index) { using ElementType = vectorElementTypeT; std::vector retVector; + // Get array offset if (index + 32 > bytes.size()) throw std::length_error("Data too short for vector"); Bytes tmp(bytes.begin() + index, bytes.begin() + index + 32); @@ -940,12 +1097,40 @@ namespace ABI { return retVector; }; + /// Specialization for std::array + template requires isArrayV T TypeDecoder::decode(const View& bytes, uint64_t& index) { + using ElementType = arrayElementTypeT; + // Get the size of the array + constexpr size_t argumentArrSize = std::tuple_size_v; + std::array retArray; + + // Get array offset + if (index + 32 > bytes.size()) throw std::length_error("Data too short for array"); + Bytes tmp(bytes.begin() + index, bytes.begin() + index + 32); + uint64_t arrayStart = Utils::fromBigEndian(tmp); + index += 32; + + // Get array length + tmp.clear(); + if (arrayStart + 32 > bytes.size()) throw std::length_error("Data too short for array"); + tmp.insert(tmp.end(), bytes.begin() + arrayStart, bytes.begin() + arrayStart + 32); + uint64_t arrayLength = Utils::fromBigEndian(tmp); + + if (arrayLength != argumentArrSize) throw std::length_error("Array length does not match the expected size"); + if (arrayStart + 32 > bytes.size()) throw std::length_error("Data too short for array"); + uint64_t newIndex = 0; + auto view = bytes.subspan(arrayStart + 32); + for (uint64_t i = 0; i < arrayLength; i++) { + retArray[i] = TypeDecoder::decode(view, newIndex); // Hic sunt recursis + } + return retArray; + }; ///@endcond /// Specialization of decodeTupleHelper() for when tuple index is the last one template - typename std::enable_if_t - decodeTupleHelper(const BytesArrView&, const uint64_t&, std::tuple&) { + requires (Index == sizeof...(Args)) + void decodeTupleHelper(const View&, const uint64_t&, std::tuple&) { // End of recursion, do nothing } @@ -958,8 +1143,8 @@ namespace ABI { * @param tuple The tuple to hold the decoded values. */ template - typename std::enable_if_t - decodeTupleHelper(const BytesArrView& encodedData, uint64_t& index, std::tuple& tuple) { + requires (Index < sizeof...(Args)) + void decodeTupleHelper(const View& encodedData, uint64_t& index, std::tuple& tuple) { // TODO: Technically, we could pass std::get(tuple) as a reference to decode<>(). // But, it is worth to reduce code readability for a few nanoseconds? Need to benchmark. std::get(tuple) = TypeDecoder>>::decode(encodedData, index); @@ -974,7 +1159,7 @@ namespace ABI { * @return A tuple with the decoded data, or an empty tuple if there's no arguments to decode. */ template - inline std::tuple decodeData(const BytesArrView& encodedData, uint64_t index = 0) { + inline std::tuple decodeData(const View& encodedData, uint64_t index = 0) { if constexpr (sizeof...(Args) == 0) { return std::tuple<>(); } else { @@ -984,15 +1169,21 @@ namespace ABI { } } + std::string decodeError(View data); + + /// Specialization for tuples without args. template struct decodeDataAsTuple { - static T decode(const BytesArrView&) { + /// Decode the tuple. + static T decode(const View&) { static_assert(always_false, "Can't use decodeDataAsTuple with a non-tuple type"); return T(); } }; + /// Specialization for tuples with args. template struct decodeDataAsTuple> { - static std::tuple decode(const BytesArrView& encodedData) { + /// Decode the tuple. + static std::tuple decode(const View& encodedData) { if constexpr (sizeof...(Args) == 0) { throw std::invalid_argument("Can't decode empty tuple"); } else { diff --git a/src/contract/anyencodedmessagehandler.h b/src/contract/anyencodedmessagehandler.h new file mode 100644 index 00000000..aa5f380f --- /dev/null +++ b/src/contract/anyencodedmessagehandler.h @@ -0,0 +1,43 @@ +#ifndef BDK_MESSAGES_ANYENCODEDMESSAGEHANDLER_H +#define BDK_MESSAGES_ANYENCODEDMESSAGEHANDLER_H + +#include "encodedmessages.h" + +class AnyEncodedMessageHandler { +public: + template + static AnyEncodedMessageHandler from(MessageHandler& handler) { + AnyEncodedMessageHandler anyHandler; + + const auto generic = [] (void *obj, auto& msg) { return static_cast(obj)->onMessage(msg); }; + + anyHandler.handler_ = &handler; + anyHandler.onCreate_ = static_cast(generic); + anyHandler.onSaltCreate_ = static_cast(generic); + anyHandler.onCall_ = static_cast(generic); + anyHandler.onStaticCall_ = static_cast(generic); + anyHandler.onDelegateCall_ = static_cast(generic); + + return anyHandler; + } + + Address onMessage(EncodedCreateMessage& msg) { return std::invoke(onCreate_, handler_, msg); } + + Address onMessage(EncodedSaltCreateMessage& msg) { return std::invoke(onSaltCreate_, handler_, msg); } + + Bytes onMessage(EncodedCallMessage& msg) { return std::invoke(onCall_, handler_, msg); } + + Bytes onMessage(EncodedStaticCallMessage& msg) { return std::invoke(onStaticCall_, handler_, msg); } + + Bytes onMessage(EncodedDelegateCallMessage& msg) { return std::invoke(onDelegateCall_, handler_, msg); } + +private: + void *handler_; + Address (*onCreate_)(void*, EncodedCreateMessage&); + Address (*onSaltCreate_)(void*, EncodedSaltCreateMessage&); + Bytes (*onCall_)(void*, EncodedCallMessage&); + Bytes (*onStaticCall_)(void*, EncodedStaticCallMessage&); + Bytes (*onDelegateCall_)(void*, EncodedDelegateCallMessage&); +}; + +#endif // BDK_MESSAGES_ANYENCODEDMESSAGEHANDLER_H diff --git a/src/contract/basemessage.h b/src/contract/basemessage.h new file mode 100644 index 00000000..cc305609 --- /dev/null +++ b/src/contract/basemessage.h @@ -0,0 +1,140 @@ +#ifndef BDK_MESSAGES_BASEMESSAGE_H +#define BDK_MESSAGES_BASEMESSAGE_H + +#include "bytes/range.h" +#include "utils/address.h" +#include "utils/hash.h" +#include "gas.h" + +struct BaseContract; + +template +struct BaseMessage : BaseMessage, BaseMessage { + template + constexpr BaseMessage(U&& first, Us&&... others) + : BaseMessage(std::forward(first)), + BaseMessage(std::forward(others)...) {} +}; + +template +struct BaseMessage : T { + template + explicit constexpr BaseMessage(U&& first) : T(std::forward(first)) {} +}; + +class FromField { +public: + template + requires std::convertible_to> + explicit constexpr FromField(R&& range) : from_(range) {} + + constexpr View
from() const { return from_; } + +private: + View
from_; +}; + +class ToField { +public: + template + requires std::convertible_to> + explicit constexpr ToField(R&& range) : to_(range) {} + + constexpr View
to() const { return to_; } + +private: + View
to_; +}; + +class GasField { +public: + explicit constexpr GasField(Gas& gas) : gas_(gas) {} + constexpr Gas& gas() { return gas_; } + constexpr const Gas& gas() const { return gas_; } + +private: + Gas& gas_; +}; + +class ValueField { +public: + explicit constexpr ValueField(const uint256_t& value) : value_(value) {} + constexpr const uint256_t& value() const { return value_; } + +private: + const uint256_t& value_; +}; + +class InputField { +public: + explicit constexpr InputField(View input) : input_(input) {} + constexpr View input() const { return input_; } + +private: + View input_; +}; + +class CodeField { +public: + explicit constexpr CodeField(View code) : code_(code) {} + constexpr View code() const { return code_; } + +private: + View code_; +}; + +class SaltField { +public: + template + requires std::convertible_to> + explicit constexpr SaltField(R&& range) : salt_(range) {} + constexpr View salt() const { return salt_; } + +private: + View salt_; +}; + +class CodeAddressField { +public: + template + requires std::convertible_to> + explicit constexpr CodeAddressField(R&& range) : codeAddress_(range) {} + constexpr View
codeAddress() const { return codeAddress_; } + +private: + View
codeAddress_; +}; + +template +class MethodField { +public: + explicit constexpr MethodField(M method) : method_(method) {} + constexpr M& method() { return method_; } + constexpr const M& method() const { return method_; } + +private: + M method_; +}; + +template +class ArgsField { +public: + explicit constexpr ArgsField(Args&&... args) : args_(std::forward(args)...) {} + constexpr std::tuple& args() & { return args_; } + constexpr std::tuple&& args() && { return std::move(args_); } + constexpr const std::tuple& args() const& { return args_; } + +private: + std::tuple args_; +}; + +template +ArgsField(Args&&...) -> ArgsField; + +template +struct BaseMessage> : ArgsField { + explicit constexpr BaseMessage(auto&&... args) + : ArgsField(std::forward(args)...) {} +}; + +#endif // BDK_MESSAGES_BASEMESSAGE_H diff --git a/src/contract/blockobservers.cpp b/src/contract/blockobservers.cpp new file mode 100644 index 00000000..8d9ca910 --- /dev/null +++ b/src/contract/blockobservers.cpp @@ -0,0 +1,127 @@ +#include "blockobservers.h" +#include "contracthost.h" +#include "bytes/random.h" + +BlockObservers::BlockObservers( + evmc_vm *vm, + DumpManager& manager, + Storage& storage, + Contracts& contracts, + Accounts& accounts, + VmStorage& vmStorage, + const Options& options) + : vm_(vm), + manager_(manager), + storage_(storage), + contracts_(contracts), + accounts_(accounts), + vmStorage_(vmStorage), + options_(options) {} + +void BlockObservers::add(BlockNumberObserver observer) { + blockNumberQueue_.push(std::move(observer)); +} + +void BlockObservers::add(BlockTimestampObserver observer) { + blockTimestampQueue_.push(std::move(observer)); +} + +void BlockObservers::notify(const FinalizedBlock& block) { + notifyNumberQueue(block); + notifyTimestampQueue(block); +} + +void BlockObservers::notifyNumberQueue(const FinalizedBlock& block) { + if (blockNumberQueue_.empty()) { + Utils::safePrint("EMPTY BLOCK NUMBER QUEUE"); + return; + } + uint64_t indexCount = 0; + Utils::safePrint("BlockObservers::notifyNumberQueue: blockNumberQueue_.top().blockNumber: " + std::to_string(blockNumberQueue_.top().blockNumber) + " block.getNHeight(): " + std::to_string(block.getNHeight())); + while (blockNumberQueue_.top().blockNumber <= block.getNHeight()) { + BlockNumberObserver observer = blockNumberQueue_.top(); + blockNumberQueue_.pop(); + + try { + const Hash randomSeed(UintConv::uint256ToBytes((static_cast(block.getBlockRandomness()) + indexCount))); + + ExecutionContext context = ExecutionContext::Builder{} + .storage(vmStorage_) + .accounts(accounts_) + .contracts(contracts_) + .blockHash(block.getHash()) + .txHash(Hash()) + .txOrigin(Address()) + .blockCoinbase(ContractGlobals::getCoinbase()) + .txIndex((block.getTxs().size() == 0 ) ? block.getTxs().size() : block.getTxs().size() + indexCount) + .blockNumber(ContractGlobals::getBlockHeight()) + .blockTimestamp(ContractGlobals::getBlockTimestamp()) + .blockGasLimit(10'000'000) + .txGasPrice(0) + .chainId(this->options_.getChainID()) + .build(); + + ContractHost host( + vm_, + manager_, + storage_, + randomSeed, + context, + this + ); + + std::invoke(observer.callback, host); + ++indexCount; + } catch (...) {} + + observer.blockNumber = block.getNHeight() + observer.step; + + blockNumberQueue_.push(observer); + } +} + +void BlockObservers::notifyTimestampQueue(const FinalizedBlock& block) { + if (blockTimestampQueue_.empty()) { + return; + } + + while (blockTimestampQueue_.top().timestamp <= block.getTimestamp()) { + BlockTimestampObserver observer = blockTimestampQueue_.top(); + + blockTimestampQueue_.pop(); + uint64_t indexCount; + try { + const Hash randomSeed(UintConv::uint256ToBytes((static_cast(block.getBlockRandomness()) + indexCount))); + ExecutionContext context = ExecutionContext::Builder{} + .storage(vmStorage_) + .accounts(accounts_) + .contracts(contracts_) + .blockHash(block.getHash()) + .txHash(Hash()) + .txOrigin(Address()) + .blockCoinbase(ContractGlobals::getCoinbase()) + .txIndex((block.getTxs().size() == 0 ) ? block.getTxs().size() : block.getTxs().size() + indexCount) + .blockNumber(ContractGlobals::getBlockHeight()) + .blockTimestamp(ContractGlobals::getBlockTimestamp()) + .blockGasLimit(10'000'000) + .txGasPrice(0) + .chainId(this->options_.getChainID()) + .build(); + + ContractHost host( + vm_, + manager_, + storage_, + randomSeed, + context, + this + ); + std::invoke(observer.callback, host); + ++indexCount; + } catch (...) {} + + observer.timestamp = observer.step + block.getTimestamp(); + + blockTimestampQueue_.push(observer); + } +} diff --git a/src/contract/blockobservers.h b/src/contract/blockobservers.h new file mode 100644 index 00000000..b89c1f00 --- /dev/null +++ b/src/contract/blockobservers.h @@ -0,0 +1,76 @@ +#ifndef BDK_CONTRACT_BLOCKOBSERVERS_H +#define BDK_CONTRACT_BLOCKOBSERVERS_H + +#include "utils/tx.h" +#include "utils/finalizedblock.h" +#include "contract/contract.h" +#include +#include + +class ContractHost; + +struct BlockNumberObserver { + std::function callback; + uint64_t blockNumber; + uint64_t step; +}; + +struct BlockTimestampObserver { + std::function callback; + uint64_t timestamp; + uint64_t step; +}; + +constexpr bool operator<(const BlockNumberObserver& lhs, const BlockNumberObserver& rhs) { + return lhs.blockNumber < rhs.blockNumber; +} + +constexpr bool operator>(const BlockNumberObserver& lhs, const BlockNumberObserver& rhs) { + return lhs.blockNumber > rhs.blockNumber; +} + +constexpr bool operator<(const BlockTimestampObserver& lhs, const BlockTimestampObserver& rhs) { + return lhs.timestamp < rhs.timestamp; +} + +constexpr bool operator>(const BlockTimestampObserver& lhs, const BlockTimestampObserver& rhs) { + return lhs.timestamp > rhs.timestamp; +} + +class BlockObservers { +public: + using Contracts = boost::unordered_flat_map, SafeHash, SafeCompare>; + using Accounts = boost::unordered_flat_map, SafeHash, SafeCompare>; + using VmStorage = boost::unordered_flat_map; + + BlockObservers(evmc_vm *vm, + DumpManager& manager, + Storage& storage, + Contracts& contracts, + Accounts& accounts, + VmStorage& vmStorage, + const Options& options); + + void add(BlockNumberObserver observer); + + void add(BlockTimestampObserver observer); + + void notify(const FinalizedBlock& block); + +private: + void notifyNumberQueue(const FinalizedBlock& block); + + void notifyTimestampQueue(const FinalizedBlock& block); + + std::priority_queue, std::greater> blockNumberQueue_; + std::priority_queue, std::greater> blockTimestampQueue_; + evmc_vm* vm_; + DumpManager& manager_; + Storage& storage_; + Contracts& contracts_; + Accounts& accounts_; + VmStorage& vmStorage_; + const Options& options_; +}; + +#endif // BDK_CONTRACT_BLOCKOBSERVERS_H diff --git a/src/contract/calltracer.h b/src/contract/calltracer.h new file mode 100644 index 00000000..00a3e6e9 --- /dev/null +++ b/src/contract/calltracer.h @@ -0,0 +1,110 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef CONTRACT_CALLTRACER_H +#define CONTRACT_CALLTRACER_H + +#include "utils/utils.h" +#include "utils/options.h" +#include "contract/concepts.h" +#include "contract/outofgas.h" +#include "contract/trace/call.h" +#include "contract/traits.h" +#include "contract/abi.h" + +template +class CallTracer { +public: + CallTracer(MessageHandler handler, IndexingMode indexingMode) + : handler_(std::move(handler)), rootCall_(), callStack_(), indexingMode_(indexingMode) {} + + template + decltype(auto) onMessage(Message&& msg) { + using Result = traits::MessageResult; + + if (indexingMode_ != IndexingMode::RPC_TRACE) { + return handler_.onMessage(std::forward(msg)); + } + + trace::Call& callTrace = callStack_.empty() + ? *(rootCall_ = std::make_unique()) + : callStack_.top()->calls.emplace_back(); + + Gas& gas = msg.gas(); + + callTrace.type = trace::getMessageCallType(msg); + callTrace.status = trace::CallStatus::SUCCEEDED; + callTrace.from = Address(msg.from()); + callTrace.to = Address(msg.to()); + callTrace.value = FixedBytes<32>(UintConv::uint256ToBytes(messageValueOrZero(msg))); + callTrace.gas = uint64_t(gas); + + try { + callTrace.input = messageInputEncoded(msg); + } catch (const std::exception& ignored) {} + + callStack_.push(&callTrace); + + try { + if constexpr (not std::same_as) { + Result result = handler_.onMessage(std::forward(msg)); + + if constexpr (concepts::PackedMessage) { + callTrace.output = ABI::Encoder::encodeData(result); + } else { + callTrace.output = result; + } + + callTrace.gasUsed = callTrace.gas - uint64_t(gas); + callStack_.pop(); + + return result; + } + + handler_.onMessage(std::forward(msg)); + } catch (const OutOfGas& outOfGas) { + callTrace.status = trace::CallStatus::OUT_OF_GAS; + callTrace.gasUsed = callTrace.gas - uint64_t(gas); + callStack_.pop(); + + throw; + } catch (const std::exception& error) { + callTrace.status = trace::CallStatus::EXECUTION_REVERTED; + + if (error.what()) { + try { + callTrace.output = ABI::Encoder::encodeError(error.what()); + } catch (const std::exception& ignored) {} + } + + callTrace.gasUsed = callTrace.gas - uint64_t(gas); + callStack_.pop(); + + throw; + } + } + + decltype(auto) onMessage(concepts::CreateMessage auto&& msg) { + return handler_.onMessage(std::forward(msg)); + } + + const MessageHandler& handler() const { return handler_; } + + MessageHandler& handler() { return handler_; } + + bool hasCallTrace() const { return rootCall_ != nullptr; } + + const trace::Call& getCallTrace() const { return *rootCall_; } + +private: + MessageHandler handler_; + std::unique_ptr rootCall_; + std::stack callStack_; + IndexingMode indexingMode_; +}; + +#endif // CONTRACT_CALLTRACER_H diff --git a/src/contract/common.cpp b/src/contract/common.cpp new file mode 100644 index 00000000..abcb457d --- /dev/null +++ b/src/contract/common.cpp @@ -0,0 +1,56 @@ +#include "common.h" + +Address generateContractAddress(uint64_t nonce, View
address) { +#ifdef BUILD_TESTNET + // TODO: Make forkable + uint8_t rlpSize = 0xc0; + rlpSize += 20; + rlpSize += (nonce < 0x80) ? 1 : 1 + Utils::bytesRequired(nonce); + Bytes rlp; + rlp.insert(rlp.end(), rlpSize); + rlp.insert(rlp.end(), address.cbegin(), address.cend()); + if (nonce < 0x80) { + rlp.insert(rlp.end(), static_cast(nonce)); + } else { + rlp.insert(rlp.end(), 0x80 + Utils::bytesRequired(nonce)); + Utils::appendBytes(rlp, Utils::uintToBytes(nonce)); + } + + return Address(Utils::sha3(rlp).view(12)); +#else + + Bytes payload; + payload.push_back(0x80 + 20); + payload.insert(payload.end(), address.cbegin(), address.cend()); + + // 1b) RLP‑encode the nonce + if (nonce == 0) { + // zero is encoded as empty string → 0x80 + payload.push_back(0x80); + } else if (nonce < 0x80) { + // single byte [0x00..0x7f] + payload.push_back(static_cast(nonce)); + } else { + // longer nonce: prefix + raw bytes + auto nb = Utils::uintToBytes(nonce); + payload.push_back(0x80 + nb.size()); + Utils::appendBytes(payload, nb); + } + + Bytes rlp; + rlp.push_back(0xc0 + static_cast(payload.size())); + rlp.insert(rlp.end(), payload.begin(), payload.end()); + + auto hash = Utils::sha3(rlp); + return Address(hash.view(12)); // skip first 12 bytes, keep last 20 +#endif +} + + +Address generateContractAddress(View
from, View salt, View code) { + const Hash codeHash = Utils::sha3(code); + Bytes buffer(from.size() + salt.size() + codeHash.size() + 1); // 85 + buffer[0] = 0xFF; + bytes::join(from, salt, codeHash).to(buffer | std::views::drop(1)); + return Address(Utils::sha3(buffer).view(12)); +} diff --git a/src/contract/common.h b/src/contract/common.h new file mode 100644 index 00000000..e15081de --- /dev/null +++ b/src/contract/common.h @@ -0,0 +1,68 @@ +#ifndef BDK_MESSAGES_COMMON_H +#define BDK_MESSAGES_COMMON_H + +#include "utils/utils.h" +#include "utils/contractreflectioninterface.h" +#include "concepts.h" + +Address generateContractAddress(uint64_t nonce, View
address); + +Address generateContractAddress(View
from, View salt, View code); + +constexpr uint256_t messageValueOrZero(const auto& msg) { + if constexpr (concepts::HasValueField) { + return msg.value(); + } else { + return uint256_t(0); + } +} + +constexpr View
messageCodeAddress(const auto& msg) { + if constexpr (concepts::DelegateCallMessage>) { + + return msg.codeAddress(); + } else { + return msg.to(); + } +} + +constexpr Address messageRecipientOrDefault(const auto& msg) { + if constexpr (concepts::CreateMessage) { + return Address{}; + } else { + return Address(msg.to()); + } +} + +constexpr Hash messageSaltOrDefault(const auto& msg) { + if constexpr (concepts::SaltMessage) { + return Hash(msg.salt()); + } else { + return Hash{}; + } +} + +Bytes messageInputEncoded(const concepts::EncodedMessage auto& msg) { + return Bytes(msg.input()); +} + +Bytes messageInputEncoded(const concepts::PackedMessage auto& msg) { + return std::apply([&] (const auto&... args) -> Bytes { + const std::string functionName = ContractReflectionInterface::getFunctionName(msg.method()); + + if (functionName.empty()) { + throw DynamicException("Contract fuction not found (contract not registered?)"); + } + + const BytesArr<4> encodedFunctor = UintConv::uint32ToBytes(ABI::FunctorEncoder::encode(std::string(functionName)).value); + + if constexpr (sizeof...(args) > 0) { + const Bytes encodedArgs = ABI::Encoder::encodeData(args...); + return Utils::makeBytes(bytes::join(encodedFunctor, encodedArgs)); + } + + return Utils::makeBytes(encodedFunctor); + }, msg.args()); +} + +#endif // BDK_MESSAGES_COMMON_H diff --git a/src/contract/concepts.h b/src/contract/concepts.h new file mode 100644 index 00000000..4cbdb2e9 --- /dev/null +++ b/src/contract/concepts.h @@ -0,0 +1,107 @@ +#ifndef BDK_MESSAGES_CONCEPTS_H +#define BDK_MESSAGES_CONCEPTS_H + +#include "utils/strings.h" +#include "gas.h" + +namespace concepts { + +/** + * Basic message concept. Every message has as sender address and a gas reference to be consumed. + */ +template +concept Message = requires (M m) { + { m.from() } -> std::convertible_to>; + { m.gas() } -> std::convertible_to; +}; + +/** + * A message that also has value. Often used to describe composed concepts. + */ +template +concept HasValueField = requires (M m) { + { m.value() } -> std::convertible_to; +}; + +template +concept HasInputField = requires (M m) { + { m.input() } -> std::convertible_to>; +}; + +template +concept HasCodeField = requires (M m) { + { m.code() } -> std::convertible_to>; +}; + +/** + * A message that also has recipient address. Often used to describe composed concepts. + */ +template +concept HasToField = requires (M m) { + { m.to() } -> std::convertible_to>; +}; + +/** + * A message aimed to create an address. In general, contract creation messages + * do not have a target address (i.e. recipient) but can have value. + */ +template +concept CreateMessage = Message && HasValueField && !HasToField; + +/** + * Call messages can have value and must have a target address. + */ +template +concept CallMessage = Message && HasToField; + +/** + * Static call messages are messages that calls const/view functions. They + * can't have value but (as any call message) must have a recipient address + */ +template +concept StaticCallMessage = CallMessage && !HasValueField; + +/** + * Solution based on the C++ standard: std::ranges::enable_borrowed_range. + * In short, delegate calls have the exact same structure of a normal call, + * but with different intention. Thus, any message that represents a + * delegate message must specialize the EnableDelegate as a true_type, + * allowing the DelegateCallMessage concept to be chosen during overload + * resolution. + */ +template +constexpr bool EnableDelegate = false; + +template +constexpr bool EnableCallCode = false; + +/** + * Concept of delegate call messages. + */ +template +concept DelegateCallMessage = CallMessage && requires (M m) { + { m.codeAddress() } -> std::convertible_to>; +}; + +/** + * Concept of delegate call messages. + */ +template +concept CallCodeMessage = CallMessage && EnableCallCode>; + +template +concept SaltMessage = CreateMessage && requires (M m) { + { m.salt() } -> std::convertible_to>; +}; + +template +concept EncodedMessage = (CallMessage && HasInputField) || (CreateMessage && HasCodeField); + +template +concept PackedMessage = Message && requires (M m) { + m.args(); +}; + +} // namespace concepts + +#endif // BDK_MESSAGES_CONCEPTS_H diff --git a/src/contract/contract.cpp b/src/contract/contract.cpp index 4426711d..344dd136 100644 --- a/src/contract/contract.cpp +++ b/src/contract/contract.cpp @@ -1,14 +1,43 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. */ #include "contract.h" +#include "contracthost.h" // leave it in to avoid "invalid use of incomplete type" errors + +#include "../utils/dynamicexception.h" Address ContractGlobals::coinbase_ = Address(Hex::toBytes("0x0000000000000000000000000000000000000000")); Hash ContractGlobals::blockHash_ = Hash(); uint64_t ContractGlobals::blockHeight_ = 0; uint64_t ContractGlobals::blockTimestamp_ = 0; +Address BaseContract::getOrigin() const { + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to get origin without a host!"); + } + return Address(this->host_->context().getTxOrigin()); +} + +uint64_t BaseContract::getNonce(const Address& address) const { + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to get nonce without a host!"); + } + return this->host_->context().getAccount(address).getNonce(); +} + +void BaseContract::ethCall(const evmc_message& data, ContractHost* host) { + throw DynamicException("Derived Class from Contract does not override ethCall()"); +} + +Bytes BaseContract::evmEthCall(const evmc_message& data, ContractHost* host) { + throw DynamicException("Derived Class from Contract does not override ethCall()"); +} + +Bytes BaseContract::ethCallView(const evmc_message &data, ContractHost* host) const { + throw DynamicException("Derived Class from Contract does not override ethCallView()"); +} + diff --git a/src/contract/contract.h b/src/contract/contract.h index c1a19c86..385edc17 100644 --- a/src/contract/contract.h +++ b/src/contract/contract.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,107 +8,97 @@ See the LICENSE.txt file in the project root for more information. #ifndef CONTRACT_H #define CONTRACT_H -#include -#include -#include -#include +#include "../core/dump.h" // core/storage.h, utils/db.h -> utils.h -> strings.h, libs/json.hpp -> cstdint, memory, string, tuple -#include "../utils/db.h" -#include "../utils/strings.h" -#include "../utils/tx.h" -#include "../utils/utils.h" -#include "variables/safebase.h" +#include "../utils/uintconv.h" +#include "../utils/strconv.h" // Forward declarations. -class ContractCallLogger; +class ContractHost; class State; +class BlockNumberObserver; +class BlockTimestampObserver; + /// Class that maintains global variables for contracts. class ContractGlobals { protected: static Address coinbase_; ///< Coinbase address (creator of current block). static Hash blockHash_; ///< Current block hash. - static uint64_t blockHeight_; ///< Current block height. - static uint64_t blockTimestamp_; ///< Current block timestamp. + static uint64_t blockHeight_; ///< Current block height. + static uint64_t blockTimestamp_; ///< Current block timestamp. public: - /// Getter for `coinbase_`. + ///@{ + /** Getter. */ static const Address& getCoinbase() { return ContractGlobals::coinbase_; } - - /// Getter for `blockHash_`. static const Hash& getBlockHash() { return ContractGlobals::blockHash_; } - - /// Getter for `blockHeight_`. static const uint64_t& getBlockHeight() { return ContractGlobals::blockHeight_; } - - /// Getter for `getBlockTimestamp_`. static const uint64_t& getBlockTimestamp() { return ContractGlobals::blockTimestamp_; } + ///@} - /// ContractManager is a friend as it can update private global vars - /// (e.g. before ethCall() with a TxBlock, State calls CM->updateContractGlobals(...)). - friend class ContractManager; + /// State can update private global vars (e.g. before starting transaction execution). + friend class State; }; /// Class that maintains local variables for contracts. class ContractLocals : public ContractGlobals { - private: - mutable Address origin_; ///< Who called the contract. - mutable Address caller_; ///< Who sent the transaction. - mutable uint256_t value_; ///< Value sent within the transaction. + public: // TODO: revert this to private + // TODO: DONT RELY ON ContractLocals, INSTEAD, USE CONTRACTHOST TO STORE LOCALS + mutable Address caller_; ///< Who sent the transaction. + mutable uint256_t value_; ///< Value sent within the transaction. protected: - /// Getter for `origin`. - const Address& getOrigin() const { return this->origin_; } - - /// Getter for `caller`. + ///@{ + /** Getter. */ const Address& getCaller() const { return this->caller_; } - - /// Getter for `value`. const uint256_t& getValue() const { return this->value_; } + ///@} - /// ContractCallLogger is a friend as it can update private local vars (e.g. before ethCall() within a contract). - friend class ContractCallLogger; + /// ContractCallLogger can update private local vars (e.g. before ethCall() within a contract). + friend class ContractHost; }; /// Base class for all contracts. -class BaseContract : public ContractLocals { +class BaseContract : public ContractLocals, public Dumpable { private: - /* Contract-specific variables */ - std::string contractName_; ///< Name of the contract, used to identify the Contract Class. - Bytes dbPrefix_; ///< Prefix for the contract DB. - Address contractAddress_; ///< Address where the contract is deployed. - Address contractCreator_; ///< Address of the creator of the contract. - uint64_t contractChainId_; ///< Chain where the contract is deployed. + const Address contractAddress_; ///< Address where the contract is deployed. + const Bytes dbPrefix_; ///< Prefix for the contract DB. + const std::string contractName_; ///< Name of the contract, used to identify the Contract Class. + const Address contractCreator_; ///< Address of the creator of the contract. + const uint64_t contractChainId_; ///< Chain where the contract is deployed. protected: - const std::unique_ptr& db_; ///< Pointer reference to the DB instance. + mutable ContractHost* host_ = nullptr; ///< Reference to the ContractHost instance. + bool reentrancyLock_ = false; ///< Lock (for reentrancy). public: - bool reentrancyLock_ = false; ///< Lock (for reentrancy). - /** * Constructor from scratch. * @param contractName The name of the contract. * @param address The address where the contract will be deployed. * @param creator The address of the creator of the contract. * @param chainId The chain where the contract will be deployed. - * @param db Pointer to the DB instance. */ BaseContract(const std::string& contractName, const Address& address, - const Address& creator, const uint64_t& chainId, const std::unique_ptr& db - ) : contractName_(contractName), contractAddress_(address), - contractCreator_(creator), contractChainId_(chainId), db_(db) - { - this->dbPrefix_ = [&]() { - Bytes prefix = DBPrefix::contracts; - prefix.reserve(prefix.size() + contractAddress_.size()); - prefix.insert(prefix.end(), contractAddress_.cbegin(), contractAddress_.cend()); - return prefix; - }(); - db->put(std::string("contractName_"), contractName_, this->getDBPrefix()); - db->put(std::string("contractAddress_"), contractAddress_.get(), this->getDBPrefix()); - db->put(std::string("contractCreator_"), contractCreator_.get(), this->getDBPrefix()); - db->put(std::string("contractChainId_"), Utils::uint64ToBytes(contractChainId_), this->getDBPrefix()); + const Address& creator, const uint64_t& chainId + ) : contractAddress_(address), + dbPrefix_([&]() { + Bytes prefix = DBPrefix::contracts; + prefix.reserve(prefix.size() + address.size()); + prefix.insert(prefix.end(), address.cbegin(), address.cend()); + return prefix; + }()), + contractName_(contractName), contractCreator_(creator), contractChainId_(chainId) { + } + + DBBatch dump() const override { + DBBatch batch; + batch.push_back(StrConv::stringToBytes("contractName_"), StrConv::stringToBytes(contractName_), this->getDBPrefix()); + batch.push_back(StrConv::stringToBytes("contractAddress_"), contractAddress_, this->getDBPrefix()); + batch.push_back(StrConv::stringToBytes("contractCreator_"), contractCreator_, this->getDBPrefix()); + batch.push_back(StrConv::stringToBytes("contractChainId_"), UintConv::uint64ToBytes(contractChainId_), this->getDBPrefix()); + return batch; } /** @@ -116,71 +106,83 @@ class BaseContract : public ContractLocals { * @param address The address where the contract will be deployed. * @param db Pointer to the DB instance. */ - BaseContract(const Address &address, const std::unique_ptr &db) : contractAddress_(address), db_(db) { - this->dbPrefix_ = [&]() -> Bytes { + BaseContract(const Address &address, const DB& db) : + contractAddress_(address), + dbPrefix_([&]() { Bytes prefix = DBPrefix::contracts; - prefix.reserve(prefix.size() + contractAddress_.size()); - prefix.insert(prefix.end(), contractAddress_.cbegin(), contractAddress_.cend()); + prefix.reserve(prefix.size() + address.size()); + prefix.insert(prefix.end(), address.cbegin(), address.cend()); return prefix; - }(); - this->contractName_ = Utils::bytesToString(db->get(std::string("contractName_"), this->getDBPrefix())); - this->contractCreator_ = Address(db->get(std::string("contractCreator_"), this->getDBPrefix())); - this->contractChainId_ = Utils::bytesToUint64(db->get(std::string("contractChainId_"), this->getDBPrefix())); - } + }()), + contractName_([&]() { + return StrConv::bytesToString(db.get(std::string("contractName_"), dbPrefix_)); + }()), + contractCreator_([&]() { + return Address(db.get(std::string("contractCreator_"), dbPrefix_)); + }()), + contractChainId_([&]() { + return UintConv::bytesToUint64(db.get(std::string("contractChainId_"), dbPrefix_)); + }()) + {} + + virtual ~BaseContract() = default; ///< Destructor. All derived classes should override it in order to call DB functions. /** - * Destructor. - * All derived classes should override it in order to call DB functions. + * Invoke a contract function using a tuple of (from, to, gasLimit, gasPrice, + * value, data). Should be overriden by derived classes. + * @param data The tuple of (from, to, gasLimit, gasPrice, value, data). + * @param host Pointer to the contract host. + * @throw DynamicException if the derived class does not override this. */ - virtual ~BaseContract() = default; + virtual void ethCall(const evmc_message& data, ContractHost* host); /** - * Invoke a contract function using a tuple of (from, to, gasLimit, gasPrice, - * value, data). Should be overriden by derived classes. + * Invoke a contract function and returns the ABI serialized output. + * To be used by EVM -> CPP calls. * @param data The tuple of (from, to, gasLimit, gasPrice, value, data). - * @throw std::runtime_error if the derived class does not override this. + * @param host Pointer to the contract host. + * @throw DynamicException if the derived class does not override this. + * @returns The ABI serialized output. */ - virtual void ethCall(const ethCallInfo& data) { - throw std::runtime_error("Derived Class from Contract does not override ethCall()"); - } + virtual Bytes evmEthCall(const evmc_message& data, ContractHost* host); /** * Do a contract call to a view function. * Should be overriden by derived classes. * @param data The tuple of (from, to, gasLimit, gasPrice, value, data). + * @param host Pointer to the contract host. * @return A string with the answer to the call. - * @throw std::runtime_error if the derived class does not override this. + * @throw DynamicException if the derived class does not override this. */ - virtual const Bytes ethCallView(const ethCallInfo &data) const { - throw std::runtime_error("Derived Class from Contract does not override ethCall()"); - } + virtual Bytes ethCallView(const evmc_message &data, ContractHost* host) const; - /// Getter for `contractAddress`. - const Address& getContractAddress() const { return this->contractAddress_; } - - /// Getter for `contractCreator`. - const Address& getContractCreator() const { return this->contractCreator_; } - - /// Getter for `contractChainId`. - const uint64_t& getContractChainId() const { return this->contractChainId_; } - - /// Getter for `contractName_`. - const std::string& getContractName() const { return this->contractName_; } - - /// Getter for `dbPrefix`. - const Bytes& getDBPrefix() const { return this->dbPrefix_; } + ///@{ + /** Getter. */ + inline const Address& getContractAddress() const { return this->contractAddress_; } + inline const Address& getContractCreator() const { return this->contractCreator_; } + inline const uint64_t& getContractChainId() const { return this->contractChainId_; } + inline const std::string& getContractName() const { return this->contractName_; } + inline const Bytes& getDBPrefix() const { return this->dbPrefix_; } + ///@} /** - * Creates a new DB prefix appending a new string to the current prefix. + * Create a new DB prefix by appending a new string to the current prefix. * @param newPrefix The new prefix to append. * @return The new prefix. */ - const Bytes getNewPrefix(const std::string& newPrefix) const { + Bytes getNewPrefix(std::string_view newPrefix) const { Bytes prefix = this->dbPrefix_; prefix.reserve(prefix.size() + newPrefix.size()); prefix.insert(prefix.end(), newPrefix.cbegin(), newPrefix.cend()); return prefix; } + + Address getOrigin() const; ///< Get the origin of the transaction. + uint64_t getNonce(const Address& address) const; ///< Get the nonce of an address. + + virtual std::span getBlockNumberObservers() const { return std::span{}; } + + virtual std::span getBlockTimestampObservers() const { return std::span{}; } }; #endif // CONTRACT_H diff --git a/src/contract/contractcalllogger.cpp b/src/contract/contractcalllogger.cpp deleted file mode 100644 index ac036e48..00000000 --- a/src/contract/contractcalllogger.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright (c) [2023-2024] [Sparq Network] - -This software is distributed under the MIT License. -See the LICENSE.txt file in the project root for more information. -*/ - -#include "contractcalllogger.h" -#include "contractmanager.h" -#include "dynamiccontract.h" -#include "contractfactory.h" - -ContractCallLogger::ContractCallLogger(ContractManager& manager) : manager_(manager) {} - -ContractCallLogger::~ContractCallLogger() { - if (this->commitCall_) { - this->commit(); - } else { - this->revert(); - } - this->manager_.factory_->clearRecentContracts(); - this->balances_.clear(); - this->usedVars_.clear(); -} - -void ContractCallLogger::commit() { - for (auto rbegin = this->usedVars_.rbegin(); rbegin != this->usedVars_.rend(); rbegin++) { - rbegin->get().commit(); - } -} - -void ContractCallLogger::revert() { - for (auto rbegin = this->usedVars_.rbegin(); rbegin != this->usedVars_.rend(); rbegin++) { - rbegin->get().revert(); - } - for (const Address& badContract : this->manager_.factory_->getRecentContracts()) { - this->manager_.contracts_.erase(badContract); // Erase failed contract creations - } -} - diff --git a/src/contract/contractcalllogger.h b/src/contract/contractcalllogger.h deleted file mode 100644 index d88ac5c2..00000000 --- a/src/contract/contractcalllogger.h +++ /dev/null @@ -1,143 +0,0 @@ -/* -Copyright (c) [2023-2024] [Sparq Network] - -This software is distributed under the MIT License. -See the LICENSE.txt file in the project root for more information. -*/ - -#ifndef CONTRACTCALLLOGGER_H -#define CONTRACTCALLLOGGER_H - -#include -#include - -#include "../utils/safehash.h" -#include "../utils/strings.h" -#include "../utils/utils.h" - -#include "contract.h" - -// Forward declarations. -class ContractManager; -class ContractLocals; - -/// Class for managing contract nested call chains and their temporary data. -class ContractCallLogger { - private: - /// Pointer back to the contract manager. - ContractManager& manager_; - - /** - * Temporary map of balances within the chain. - * Used during callContract with a payable function. - * Cleared after the callContract function commited to the state on the end - * of callContract(TxBlock&) if everything was succesful. - */ - std::unordered_map balances_; - - /** - * Temporary list of variables used by the current contract nested call chain. - * Acts as a buffer for atomic commit/revert operations on SafeVariables. - * Only holds variables for *one* nested call at a time. This means that - * once a given nested call chain ends, all variables currently in this - * list are either commited or reverted entirely, then the list itself - * is cleaned up so it can hold the variables of the next nested call. - */ - std::vector> usedVars_; - - /// Indicates whether the current call should be committed or not during logger destruction. - bool commitCall_ = false; - - /// Commit all used SafeVariables registered in the list. - void commit(); - - /// Revert all used SafeVariables registered in the list. - void revert(); - - public: - /** - * Constructor. - * @param manager Pointer back to the contract manager. - */ - explicit ContractCallLogger(ContractManager& manager); - - /// Destructor. Clears recently created contracts, altered balances and used SafeVariables. - ~ContractCallLogger(); - - /// Copy constructor (deleted). - ContractCallLogger(ContractCallLogger& other) = delete; - - /// Move constructor (deleted). - ContractCallLogger(ContractCallLogger&& other) = delete; - - /// Copy assignment opetator (deleted). - ContractCallLogger& operator=(ContractCallLogger& other) = delete; - - /// Move assignment opetator (deleted). - ContractCallLogger& operator=(ContractCallLogger&& other) = delete; - - /// Getter for `balances`. - std::unordered_map& getBalances() { return this->balances_; } - - /** - * Get a given balance value of a given address. - * @param add The address to get the balance of. - * @return The current balance for the address. - */ - uint256_t getBalanceAt(const Address& add) { return (this->hasBalance(add)) ? this->balances_[add] : 0; } - - /** - * Set a given balance for a given address. - * @param add The address to set a balance to. - * @param value The balance value to set. - */ - inline void setBalanceAt(const Address& add, const uint256_t& value) { this->balances_[add] = value; } - - /** - * Set the local variables for a given contract (origin, caller, value) - * @param contract The contract to set the local variables for. - * @param origin The origin address to set. - * @param caller The caller address to set. - * @param value The value to set. - */ - inline void setContractVars( - ContractLocals* contract, const Address& origin, - const Address& caller, const uint256_t& value - ) const { - contract->origin_ = origin; - contract->caller_ = caller; - contract->value_ = value; - } - - /** - * Add a given balance value to a given address. - * @param to The address to add balance to. - * @param value The balance value to add. - */ - inline void addBalance(const Address& to, const uint256_t& value) { this->balances_[to] += value; } - - /** - * Subtract a given balance value to a given address. - * @param to The address to subtract balance to. - * @param value The balance value to subtract. - */ - inline void subBalance(const Address& to, const uint256_t& value) { this->balances_[to] -= value; } - - /** - * Check if a given address is registered in the balances map. - * @param add The address to check. - * @return `true` if an entry exists for the address, `false` otherwise. - */ - inline bool hasBalance(const Address& add) const { return this->balances_.contains(add); } - - /** - * Add a SafeVariable to the list of used variables. - * @param var The variable to add to the list. - */ - inline void addUsedVar(SafeBase& var) { this->usedVars_.emplace_back(var); } - - /// Tell the state that the current call should be committed on the destructor. - inline void shouldCommit() { this->commitCall_ = true; } -}; - -#endif // CONTRACTCALLLOGGER_H diff --git a/src/contract/contractfactory.cpp b/src/contract/contractfactory.cpp index 56e714ed..74f0083e 100644 --- a/src/contract/contractfactory.cpp +++ b/src/contract/contractfactory.cpp @@ -1,26 +1,8 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. */ #include "contractfactory.h" - -std::unordered_set ContractFactory::getRecentContracts() const { - return this->recentContracts_; -} - -void ContractFactory::clearRecentContracts() { - this->recentContracts_.clear(); -} - -std::function ContractFactory::getCreateContractFunc(Functor func) const { - std::function ret; - if ( - auto it = this->createContractFuncs_.find(func.asBytes()); - it != this->createContractFuncs_.end() - ) ret = it->second; - return ret; -} - diff --git a/src/contract/contractfactory.h b/src/contract/contractfactory.h index ed91000a..418beb9e 100644 --- a/src/contract/contractfactory.h +++ b/src/contract/contractfactory.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,110 +8,30 @@ See the LICENSE.txt file in the project root for more information. #ifndef CONTRACTFACTORY_H #define CONTRACTFACTORY_H -#include "../utils/safehash.h" -#include "../utils/strings.h" -#include "../utils/utils.h" -#include "../utils/contractreflectioninterface.h" - -#include "abi.h" -#include "contract.h" -#include "contractmanager.h" - -/// Factory class that does the setup, creation and registration of contracts to the blockchain. -class ContractFactory { - private: - ContractManager& manager_; ///< Reference to the contract manager. - - /// Map of contract functors and create functions, used to create contracts. - std::unordered_map, SafeHash> createContractFuncs_; - - /// Set of recently created contracts. - std::unordered_set recentContracts_; - - public: - /** - * Constructor. - * @param manager Reference to the contract manager. - */ - explicit ContractFactory(ContractManager& manager) : manager_(manager) {} - - /// Getter for `recentContracts`. - std::unordered_set getRecentContracts() const; - - /// Clearer for `recentContracts`. - void clearRecentContracts(); - - /** - * Get the createNewContract function of a given contract. - * @param func The functor to search for. - * @return The respective functor's creation function, or an empty function if not found. - */ - std::function getCreateContractFunc(Functor func) const; - - /** - * Setup data for a new contract before creating/validating it. - * @param callInfo The call info to process. - * @return A pair containing the contract address and the ABI decoder. - * @throw std::runtime_error if non contract creator tries to create a contract. - * @throw std::runtime_error if contract already exists. - */ - template auto setupNewContract(const ethCallInfo &callInfo) { - // Check if caller is creator - // TODO: Check if caller is creator of the contract, not the creator of the transaction - // Allow contracts to create other contracts though. - if (this->manager_.getOrigin() != this->manager_.getContractCreator()) { - throw std::runtime_error("Only contract creator can create new contracts"); - } - - // Check if contract address already exists on the Dynamic Contract list - const Address derivedAddress = this->manager_.deriveContractAddress(); - if (this->manager_.contracts_.contains(derivedAddress)) { - throw std::runtime_error("Contract already exists as a Dynamic Contract"); - } - - // Check if contract address already exists on the Protocol Contract list - for (const auto &[name, address] : ProtocolContractAddresses) { - if (address == derivedAddress) { - throw std::runtime_error("Contract already exists as a Protocol Contract"); - } - } - - // Setup the contract - using ConstructorArguments = typename TContract::ConstructorArguments; - using DecayedArguments = decltype(Utils::removeQualifiers()); - - DecayedArguments arguments = std::apply([&callInfo](auto&&... args) { - return ABI::Decoder::decodeData...>(std::get<6>(callInfo)); - }, DecayedArguments{}); - return std::make_pair(derivedAddress, arguments); - } - - /** - * Create a new contract from a given call info. - * @param callInfo The call info to process. - * @throw runtime_error if the call to the ethCall function fails, - * or if the contract does not exist. - */ - template void createNewContract(const ethCallInfo& callInfo) { - using ConstructorArguments = typename TContract::ConstructorArguments; - auto setupResult = this->setupNewContract(callInfo); - if (!ContractReflectionInterface::isContractFunctionsRegistered()) { - throw std::runtime_error("Contract " + Utils::getRealTypeName() + " is not registered"); - } - - Address derivedAddress = setupResult.first; - const auto& decodedData = setupResult.second; - - // Update the inner variables of the contract. - // The constructor can set SafeVariable values from the constructor. - // We need to take account of that and set the variables accordingly. - auto contract = createContractWithTuple( - std::get<0>(callInfo), derivedAddress, decodedData - ); - this->recentContracts_.insert(derivedAddress); - this->manager_.contracts_.insert(std::make_pair(derivedAddress, std::move(contract))); - } - +// Leave it in to avoid "invalid use of incomplete type" warnings +#include "contracthost.h" // utils/{contractreflectioninterface.h,safehash.h,string.h,utils.h}, contractmanager.h -> abi.h, contract.h + +#include "../utils/evmcconv.h" +#include "../utils/uintconv.h" + +/// Factory **namespace** that does the setup, creation and registration of contracts to the blockchain. +/// As it is a namespace, it must take the required arguments (such as current contract list, etc.) as parameters. +/** + * + * The main argument used through the program is the following: + * boost::unordered_flat_map< + * Functor, + * std::function< + * void(const evmc_message&, + * const Address&, + * boost::unordered_flat_map, SafeHash, SafeCompare>& contracts_, + * const uint64_t&, + * ContractHost* + * )>, + * SafeHash + * > createContractFuncs_; + */ +namespace ContractFactory { /** * Helper function to create a new contract from a given call info. * @tparam TContract The contract type to create. @@ -119,29 +39,30 @@ class ContractFactory { * @tparam Is The indices of the tuple. * @param creator The address of the contract creator. * @param derivedContractAddress The address of the contract to create. + * @param chainId The chain ID of the network the contract is in. * @param dataTlp The tuple of arguments to pass to the contract constructor. * @return A unique pointer to the newly created contract. - * @throw runtime_error if any argument type mismatches. + * @throw DynamicException if any argument type mismatches. */ template std::unique_ptr createContractWithTuple( const Address& creator, const Address& derivedContractAddress, - const TTuple& dataTlp, - std::index_sequence + const uint64_t& chainId, + const TTuple& dataTlp, std::index_sequence ) { try { return std::make_unique( std::get(dataTlp)..., - *this->manager_.interface_, derivedContractAddress, creator, - this->manager_.options_->getChainID(), this->manager_.db_ + derivedContractAddress, creator, + chainId ); } catch (const std::exception& ex) { - /// TODO: If the contract constructor throws an exception, the contract is not created. - /// But the variables owned by the contract were registered as used in the ContractCallLogger. - /// Meaning: we throw here, the variables are freed (as TContract ceases from existing), but a reference to the variable is still - /// in the ContractCallLogger. This causes a instant segfault when ContractCallLogger tries to revert the variable - throw std::runtime_error( + // TODO: If the contract constructor throws an exception, the contract is not created. + // But the variables owned by the contract were registered as used in the ContractCallLogger. + // Meaning: we throw here, the variables are freed (as TContract ceases from existing), but a reference to the variable is still + // in the ContractCallLogger. This causes a instant segfault when ContractCallLogger tries to revert the variable + throw DynamicException( "Could not construct contract " + Utils::getRealTypeName() + ": " + ex.what() ); } @@ -151,80 +72,148 @@ class ContractFactory { * Helper function to create a new contract from a given call info. * @param creator The address of the contract creator. * @param derivedContractAddress The address of the contract to create. + * @param chainId The chain ID of the network the contract is in. * @param dataTpl The vector of arguments to pass to the contract constructor. - * @throw runtime_error if the size of the vector does not match the number of - * arguments of the contract constructor. + * @throw DynamicException if the size of the vector does not match the number of arguments of the contract constructor. */ template std::unique_ptr createContractWithTuple( - const Address& creator, const Address& derivedContractAddress, + const Address& creator, + const Address& derivedContractAddress, + const uint64_t& chainId, const TTuple& dataTpl ) { constexpr std::size_t TupleSize = std::tuple_size::value; - return this->createContractWithTuple( - creator, derivedContractAddress, dataTpl, std::make_index_sequence{} + return createContractWithTuple( + creator, derivedContractAddress, chainId, dataTpl, std::make_index_sequence{} + ); + } + + /** + * Setup data for a new contract before creating/validating it. + * @param callInfo The call info to process. + * @return A pair containing the contract address and the ABI decoder. + * @throw DynamicException if non contract creator tries to create a contract. + * @throw DynamicException if contract already exists as either a Dynamic or Protocol contract. + */ + template auto setupNewContractArgs(const evmc_message& callInfo) { + // Setup the contract + using ConstructorArguments = typename TContract::ConstructorArguments; + using DecayedArguments = decltype(Utils::removeQualifiers()); + DecayedArguments arguments = std::apply([&callInfo](auto&&... args) { + return ABI::Decoder::decodeData...>(EVMCConv::getFunctionArgs(callInfo)); + }, DecayedArguments{}); + return arguments; + } + + /** + * Create a new contract from a given call info. + * @param callInfo The call info to process. + * @param derivedAddress The derived address of the contract. + * @param contracts List of contracts. + * @param chainId Chain ID of the network the contract will be in. + * @param host Pointer to the contract host. + * @throw DynamicException if the call to the ethCall function fails, or if the contract does not exist. + */ + template void createNewContract(const evmc_message& callInfo, + const Address& derivedAddress, + boost::unordered_flat_map, SafeHash, SafeCompare>& contracts, + const uint64_t& chainId, + ContractHost* host) { + using ConstructorArguments = typename TContract::ConstructorArguments; + auto decodedData = setupNewContractArgs(callInfo); + if (!ContractReflectionInterface::isContractFunctionsRegistered()) { + throw DynamicException("Contract " + Utils::getRealTypeName() + " is not registered"); + } + // Update the inner variables of the contract. + // The constructor can set SafeVariable values from the constructor. + // We need to take account of that and set the variables accordingly. + auto contract = createContractWithTuple( + Address(callInfo.sender), derivedAddress, chainId, decodedData ); + host->addContractObservers(*contract); + host->context().addContract(derivedAddress, std::move(contract)); } /** - * Adds contract create and validate functions to the respective maps - * @tparam Contract Contract type - * @param createFunc Function to create a new contract + * Adds contract create and validate functions to the respective maps. + * @tparam Contract Contract type. + * @param createFunc Function to create a new contract. + * @param createContractFuncs Function to create the functions of a new contract. */ template - void addContractFuncs(const std::function& createFunc) { + void addContractFuncs(const std::function< + void(const evmc_message&, + const Address&, + boost::unordered_flat_map, SafeHash, SafeCompare>& contracts_, + const uint64_t&, + ContractHost* host)>& createFunc + ,boost::unordered_flat_map, SafeHash, SafeCompare>& contracts_, + const uint64_t&, + ContractHost*)>,SafeHash>& createContractFuncs + ) { std::string createSignature = "createNew" + Utils::getRealTypeName() + "Contract("; // Append args createSignature += ContractReflectionInterface::getConstructorArgumentTypesString(); createSignature += ")"; - Functor functor = Utils::sha3(Utils::create_view_span(createSignature)).view_const(0, 4); - this->createContractFuncs_[functor.asBytes()] = createFunc; + auto hash = Utils::sha3(Utils::create_view_span(createSignature)); + Functor functor; + functor.value = UintConv::bytesToUint32(hash.view(0,4)); + createContractFuncs[functor] = createFunc; } /** * Register all contracts in the variadic template. * @tparam Contracts The contracts to register. */ - template - void addAllContractFuncsHelper(std::index_sequence) { - ((this->addContractFuncs>( [&](const ethCallInfo &callInfo) { - this->createNewContract>(callInfo); - })), ...); + template void addAllContractFuncsHelper( + boost::unordered_flat_map, SafeHash, SafeCompare>& contracts_, + const uint64_t&, + ContractHost* + )>, SafeHash>& createContractFuncs, + std::index_sequence + ) { + ((addContractFuncs>([]( + const evmc_message &callInfo, + const Address &derivedAddress, + boost::unordered_flat_map, SafeHash, SafeCompare> &contracts, + const uint64_t &chainId, + ContractHost* host + ) { + createNewContract>(callInfo, derivedAddress, contracts, chainId, host); + }, createContractFuncs)), ...); } /** * Add all contract functions to the respective maps using the helper function. * @tparam Tuple The tuple of contracts to add. */ - template - std::enable_if_t::value, void> addAllContractFuncs() { - addAllContractFuncsHelper(std::make_index_sequence::value>{}); - } - - /** - * Add all contract functions to the respective maps. - * @tparam Contracts The contracts to add. - */ - template - std::enable_if_t>::value, void> addAllContractFuncs() { - (void)std::initializer_list{((void)addAllContractFuncs(), 0)...}; + template requires Utils::is_tuple::value void addAllContractFuncs( + boost::unordered_flat_map, SafeHash, SafeCompare>& contracts_, + const uint64_t&, + ContractHost*)>,SafeHash>& createContractFuncs) { + addAllContractFuncsHelper(createContractFuncs, std::make_index_sequence::value>{}); } /** * Struct for calling the registerContract function of a contract. * @tparam TContract The contract to register. */ - template struct RegisterContract { - RegisterContract() { T::registerContract(); } - }; + template struct RegisterContract { RegisterContract() { T::registerContract(); } }; /** * Helper function to register all contracts. * @tparam Tuple The tuple of contracts to register. * @tparam Is The indices of the tuple. */ - template - void registerContractsHelper(std::index_sequence) const { + template void registerContractsHelper(std::index_sequence) { (RegisterContract>(), ...); } @@ -232,8 +221,7 @@ class ContractFactory { * Register all contracts in the tuple. * @tparam Tuple The tuple of contracts to register. */ - template - std::enable_if_t::value, void> registerContracts() { + template requires Utils::is_tuple::value void registerContracts() { registerContractsHelper(std::make_index_sequence::value>{}); } }; diff --git a/src/contract/contracthost.cpp b/src/contract/contracthost.cpp new file mode 100644 index 00000000..5e9d3788 --- /dev/null +++ b/src/contract/contracthost.cpp @@ -0,0 +1,62 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "../utils/strconv.h" //cArrayToBytes + +#include "contracthost.h" + +uint256_t ContractHost::getRandomValue() { + return std::invoke(messageHandler_.handler().precompiledExecutor().randomGenerator()); +} + +ContractHost::~ContractHost() { + if (mustRevert_) { + for (auto& var : this->stack_.getUsedVars()) { + var.get().revert(); + } + + context_.revert(); + } else { + for (auto& var : this->stack_.getUsedVars()) + var.get().commit(); + + for (auto& [address, contract] : context_.getNewContracts()) { + if (contract == nullptr) { + continue; + } + + this->manager_.pushBack(dynamic_cast(contract)); + } + + auto transaction = this->storage_.events().transaction(); + + for (const auto& event : context_.getEvents()) { + this->storage_.events().putEvent(event); + } + + transaction->commit(); + context_.commit(); + } + + if (messageHandler_.hasCallTrace()) { + storage_.putCallTrace(Hash(context_.getTxHash()), messageHandler_.getCallTrace()); + } +} + +void ContractHost::addContractObservers(const BaseContract& contract) { + if (blockObservers_ == nullptr) { + return; + } + + for (const auto& observer : contract.getBlockNumberObservers()) { + blockObservers_->add(observer); + } + + for (const auto& observer : contract.getBlockTimestampObservers()) { + blockObservers_->add(observer); + } +} diff --git a/src/contract/contracthost.h b/src/contract/contracthost.h new file mode 100644 index 00000000..c16faf48 --- /dev/null +++ b/src/contract/contracthost.h @@ -0,0 +1,242 @@ + +#ifndef CONTRACT_HOST_H +#define CONTRACT_HOST_H + +// utils/{contractreflectioninterface.h, db.h, safehash.h, (strings.h -> hex.h, evmc/evmc.hpp), utils.h}, +// contract.h -> core/{dump.h, storage.h -> calltracer.h} +#include "contractmanager.h" +#include "../core/dump.h" +#include "calltracer.h" +#include "bytes/join.h" +#include "bytes/cast.h" +#include "gas.h" +#include "concepts.h" +#include "executioncontext.h" +#include "messagedispatcher.h" +#include "packedmessages.h" +#include "calltracer.h" +#include "costs.h" +#include "blockobservers.h" + +// TODO: EVMC Static Mode Handling +// TODO: Contract creating other contracts (EVM Factories) +// TODO: Proper gas limit tests. + +/** + * The ContractHost class is the class which holds a single line of execution for a contract. (This also includes nested calls) + * It MUST be unique for each line of execution, as it holds the used stack, accounts, and storage for the contract. + * The ContractHost class have the following responsibilities: + * - Be the EVMC Host implementation for EVM contracts + * - Hold the stack (ContractStack) of the execution. + * - Allow both C++ and EVM contracts to be called. + * - Allow interoperability between C++ and EVM contracts. + * - Process the commit/revert of the stack during **destructor** call. + */ + +/** + * GasLimit Rules + * Any call: 21000 + * C++ Call: 1000 + * EVM Call: 5000 + * Any EVM Contract: 100000 + * Any CPP Contract: 50000 + */ + +class ContractHost { + private: + DumpManager& manager_; + Storage& storage_; + mutable ContractStack stack_; + bool mustRevert_ = true; // We always assume that we must revert until proven otherwise. + ExecutionContext& context_; + CallTracer messageHandler_; + BlockObservers *blockObservers_; + + public: + ContractHost(evmc_vm* vm, + DumpManager& manager, + Storage& storage, + const Hash& randomnessSeed, + ExecutionContext& context, + BlockObservers *blockObservers = nullptr) : + manager_(manager), + storage_(storage), + stack_(), + context_(context), + blockObservers_(blockObservers), + messageHandler_(MessageDispatcher(context_, CppContractExecutor(context_, *this), EvmContractExecutor(context_, vm, storage.getIndexingMode()), PrecompiledContractExecutor(RandomGen(randomnessSeed))), storage.getIndexingMode()) { + messageHandler_.handler().evmExecutor().setMessageHandler(AnyEncodedMessageHandler::from(messageHandler_)); // TODO: is this really required? + } + + // Rule of five, no copy/move allowed. + ContractHost(const ContractHost&) = delete; + ContractHost(ContractHost&&) = delete; + ContractHost& operator=(const ContractHost&) = delete; + ContractHost& operator=(ContractHost&&) = delete; + ~ContractHost(); + + decltype(auto) simulate(concepts::Message auto&& msg) { + decltype(auto) result = execute(std::forward(msg)); + mustRevert_ = true; + return result; + } + + decltype(auto) execute(concepts::Message auto&& msg) { + try { + mustRevert_ = false; + msg.gas().use(CONTRACT_EXECUTION_COST); + return dispatchMessage(std::forward(msg)); + } catch (VMExecutionError& e){ + mustRevert_ = true; + throw; + } catch (const std::exception& err) { + mustRevert_ = true; + throw; + } + } + + /** + * Call a contract view function based on the basic requirements of a contract call. + * @tparam R The return type of the view function. + * @tparam C The contract type. + * @tparam Args The argument types of the view function. + * @param caller Pointer to the contract that made the call. + * @param targetAddr The address of the contract to call. + * @param func The view function to call. + * @param args The arguments to pass to the view function. + * @return The result of the view function. + */ + template + R callContractViewFunction(const BaseContract* caller, const Address& targetAddr, R(C::*func)(const Args&...) const, const Args&... args) { + PackedStaticCallMessage msg( + caller->getContractAddress(), + targetAddr, + this->getCurrentGas(), + func, + args...); + + return this->dispatchMessage(std::move(msg)); + } + + /** + * Call a contract non-view function based on the basic requirements of a contract call. + * @tparam R The return type of the view function. + * @tparam C The contract type. + * @tparam Args The argument types of the view function. + * @param caller Pointer to the contract that made the call. + * @param targetAddr The address of the contract to call. + * @param value The value to use in the call. + * @param func The view function to call. + * @param args The arguments to pass to the view function. + * @return The result of the non-view function. + */ + template + R callContractFunction( + BaseContract* caller, const Address& targetAddr, + const uint256_t& value, + R(C::*func)(const Args&...), const Args&... args) { + + PackedCallMessage msg( + caller->getContractAddress(), + targetAddr, + this->getCurrentGas(), + value, + func, + args...); + + return this->dispatchMessage(std::move(msg)); + } + + /** + * Call the createNewContract function of a contract. + * Used by DynamicContract to create new contracts. + * @tparam TContract The contract type. + * @param caller Pointer to the contract that made the call. + * @param fullData The caller data. + * @return The address of the new contract. + */ + template + Address callCreateContract(BaseContract& caller, Args&&... args) { + const uint256_t value = 0; + PackedCreateMessage msg( + caller.getContractAddress(), + this->getCurrentGas(), + value, + std::forward(args)... + ); + + return this->dispatchMessage(std::move(msg)); + } + + void addContractObservers(const BaseContract& contract); + + + /** + * Get a contract by its address. + * Used by DynamicContract to access view/const functions of other contracts. + * @tparam T The contract type. + * @param address The address of the contract. + * @return A pointer to the contract. + * @throw DynamicException if contract is not found or not of the requested type. + */ + template + const T* getContract(View
address) const { + const T* pointer = dynamic_cast(&context_.getContract(address)); + + if (pointer == nullptr) { + throw DynamicException("Wrong contract type"); + } + + return pointer; + } + + /** + * Get a contract by its address (non-const). + * Used by DynamicContract to access view/const functions of other contracts. + * @tparam T The contract type. + * @param address The address of the contract. + * @return A pointer to the contract. + * @throw DynamicException if contract is not found or not of the requested type. + */ + template + T* getContract(const Address& address) { + T* pointer = dynamic_cast(&context_.getContract(address)); + + if (pointer == nullptr) { + throw DynamicException("Wrong contract type"); + } + + return pointer; + } + + template + void emitEvent( + const std::string& name, + const Address& contract, + const std::tuple...>& args, + bool anonymous + ) { + if (storage_.getIndexingMode() == IndexingMode::RPC_TRACE) { + context_.addEvent(name, contract, args, anonymous); + } + } + + ExecutionContext& context() { return context_; } + + const ExecutionContext& context() const { return context_; } + + void registerVariableUse(SafeBase& var) { stack_.registerVariableUse(var); } + + uint256_t getRandomValue(); + +private: + decltype(auto) dispatchMessage(auto&& msg) { + return messageHandler_.onMessage(std::forward(msg)); + } + + Gas& getCurrentGas() { + return messageHandler_.handler().cppExecutor().currentGas(); + } +}; + +#endif // CONTRACT_HOST_H diff --git a/src/contract/contractmanager.cpp b/src/contract/contractmanager.cpp index b90b4d82..74144e64 100644 --- a/src/contract/contractmanager.cpp +++ b/src/contract/contractmanager.cpp @@ -1,304 +1,147 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. */ #include "contractmanager.h" -#include "abi.h" #include "contractfactory.h" -#include "contractcalllogger.h" #include "customcontracts.h" + #include "../core/rdpos.h" -#include "../core/state.h" ContractManager::ContractManager( - const std::unique_ptr& db, State* state, - const std::unique_ptr& rdpos, const std::unique_ptr& options -) : BaseContract("ContractManager", ProtocolContractAddresses.at("ContractManager"), options->getChainOwner(), 0, db), - state_(state), - rdpos_(rdpos), - options_(options), - factory_(std::make_unique(*this)), - interface_(std::make_unique(*this)), - eventManager_(std::make_unique(db, options)) + const DB& db, boost::unordered_flat_map, SafeHash, SafeCompare>& contracts, + DumpManager& manager, BlockObservers& observer, const Options& options +) : BaseContract("ContractManager", ProtocolContractAddresses.at("ContractManager"), + options.getChainOwner(), options.getChainID()), contracts_(contracts), manager_(manager) { - this->callLogger_ = std::make_unique(*this); - this->factory_->registerContracts(); - this->factory_->addAllContractFuncs(); + ContractFactory::registerContracts(); + ContractFactory::registerContracts(); + ContractFactory::addAllContractFuncs(this->createContractFuncs_); // Load Contracts from DB - std::vector contractsFromDB = this->db_->getBatch(DBPrefix::contractManager); - for (const DBEntry& contract : contractsFromDB) { + for (const DBEntry& contract : db.getBatch(DBPrefix::contractManager)) { Address address(contract.key); - if (!this->loadFromDB(contract, address)) { - throw std::runtime_error("Unknown contract: " + Utils::bytesToString(contract.value)); + if (!this->loadFromDB(contract, address, db, observer)) { + throw DynamicException("Unknown contract: " + StrConv::bytesToString(contract.value)); } } + manager.pushBack(this); } -ContractManager::~ContractManager() { +ContractManager::~ContractManager() {} + +DBBatch ContractManager::dump() const { DBBatch contractsBatch; for (const auto& [address, contract] : this->contracts_) { + if (typeid(*contract) == typeid(ContractManager)) continue; + if (typeid(*contract) == typeid(rdPoS)) continue; contractsBatch.push_back( Bytes(address.asBytes()), - Utils::stringToBytes(contract->getContractName()), + StrConv::stringToBytes(contract->getContractName()), DBPrefix::contractManager ); } - this->db_->putBatch(contractsBatch); -} - -Address ContractManager::deriveContractAddress() const { - // Contract address is last 20 bytes of sha3 ( rlp ( tx from address + tx nonce ) ) - uint8_t rlpSize = 0xc0; - rlpSize += this->getCaller().size(); - // As we don't have actually access to the nonce, we will use the number of contracts existing in the chain - rlpSize += (this->contracts_.size() < 0x80) - ? 1 : 1 + Utils::bytesRequired(this->contracts_.size()); - Bytes rlp; - rlp.insert(rlp.end(), rlpSize); - rlp.insert(rlp.end(), this->getCaller().cbegin(), this->getCaller().cend()); - rlp.insert(rlp.end(), (this->contracts_.size() < 0x80) - ? (char)this->contracts_.size() - : (char)0x80 + Utils::bytesRequired(this->contracts_.size()) - ); - return Address(Utils::sha3(rlp).view_const(12)); + return contractsBatch; } -Bytes ContractManager::getDeployedContracts() const { - std::shared_lock lock(this->contractsMutex_); - std::vector names; - std::vector
addresses; +std::vector> ContractManager::getDeployedContracts() const { + std::vector> contracts; for (const auto& [address, contract] : this->contracts_) { - names.push_back(contract->getContractName()); - addresses.push_back(address); + std::tuple contractTuple(contract->getContractName(), address); + contracts.push_back(contractTuple); } - Bytes result = ABI::Encoder::encodeData(names, addresses); - return result; -} - -void ContractManager::ethCall(const ethCallInfo& callInfo) { - Functor functor = std::get<5>(callInfo); - std::function f; - f = this->factory_->getCreateContractFunc(functor.asBytes()); - if (!f) throw std::runtime_error("Invalid function call with functor: " + functor.hex().get()); - f(callInfo); -} - -const Bytes ContractManager::ethCallView(const ethCallInfo& data) const { - const auto& functor = std::get<5>(data); - // This hash is equivalent to "function getDeployedContracts() public view returns (string[] memory, address[] memory) {}" - if (functor == Hex::toBytes("0xaa9a068f")) return this->getDeployedContracts(); - throw std::runtime_error("Invalid function call"); + return contracts; } -void ContractManager::callContract(const TxBlock& tx, const Hash& blockHash, const uint64_t& txIndex) { - this->callLogger_ = std::make_unique(*this); - auto callInfo = tx.txToCallInfo(); - const auto& [from, to, gasLimit, gasPrice, value, functor, data] = callInfo; - if (to == this->getContractAddress()) { - this->callLogger_->setContractVars(this, from, from, value); - try { - this->ethCall(callInfo); - } catch (std::exception &e) { - this->callLogger_.reset(); - this->eventManager_->revertEvents(); - throw std::runtime_error(e.what()); - } - this->callLogger_->shouldCommit(); - this->callLogger_.reset(); - this->eventManager_->commitEvents(tx.hash(), txIndex); - return; - } - - if (to == ProtocolContractAddresses.at("rdPoS")) { - this->callLogger_->setContractVars(rdpos_.get(), from, from, value); - try { - rdpos_->ethCall(callInfo); - } catch (std::exception &e) { - this->callLogger_.reset(); - this->eventManager_->revertEvents(); - throw std::runtime_error(e.what()); +std::vector> ContractManager::getDeployedContractsForCreator(const Address& creator) const { + std::vector> contracts; + for (const auto& [address, contract] : this->contracts_) { + if (contract->getContractCreator() == creator) { + std::tuple contractTuple(contract->getContractName(), address); + contracts.push_back(contractTuple); } - this->callLogger_->shouldCommit(); - this->callLogger_.reset(); - this->eventManager_->commitEvents(tx.hash(), txIndex); - return; } + return contracts; +} - std::unique_lock lock(this->contractsMutex_); - auto it = this->contracts_.find(to); +std::tuple ContractManager::getContractInfo(const Address& addr) const { + auto it = this->contracts_.find(addr); if (it == this->contracts_.end()) { - this->callLogger_.reset(); - this->eventManager_->revertEvents(); - throw std::runtime_error(std::string(__func__) + "(void): Contract does not exist"); - } - - const std::unique_ptr& contract = it->second; - this->callLogger_->setContractVars(contract.get(), from, from, value); - try { - contract->ethCall(callInfo); - } catch (std::exception &e) { - this->callLogger_.reset(); - this->eventManager_->revertEvents(); - throw std::runtime_error(e.what()); - } - - if (contract->isPayableFunction(functor)) { - this->state_->processContractPayable(this->callLogger_->getBalances()); + return std::make_tuple(false, ""); } - this->callLogger_->shouldCommit(); - this->callLogger_.reset(); - this->eventManager_->commitEvents(tx.hash(), txIndex); + return std::make_tuple(true, it->second->getContractName()); } -const Bytes ContractManager::callContract(const ethCallInfo& callInfo) const { - const auto& [from, to, gasLimit, gasPrice, value, functor, data] = callInfo; - if (to == this->getContractAddress()) return this->ethCallView(callInfo); - if (to == ProtocolContractAddresses.at("rdPoS")) return rdpos_->ethCallView(callInfo); - std::shared_lock lock(this->contractsMutex_); - if (!this->contracts_.contains(to)) { - throw std::runtime_error(std::string(__func__) + "(Bytes): Contract does not exist"); +void ContractManager::ethCall(const evmc_message& callInfo, ContractHost* host) { + this->host_ = host; + PointerNullifier nullifier(this->host_, this->nullifiable_); + const Address caller(callInfo.sender); + const Functor functor = EVMCConv::getFunctor(callInfo); + // Call the function on this->createContractFuncs_ + auto it = this->createContractFuncs_.find(functor); + if (it == this->createContractFuncs_.end()) { + return; } - return this->contracts_.at(to)->ethCallView(callInfo); -} - -bool ContractManager::isPayable(const ethCallInfo& callInfo) const { - const auto& address = std::get<1>(callInfo); - const auto& functor = std::get<5>(callInfo); - std::shared_lock lock(this->contractsMutex_); - auto it = this->contracts_.find(address); - if (it == this->contracts_.end()) return false; - return it->second->isPayableFunction(functor); -} - -bool ContractManager::validateCallContractWithTx(const ethCallInfo& callInfo) { - this->callLogger_ = std::make_unique(*this); - const auto& [from, to, gasLimit, gasPrice, value, functor, data] = callInfo; - try { - if (value) { - // Payable, we need to "add" the balance to the contract - this->interface_->populateBalance(from); - this->interface_->populateBalance(to); - this->callLogger_->subBalance(from, value); - this->callLogger_->addBalance(to, value); - } - if (to == this->getContractAddress()) { - this->callLogger_->setContractVars(this, from, from, value); - this->ethCall(callInfo); - this->callLogger_.reset(); - return true; - } - - if (to == ProtocolContractAddresses.at("rdPoS")) { - this->callLogger_->setContractVars(rdpos_.get(), from, from, value); - rdpos_->ethCall(callInfo); - this->callLogger_.reset(); - return true; - } - - std::shared_lock lock(this->contractsMutex_); - if (!this->contracts_.contains(to)) { - this->callLogger_.reset(); - return false; - } - const auto &contract = contracts_.at(to); - this->callLogger_->setContractVars(contract.get(), from, from, value); - contract->ethCall(callInfo); - } catch (std::exception &e) { - this->callLogger_.reset(); - throw std::runtime_error(e.what()); + if (callInfo.flags == EVMC_STATIC) { + throw DynamicException("ContractManager: Static calls trying to create a contract?!"); } - this->callLogger_.reset(); - return true; -} - -bool ContractManager::isContractCall(const TxBlock &tx) const { - if (tx.getTo() == this->getContractAddress()) return true; - for (const auto& [protocolName, protocolAddress] : ProtocolContractAddresses) { - if (tx.getTo() == protocolAddress) return true; + it->second(callInfo, + generateContractAddress(this->host_->context().getAccount(caller).getNonce(), caller), + this->contracts_, + this->getContractChainId(), + this->host_); + this->executed_ = true; +} + +Bytes ContractManager::evmEthCall(const evmc_message& callInfo, ContractHost* host) { + this->ethCall(callInfo, host); + if (this->executed_) { + this->executed_ = false; + return Bytes(); } - std::shared_lock lock(this->contractsMutex_); - return this->contracts_.contains(tx.getTo()); -} - -bool ContractManager::isContractAddress(const Address &address) const { - std::shared_lock lock(this->contractsMutex_); - for (const auto& [protocolName, protocolAddress] : ProtocolContractAddresses) { - if (address == protocolAddress) return true; + auto functor = EVMCConv::getFunctor(callInfo); + // This hash is equivalent to "function getDeployedContracts() public view returns (Contract[] memory) {}" + // 0xaa9a068f == uint32_t(2862220943); + if (functor.value == 2862220943) return ABI::Encoder::encodeData(this->getDeployedContracts()); + // This hash is equivalent to "function getDeployedContractsForCreator(address creator) public view returns (Contract[] memory) {}" + // 0x73474f5a == uint32_t(1934053210) + if (functor.value == 1934053210) { + auto args = EVMCConv::getFunctionArgs(callInfo); + auto [addr] = ABI::Decoder::decodeData
(args); + return ABI::Encoder::encodeData(this->getDeployedContractsForCreator(addr)); } - return this->contracts_.contains(address); -} - -std::vector> ContractManager::getContracts() const { - std::shared_lock lock(this->contractsMutex_); - std::vector> contracts; - for (const auto& [address, contract] : this->contracts_) { - contracts.emplace_back(std::make_pair(contract->getContractName(), address)); + // This hash is equivalent to "function getContractInfo(address addr) external view returns (ContractInfo memory)" + // 0xcd481e51 = uint32_t(3444055633) + if (functor.value == 3444055633) { + auto args = EVMCConv::getFunctionArgs(callInfo); + auto [addr] = ABI::Decoder::decodeData
(args); + return ABI::Encoder::encodeData(this->getContractInfo(addr)); } - return contracts; -} - -const std::vector ContractManager::getEvents( - const uint64_t& fromBlock, const uint64_t& toBlock, - const Address& address, const std::vector& topics -) const { - return this->eventManager_->getEvents(fromBlock, toBlock, address, topics); -} - -const std::vector ContractManager::getEvents( - const Hash& txHash, const uint64_t& blockIndex, const uint64_t& txIndex -) const { - return this->eventManager_->getEvents(txHash, blockIndex, txIndex); -} - -void ContractManager::updateContractGlobals( - const Address& coinbase, const Hash& blockHash, - const uint64_t& blockHeight, const uint64_t& blockTimestamp -) const { - ContractGlobals::coinbase_ = coinbase; - ContractGlobals::blockHash_ = blockHash; - ContractGlobals::blockHeight_ = blockHeight; - ContractGlobals::blockTimestamp_ = blockTimestamp; -} - -void ContractManagerInterface::registerVariableUse(SafeBase& variable) { - this->manager_.callLogger_->addUsedVar(variable); -} - -void ContractManagerInterface::populateBalance(const Address &address) const { - if (!this->manager_.callLogger_) throw std::runtime_error( - "Contracts going haywire! Trying to call ContractState without an active callContract" - ); - if (!this->manager_.callLogger_->hasBalance(address)) { - auto it = this->manager_.state_->accounts_.find(address); - this->manager_.callLogger_->setBalanceAt(address, - (it != this->manager_.state_->accounts_.end()) ? it->second.balance : 0 - ); + throw DynamicException("ContractManager: Invalid function call"); + return Bytes(); +} + +Bytes ContractManager::ethCallView(const evmc_message& callInfo, ContractHost* host) const { + // This hash is equivalent to "function getDeployedContracts() public view returns (Contract[] memory) {}" + // 0xaa9a068f == uint32_t(2862220943); + auto functor = EVMCConv::getFunctor(callInfo); + if (functor.value == 2862220943) return ABI::Encoder::encodeData(this->getDeployedContracts()); + // This hash is equivalent to "function getDeployedContractsForCreator(address creator) public view returns (Contract[] memory) {}" + // 0x73474f5a == uint32_t(1934053210) + if (functor.value == 1934053210) { + auto args = EVMCConv::getFunctionArgs(callInfo); + auto [addr] = ABI::Decoder::decodeData
(args); + return ABI::Encoder::encodeData(this->getDeployedContractsForCreator(addr)); } -} - -uint256_t ContractManagerInterface::getBalanceFromAddress(const Address& address) const { - if (!this->manager_.callLogger_) throw std::runtime_error( - "Contracts going haywire! Trying to call ContractState without an active callContract" - ); - this->populateBalance(address); - return this->manager_.callLogger_->getBalanceAt(address); -} - -void ContractManagerInterface::sendTokens( - const Address& from, const Address& to, const uint256_t& amount -) { - if (!this->manager_.callLogger_) throw std::runtime_error( - "Contracts going haywire! Trying to call ContractState without an active callContract" - ); - this->populateBalance(from); - this->populateBalance(to); - if (this->manager_.callLogger_->getBalanceAt(from) < amount) { - throw std::runtime_error("ContractManager::sendTokens: Not enough balance"); + // This hash is equivalent to "function getContractInfo(address addr) external view returns (ContractInfo memory)" + // 0xcd481e51 = uint32_t(3444055633) + if (functor.value == 3444055633) { + auto args = EVMCConv::getFunctionArgs(callInfo); + auto [addr] = ABI::Decoder::decodeData
(args); + return ABI::Encoder::encodeData(this->getContractInfo(addr)); } - this->manager_.callLogger_->subBalance(from, amount); - this->manager_.callLogger_->addBalance(to, amount); + throw DynamicException("Invalid function call"); } diff --git a/src/contract/contractmanager.h b/src/contract/contractmanager.h index 6bb0e5a0..e1405700 100644 --- a/src/contract/contractmanager.h +++ b/src/contract/contractmanager.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,40 +8,12 @@ See the LICENSE.txt file in the project root for more information. #ifndef CONTRACTMANAGER_H #define CONTRACTMANAGER_H -#include -#include -#include +#include "../utils/contractreflectioninterface.h" // contract/abi.h -> utils.h -> strings.h, libs/json.hpp -> boost/unordered/unordered_flat_map.hpp -#include "abi.h" -#include "contract.h" -#include "contractcalllogger.h" -#include "event.h" -#include "variables/safeunorderedmap.h" +#include "contract.h" // core/dump.h -> utils/db.h -#include "../utils/db.h" -#include "../utils/options.h" -#include "../utils/safehash.h" -#include "../utils/strings.h" -#include "../utils/tx.h" -#include "../utils/utils.h" -#include "../utils/contractreflectioninterface.h" - -// Forward declarations. -class rdPoS; -class State; -class ContractFactory; -class ContractManagerInterface; - -/** - * Addresses for the contracts that are deployed at protocol level (contract name -> contract address). - * That means these contracts are deployed at the beginning of the chain. - * They cannot be destroyed nor dynamically deployed like other contracts. - * Instead, they are deployed in the constructor of State. - */ -const std::unordered_map ProtocolContractAddresses = { - {"rdPoS", Address(Hex::toBytes("0xb23aa52dbeda59277ab8a962c69f5971f22904cf"))}, // Sha3("randomDeterministicProofOfStake").substr(0,20) - {"ContractManager", Address(Hex::toBytes("0x0001cb47ea6d8b55fe44fdd6b1bdb579efb43e61"))} // Sha3("ContractManager").substr(0,20) -}; +#include "blockobservers.h" +#include "../utils/strconv.h" /** * Class that holds all current contract instances in the blockchain state. @@ -50,55 +22,47 @@ const std::unordered_map ProtocolContractAddresses = { */ class ContractManager : public BaseContract { private: - /// List of currently deployed contracts. - std::unordered_map, SafeHash> contracts_; - - /** - * Raw pointer to the blockchain state object. - * Used if the contract is a payable function. - * Can be `nullptr` due to tests not requiring state (contract balance). - */ - State* state_; - - /// Reference pointer to the rdPoS contract. - const std::unique_ptr& rdpos_; + /// Boolean that accompanies the PointerNullifier for EVM calls + bool nullifiable_ = true; + /// Reference to the current dump manager. + /// Owned by the State + DumpManager& manager_; + boost::unordered_flat_map, SafeHash, SafeCompare>& contracts_; - /// Reference pointer to the options singleton. - const std::unique_ptr& options_; + /// Functions to create contracts. + boost::unordered_flat_map< + Functor, + std::function< + void(const evmc_message&, + const Address&, + boost::unordered_flat_map, SafeHash, SafeCompare>& contracts_, + const uint64_t&, + ContractHost* + )>, + SafeHash + > createContractFuncs_; /** - * Pointer to the contract factory object. - * Responsible for actually creating the contracts and - * deploying them in the contract manager. + * Get all deployed contracts. + * struct Contract { string name; address addr; } + * function getDeployedContracts() public view returns (Contract[] memory) { */ - std::unique_ptr factory_; + std::vector> getDeployedContracts() const; - /// Pointer to the contract manager's interface to be passed to DynamicContract. - std::unique_ptr interface_; + // Temporary variable to allow the proper execution of evmEthCall + bool executed_ = false; /** - * Pointer to the event manager object. - * Responsible for maintaining events emitted in contract calls. + * Get all deployed contracts from a specific creator address. + * function getDeployedContractsForCreator(Address creator) public view returns (Contract[] memory) { */ - const std::unique_ptr eventManager_; - - /** - * Pointer to the call state object. - * Responsible for maintaining temporary data used in contract call chains. - */ - std::unique_ptr callLogger_; - - /// Mutex that manages read/write access to the contracts. - mutable std::shared_mutex contractsMutex_; - - /// Derive a new contract address based on transaction sender and nonce. - Address deriveContractAddress() const; + std::vector> getDeployedContractsForCreator(const Address& creator) const; /** - * Get a serialized string with the deployed contracts. Solidity counterpart: - * function getDeployedContracts() public view returns (string[] memory, address[] memory) {} + * Get the information of a specific contract. + * function getContractInfo(address addr) external view returns (ContractInfo memory); */ - Bytes getDeployedContracts() const; + std::tuple getContractInfo(const Address& addr) const; /** * Helper function to load all contracts from the database. @@ -106,11 +70,12 @@ class ContractManager : public BaseContract { * @tparam Is The indices of the tuple. * @param contract The contract to load. * @param contractAddress The address of the contract. - * @return True if the contract exists in the database, false otherwise. + * @param db Reference to the database. + * @return `true` if the contract exists in the database, `false` otherwise. */ template - bool loadFromDBHelper(const auto& contract, const Address& contractAddress, std::index_sequence) { - return (loadFromDBT>(contract, contractAddress) || ...); + bool loadFromDBHelper(const auto& contract, const Address& contractAddress, const DB& db, BlockObservers& observer, std::index_sequence) { + return (loadFromDBT>(contract, contractAddress, db, observer) || ...); } /** @@ -118,16 +83,25 @@ class ContractManager : public BaseContract { * @tparam Tuple The tuple of contracts to load. * @param contract The contract to load. * @param contractAddress The address of the contract. - * @return True if the contract exists in the database, false otherwise. + * @param db Reference to the database. + * @return `true` if the contract exists in the database, `false` otherwise. */ template - bool loadFromDBT(const auto& contract, const Address& contractAddress) { + bool loadFromDBT(const auto& contract, const Address& contractAddress, const DB& db, BlockObservers& observer) { // Here we disable this template when T is a tuple static_assert(!Utils::is_tuple::value, "Must not be a tuple"); - if (Utils::bytesToString(contract.value) == Utils::getRealTypeName()) { - this->contracts_.insert(std::make_pair( - contractAddress, std::make_unique(*this->interface_, contractAddress, this->db_) + if (StrConv::bytesToString(contract.value) == Utils::getRealTypeName()) { + auto it = this->contracts_.insert(std::make_pair( + contractAddress, std::make_unique(contractAddress, db) )); + for (const auto& blockObserver : it.first->second->getBlockNumberObservers()) { + observer.add(blockObserver); + } + for (const auto& timestampObserver : it.first->second->getBlockTimestampObservers()) { + observer.add(timestampObserver); + } + // Dont forget to register the contract on the dump manager + this->manager_.pushBack(it.first->second.get()); return true; } return false; @@ -138,370 +112,67 @@ class ContractManager : public BaseContract { * @tparam Tuple The tuple of contracts to load. * @param contract The contract to load. * @param contractAddress The address of the contract. - * @return True if the contract exists in the database, false otherwise. + * @param db Reference to the database. + * @return `true` if the contract exists in the database, `false` otherwise. */ - template - std::enable_if_t::value, bool> loadFromDB( - const auto& contract, const Address& contractAddress + template requires Utils::is_tuple::value bool loadFromDB( + const auto& contract, const Address& contractAddress, const DB& db, BlockObservers& observer ) { return loadFromDBHelper( - contract, contractAddress, std::make_index_sequence::value>{} + contract, contractAddress, db, observer, std::make_index_sequence::value>{} ); } public: /** * Constructor. Automatically loads contracts from the database and deploys them. - * @param db Pointer to the database. - * @param state Raw pointer to the state. - * @param rdpos Pointer to the rdPoS contract. - * @param options Pointer to the options singleton. + * @param db Reference to the database. + * @param contracts Reference to the contracts map. + * @param manager Reference to the database dumping manager. + * @param options Reference to the options singleton. + * @throw DynamicException if contract address doesn't exist in the database. */ - ContractManager( - const std::unique_ptr& db, State* state, - const std::unique_ptr& rdpos, const std::unique_ptr& options - ); + ContractManager(const DB& db, + boost::unordered_flat_map, SafeHash, SafeCompare>& contracts, + DumpManager& manager, + BlockObservers& observer, + const Options& options); - /// Destructor. Automatically saves contracts to the database before wiping them. - ~ContractManager() override; + ~ContractManager() override; ///< Destructor. Automatically saves contracts to the database before wiping them. /** * Override the default contract function call. - * ContractManager processes things in a non-standard way (you cannot use - * SafeVariables as contract creation actively writes to DB). + * ContractManager processes things in a non-standard way + * (you cannot use SafeVariables as contract creation actively writes to DB). * @param callInfo The call info to process. - * @throw runtime_error if the call is not valid. + * @param host Pointer to the contract host. + * @throw DynamicException if the call is not valid. */ - void ethCall(const ethCallInfo& callInfo) override; + void ethCall(const evmc_message& callInfo, ContractHost* host) override; /** - * Override the default contract view function call. - * ContractManager process things in a non-standard way (you cannot use - * SafeVariables as contract creation actively writes to DB). - * @param data The call info to process. - * @return A string with the requested info. - * @throw std::runtime_error if the call is not valid. - */ - const Bytes ethCallView(const ethCallInfo& data) const override; - - /** - * Process a transaction that calls a function from a given contract. - * @param tx The transaction to process. - * @param blockHash The hash of the block that called the contract. Defaults to an empty hash. - * @param txIndex The index of the transaction inside the block that called the contract. Defaults to the first position. - * @throw std::runtime_error if the call to the ethCall function fails. - * TODO: it would be a good idea to revise tests that call this function, default values here only exist as a placeholder - */ - void callContract(const TxBlock& tx, const Hash& blockHash = Hash(), const uint64_t& txIndex = 0); - - /** - * Make an eth_call to a view function from the contract. Used by RPC. + * Override the default contract function call. + * ContractManager processes things in a non-standard way + * (you cannot use SafeVariables as contract creation actively writes to DB). * @param callInfo The call info to process. - * @return A string with the requested info. - * @throw std::runtime_error if the call to the ethCall function fails - * or if the contract does not exist. - */ - const Bytes callContract(const ethCallInfo& callInfo) const; - - /** - * Check if an ethCallInfo is trying to access a payable function. - * @param callInfo The call info to check. - * @return `true` if the function is payable, `false` otherwise. - */ - bool isPayable(const ethCallInfo& callInfo) const; - - /** - * Validate a transaction that calls a function from a given contract. - * @param callInfo The call info to validate. - * @return `true` if the transaction is valid, `false` otherwise. - * @throw std::runtime_error if the validation fails. - */ - bool validateCallContractWithTx(const ethCallInfo& callInfo); - - /** - * Check if a transaction calls a contract - * @param tx The transaction to check. - * @return `true` if the transaction calls a contract, `false` otherwise. - */ - bool isContractCall(const TxBlock& tx) const; - - /** - * Check if an address is a contract. - * @param address The address to check. - * @return `true` if the address is a contract, `false` otherwise. - */ - bool isContractAddress(const Address& address) const; - - /// Get a list of contract names and addresses. - std::vector> getContracts() const; - - /** - * Get all the events emitted under the given inputs. - * Parameters are defined when calling "eth_getLogs" on an HTTP request - * (directly from the http/jsonrpc submodules, through handle_request() on httpparser). - * They're supposed to be all "optional" at that point, but here they're - * all required, even if all of them turn out to be empty. - * @param fromBlock The initial block height to look for. - * @param toBlock The final block height to look for. - * @param address The address to look for. Defaults to empty (look for all available addresses). - * @param topics The topics to filter by. Defaults to empty (look for all available topics). - * @return A list of matching events. - */ - const std::vector getEvents( - const uint64_t& fromBlock, const uint64_t& toBlock, - const Address& address = Address(), const std::vector& topics = {} - ) const; - - /** - * Overload of getEvents() for transaction receipts. - * @param txHash The hash of the transaction to look for events. - * @param blockIndex The height of the block to look for events. - * @param txIndex The index of the transaction to look for events. - * @return A list of matching events. - */ - const std::vector getEvents( - const Hash& txHash, const uint64_t& blockIndex, const uint64_t& txIndex - ) const; - - /** - * Update the ContractGlobals variables - * Used by the State (when processing a block) to update the variables. - * Also used by the tests. - * CM does NOT update the variables by itself. - * @param coinbase The coinbase address. - * @param blockHash The hash of the block. - * @param blockHeight The height of the block. - * @param blockTimestamp The timestamp of the block. - */ - void updateContractGlobals( - const Address& coinbase, const Hash& blockHash, - const uint64_t& blockHeight, const uint64_t& blockTimestamp - ) const; - - /// ContractManagerInterface is a friend so it can access private members. - friend class ContractManagerInterface; - - /// ContractFactory is a friend so it can access private members. - friend class ContractFactory; - - /// ContractCallLogger is a friend so it can access private members. - friend class ContractCallLogger; -}; - -/// Interface class for DynamicContract to access ContractManager and interact with other dynamic contracts. -class ContractManagerInterface { - private: - ContractManager& manager_; ///< Reference to the contract manager. - - public: - /** - * Constructor. - * @param manager Reference to the contract manager. - */ - explicit ContractManagerInterface(ContractManager& manager): manager_(manager) {} - - /** - * Register a variable that was used a given contract. - * @param variable Reference to the variable. + * @return The bytes from the call + * @throw DynamicException if the call is not valid. */ - void registerVariableUse(SafeBase& variable); - - /// Populate a given address with its balance from the State. - void populateBalance(const Address& address) const; + Bytes evmEthCall(const evmc_message& callInfo, ContractHost* host) override; /** - * Call a contract function. Used by DynamicContract to call other contracts. - * A given DynamicContract will only call another contract if triggered by a transaction. - * This will only be called if callContract() or validateCallContractWithTx() was called before. - * @tparam R The return type of the function. - * @tparam C The contract type. - * @tparam Args The arguments types. - * @param txOrigin The address of the originator of the transaction. - * @param fromAddr The address of the caller. - * @param targetAddr The address of the contract to call. - * @param value Flag to indicate if the function is payable., - * @param func The function to call. - * @param args The arguments to pass to the function. - * @return The return value of the function. - */ - template R callContractFunction( - const Address& txOrigin, const Address& fromAddr, const Address& targetAddr, - const uint256_t& value, - R(C::*func)(const Args&...), const Args&... args - ) { - if (!this->manager_.callLogger_) throw std::runtime_error( - "Contracts going haywire! Trying to call ContractState without an active callContract" - ); - if (value) { - this->sendTokens(fromAddr, targetAddr, value); - } - if (!this->manager_.contracts_.contains(targetAddr)) { - throw std::runtime_error(std::string(__func__) + ": Contract does not exist - Type: " - + Utils::getRealTypeName() + " at address: " + targetAddr.hex().get() - ); - } - C* contract = this->getContract(targetAddr); - this->manager_.callLogger_->setContractVars(contract, txOrigin, fromAddr, value); - try { - return contract->callContractFunction(func, args...); - } catch (const std::exception& e) { - throw std::runtime_error(e.what() + std::string(" - Type: ") - + Utils::getRealTypeName() + " at address: " + targetAddr.hex().get() - ); - } - } - - /** - * Call a contract function with no arguments. Used by DynamicContract to call other contracts. - * A given DynamicContract will only call another contract if triggered by a transaction. - * This will only be called if callContract() or validateCallContractWithTx() was called before. - * @tparam R The return type of the function. - * @tparam C The contract type. - * @param txOrigin The address of the originator of the transaction. - * @param fromAddr The address of the caller. - * @param targetAddr The address of the contract to call. - * @param value Flag to indicate if the function is payable. - * @param func The function to call. - * @return The return value of the function. - */ - template R callContractFunction( - const Address& txOrigin, const Address& fromAddr, const Address& targetAddr, - const uint256_t& value, R(C::*func)() - ) { - if (!this->manager_.callLogger_) throw std::runtime_error( - "Contracts going haywire! Trying to call ContractState without an active callContract" - ); - if (value) this->sendTokens(fromAddr, targetAddr, value); - if (!this->manager_.contracts_.contains(targetAddr)) { - throw std::runtime_error(std::string(__func__) + ": Contract does not exist"); - } - C* contract = this->getContract(targetAddr); - this->manager_.callLogger_->setContractVars(contract, txOrigin, fromAddr, value); - try { - return contract->callContractFunction(func); - } catch (const std::exception& e) { - throw std::runtime_error(e.what()); - } - } - - /** - * Call the createNewContract function of a contract. - * Used by DynamicContract to create new contracts. - * @tparam TContract The contract type. - * @param txOrigin The address of the originator of the transaction. - * @param fromAddr The address of the caller. - * @param gasValue Caller gas limit. - * @param gasPriceValue Caller gas price. - * @param callValue The caller value. - * @param encoder The ABI encoder. - * @return The address of the new contract. - */ - template Address callCreateContract( - const Address& txOrigin, const Address &fromAddr, const uint256_t &gasValue, - const uint256_t &gasPriceValue, const uint256_t &callValue, - const Bytes &encoder - ) { - if (!this->manager_.callLogger_) throw std::runtime_error( - "Contracts going haywire! Trying to call ContractState without an active callContract" - ); - ethCallInfo callInfo; - std::string createSignature = "createNew" + Utils::getRealTypeName() + "Contract("; - // Append args - createSignature += ContractReflectionInterface::getConstructorArgumentTypesString(); - createSignature += ")"; - auto& [from, to, gas, gasPrice, value, functor, data] = callInfo; - from = fromAddr; - to = this->manager_.getContractAddress(); - gas = gasValue; - gasPrice = gasPriceValue; - value = callValue; - functor = Utils::sha3(Utils::create_view_span(createSignature)).view_const(0, 4); - data = encoder; - this->manager_.callLogger_->setContractVars(&manager_, txOrigin, fromAddr, value); - Address newContractAddress = this->manager_.deriveContractAddress(); - this->manager_.ethCall(callInfo); - return newContractAddress; - } - - /** - * Get a contract by its address. - * Used by DynamicContract to access view/const functions of other contracts. - * @tparam T The contract type. - * @param address The address of the contract. - * @return A pointer to the contract. - * @throw runtime_error if contract is not found or not of the requested type. - */ - template const T* getContract(const Address &address) const { - auto it = this->manager_.contracts_.find(address); - if (it == this->manager_.contracts_.end()) throw std::runtime_error( - "ContractManager::getContract: contract at address " + - address.hex().get() + " not found." - ); - auto ptr = dynamic_cast(it->second.get()); - if (ptr == nullptr) throw std::runtime_error( - "ContractManager::getContract: Contract at address " + - address.hex().get() + " is not of the requested type: " + Utils::getRealTypeName() - ); - return ptr; - } - - /** - * Get a contract by its address (non-const). - * Used by DynamicContract to access view/const functions of other contracts. - * @tparam T The contract type. - * @param address The address of the contract. - * @return A pointer to the contract. - * @throw runtime_error if contract is not found or not of the requested type. - */ - template T* getContract(const Address& address) { - auto it = this->manager_.contracts_.find(address); - if (it == this->manager_.contracts_.end()) throw std::runtime_error( - "ContractManager::getContract: contract at address " + - address.hex().get() + " not found." - ); - auto ptr = dynamic_cast(it->second.get()); - if (ptr == nullptr) throw std::runtime_error( - "ContractManager::getContract: Contract at address " + - address.hex().get() + " is not of the requested type: " + Utils::getRealTypeName() - ); - return ptr; - } - - /** - * Emit an event from a contract. Called by DynamicContract's emitEvent(). - * @param event The event to emit. - * @throw std::runtime_error if there's an attempt to emit the event outside a contract call. - */ - void emitContractEvent(Event& event) { - // Sanity check - events should only be emitted during successful contract - // calls AND on non-pure/non-view functions. Since callLogger on view - // function calls is set to nullptr, this ensures that events only happen - // inside contracts and are not emitted if a transaction reverts. - // C++ itself already takes care of events not being emitted on pure/view - // functions due to its built-in const-correctness logic. - // TODO: check later if events are really not emitted on transaction revert - if (!this->manager_.callLogger_) throw std::runtime_error( - "Contracts going haywire! Trying to emit an event without an active contract call" - ); - this->manager_.eventManager_->registerEvent(std::move(event)); - } - - /** - * Get the balance from a given address. Calls populateBalance(), so - * it's technically the same as getting the balance directly from State. - * Does NOT consider the current transaction being processed, if there is one. - * @param address The address to get the balance from. - * @return The balance of the address. + * Override the default contract view function call. + * ContractManager process things in a non-standard way + * (you cannot use SafeVariables as contract creation actively writes to DB). + * @param data The call info to process. + * @param host Pointer to the contract host. + * @return A string with the requested info. + * @throw DynamicException if the call is not valid. */ - uint256_t getBalanceFromAddress(const Address& address) const; + Bytes ethCallView(const evmc_message& data, ContractHost* host) const override; - /** - * Send tokens to a given address. Used by DynamicContract to send tokens to other contracts. - * @param from The address from which the tokens will be sent from. - * @param to The address to send the tokens to. - * @param amount The amount of tokens to send. - */ - void sendTokens(const Address& from, const Address& to, const uint256_t& amount); + /// Dump override + DBBatch dump() const override; }; #endif // CONTRACTMANAGER_H diff --git a/src/contract/contractstack.cpp b/src/contract/contractstack.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/contract/contractstack.h b/src/contract/contractstack.h new file mode 100644 index 00000000..d3a7b3aa --- /dev/null +++ b/src/contract/contractstack.h @@ -0,0 +1,32 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef STACKTRACE_H +#define STACKTRACE_H + +// leave it in to avoid "AddressSanitizer unknown-crash" runtime errors +#include "contract.h" // core/dump.h -> utils/db.h -> utils.h -> strings.h + +/** + * ContractStack is a class/object required to initialize a sequence of contract executions (1 tx == 1 contract stack). + * The ContractStack have the following responsabilities: + * - Store original values of state variables (e.g. balance, code, nonce, evm storage, used C++ variables) etc + */ +class ContractStack { + private: + std::vector> usedVars_; + + public: + inline void registerVariableUse(SafeBase& var) { + this->usedVars_.emplace_back(var); + } + + inline const std::vector>& getUsedVars() const { return this->usedVars_; } + ///@} +}; + +#endif // STACKTRACE_H diff --git a/src/contract/costs.h b/src/contract/costs.h new file mode 100644 index 00000000..5ec4601b --- /dev/null +++ b/src/contract/costs.h @@ -0,0 +1,12 @@ +#ifndef BDK_CONTRACT_COSTS_H +#define BDK_CONTRACT_COSTS_H + +#include + +constexpr uint64_t CONTRACT_EXECUTION_COST = 21'000; +constexpr uint64_t EVM_CONTRACT_CREATION_COST = 100'000; +constexpr uint64_t CPP_CONTRACT_CREATION_COST = 50'000; +constexpr uint64_t CPP_CONTRACT_CALL_COST = 1'000; +constexpr uint64_t EVM_CONTRACT_CALL_COST = 5'000; // NOT USED DUE TO NOT BEING COMPATIBLE WITH ETHEREUM STANDARD (YELLOW PAPER) + +#endif // BDK_CONTRACT_COSTS_H diff --git a/src/contract/cppcontractexecutor.h b/src/contract/cppcontractexecutor.h new file mode 100644 index 00000000..6378dd43 --- /dev/null +++ b/src/contract/cppcontractexecutor.h @@ -0,0 +1,212 @@ +#ifndef BDK_MESSAGES_CPPCONTRACTEXECUTOR_H +#define BDK_MESSAGES_CPPCONTRACTEXECUTOR_H + +#include + +#include "executioncontext.h" +#include "traits.h" +#include "bytes/cast.h" +#include "utils/evmcconv.h" +#include "utils/contractreflectioninterface.h" +#include "contract/costs.h" + +struct ContractHost; + +class CppContractExecutor { +public: + CppContractExecutor(ExecutionContext& context, ContractHost& host) + : context_(context), host_(host) {} + + template + auto execute(M&& msg) -> traits::MessageResult { + if constexpr (concepts::DelegateCallMessage) { + throw DynamicException("Delegate call not supported for C++ contracts"); + } + + auto guard = transactional::checkpoint(currentGas_); + currentGas_ = &msg.gas(); + + if constexpr (concepts::CreateMessage) { + return createContract(std::forward(msg)); + } else { + return callContract(std::forward(msg)); + } + } + + Gas& currentGas() { return *currentGas_; } + +private: + decltype(auto) callContract(concepts::PackedMessage auto&& msg) { + msg.gas().use(CPP_CONTRACT_CALL_COST); + auto& contract = getContractAs>(msg.to()); + + transactional::Group guard = { + transactional::checkpoint(contract.caller_), + transactional::checkpoint(contract.value_) + }; + + const Address caller(msg.from()); + const uint256_t value = messageValueOrZero(msg); + + contract.caller_ = caller; + contract.value_ = value; + + return std::apply([&] (auto&&... args) { + if constexpr (concepts::StaticCallMessage) { + return std::invoke(msg.method(), contract, std::forward(args)...); + } else { + return contract.callContractFunction(&host_, msg.method(), std::forward(args)...); + } + }, std::forward(msg).args()); + } + + decltype(auto) callContract(concepts::EncodedMessage auto&& msg) { + if (msg.to() == ProtocolContractAddresses.at("ContractManager")) [[unlikely]] { + msg.gas().use(CPP_CONTRACT_CREATION_COST); + } else [[likely]] { + msg.gas().use(CPP_CONTRACT_CALL_COST); + } + + const evmc_message evmcMsg{ + .kind = EVMC_CALL, + .flags = (concepts::StaticCallMessage ? EVMC_STATIC : evmc_flags(0)), + .depth = 0, + .gas = int64_t(msg.gas()), + .recipient = bytes::cast(msg.to()), + .sender = bytes::cast(msg.from()), + .input_data = msg.input().data(), + .input_size = msg.input().size(), + .value = evmc_uint256be{}, + .create2_salt = evmc_bytes32{}, + .code_address = bytes::cast(msg.to()) + }; + + auto& contract = context_.getContract(msg.to()); + + transactional::Group guard = { + transactional::checkpoint(contract.caller_), + transactional::checkpoint(contract.value_) + }; + + Address caller(msg.from()); + uint256_t value = messageValueOrZero(msg); + + contract.caller_ = caller; + contract.value_ = value; + try { + return contract.evmEthCall(evmcMsg, &host_); + } catch (VMExecutionError &e) { + // Just rethrow the error, it will be handled by the caller + throw; + } catch (std::exception &e) { + std::string errorMessage = std::string("C++ Contract execution failed with reason \"") + e.what() + "\"" + + " with from: " + Address(msg.from()).hex(true).get(); + if constexpr(concepts::HasToField) { + errorMessage += " with to: " + Address(msg.to()).hex(true).get(); + } + if constexpr(concepts::HasInputField) { + errorMessage += " and input: " + Hex::fromBytes(View(msg.input()), true).get(); + } + throw VMExecutionError({ + -32000, + errorMessage, + (e.what() ? ABI::Encoder::encodeError(e.what()) : Bytes()) + }); + } + } + + Address createContract(concepts::CreateMessage auto&& msg) { + msg.gas().use(CPP_CONTRACT_CREATION_COST); + + const std::string createSignature = "createNew" + + Utils::getRealTypeName>() + + "Contract(" + + ContractReflectionInterface::getConstructorArgumentTypesString>() + + ")"; + + // We only need the first 4 bytes for the function signature + Bytes fullData = Utils::makeBytes(Utils::sha3(View(bytes::view(createSignature))) | std::views::take(4)); + + std::apply(Utils::Overloaded{ + [] () {}, + [&fullData] (const auto&... args) { + Utils::appendBytes(fullData, ABI::Encoder::encodeData(args...)); + } + }, msg.args()); + + const Address& to = ProtocolContractAddresses.at("ContractManager"); + + const evmc_message evmcMsg{ + .kind = EVMC_CREATE, + .flags = 0, + .depth = 1, + .gas = int64_t(msg.gas()), + .recipient = bytes::cast(to), + .sender = bytes::cast(msg.from()), + .input_data = fullData.data(), + .input_size = fullData.size(), + .value = EVMCConv::uint256ToEvmcUint256(msg.value()), + .create2_salt{}, + .code_address = bytes::cast(to) + }; + + auto& contract = context_.getContract(to); + + transactional::Group guard = { + transactional::checkpoint(contract.caller_), + transactional::checkpoint(contract.value_) + }; + + Address caller(msg.from()); + uint256_t value = 0; + + contract.caller_ = caller; + contract.value_ = value; + + auto account = context_.getAccount(msg.from()); + const Address contractAddress = generateContractAddress(account.getNonce(), msg.from()); + + try { + auto returnBytes = contract.evmEthCall(evmcMsg, &host_); + } catch (VMExecutionError &e) { + // Just rethrow the error, it will be handled by the caller + throw; + } catch (std::exception &e) { + std::string errorMessage = std::string("C++ Contract execution failed with reason \"") + e.what() + "\"" + + " with from: " + Address(msg.from()).hex(true).get(); + if constexpr(concepts::HasToField) { + errorMessage += " with to: " + Address(msg.to()).hex(true).get(); + } + if constexpr(concepts::HasInputField) { + errorMessage += " and input: " + Hex::fromBytes(View(msg.input())).get(); + } + throw VMExecutionError({ + -32000, + errorMessage, + (e.what() ? ABI::Encoder::encodeError(e.what()) : Bytes()) + }); + } + if (account.getContractType() != ContractType::NOT_A_CONTRACT) { + account.setNonce(account.getNonce() + 1); + } + + return contractAddress; + } + + template + C& getContractAs(View
address) { + C* ptr = dynamic_cast(&context_.getContract(address)); + + if (ptr == nullptr) { + throw DynamicException("Wrong contract type"); // TODO: add more info + } + + return *ptr; + } + + ExecutionContext& context_; + ContractHost& host_; + Gas *currentGas_ = nullptr; +}; + +#endif // BDK_MESSAGES_CPPCONTRACTEXECUTOR_H diff --git a/src/contract/customcontracts.h b/src/contract/customcontracts.h index dc716246..cd5285a1 100644 --- a/src/contract/customcontracts.h +++ b/src/contract/customcontracts.h @@ -1,24 +1,50 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. */ -#include "templates/erc20.h" +#include "templates/standards/erc20.h" #include "templates/erc20wrapper.h" #include "templates/nativewrapper.h" #include "templates/simplecontract.h" -#include "templates/erc721.h" +#include "templates/standards/erc721.h" +#include "templates/standards/ierc721receiver.hpp" #include "templates/dexv2/dexv2pair.h" #include "templates/dexv2/dexv2factory.h" #include "templates/dexv2/dexv2router02.h" +#include "templates/orderbook/orderbook.h" #include "templates/throwtestA.h" #include "templates/throwtestB.h" #include "templates/throwtestC.h" +#include "templates/erc721test.h" +#include "templates/testThrowVars.h" +#include "templates/randomnesstest.h" +#include "templates/snailtracer.h" +#include "templates/snailtraceroptimized.h" +#include "templates/ownable.h" +#include "templates/pebble.h" +#include "templates/btvenergy.h" +#include "templates/btvplayer.h" +#include "templates/btvproposals.h" +#include "templates/buildthevoid.h" +#include "templates/mintableerc20.h" +using InterfaceTypes = std::tuple; + +/// Typedef for the blockchain's registered contracts. +#ifdef BUILD_TESTNET +/// Typedef for the blockchain's registered contracts in TESTNET mode. +using ContractTypes = std::tuple< + ERC20, NativeWrapper, DEXV2Pair, DEXV2Factory, DEXV2Router02, ERC721, ERC721URIStorage, Ownable, Pebble, BTVPlayer, BTVEnergy, BTVProposals, BuildTheVoid, ERC20Mintable +>; +#else +/// Typedef for the blockchain's registered contracts in normal mode. using ContractTypes = std::tuple< ERC20, ERC20Wrapper, NativeWrapper, SimpleContract, DEXV2Pair, DEXV2Factory, - DEXV2Router02, ERC721, ThrowTestA, ThrowTestB, ThrowTestC + DEXV2Router02, ERC721, ThrowTestA, ThrowTestB, ThrowTestC, ERC721Test, TestThrowVars, + RandomnessTest, SnailTracer, SnailTracerOptimized, Pebble, BTVPlayer, BTVEnergy, BTVProposals, BuildTheVoid, ERC20Mintable, OrderBook, Ownable >; +#endif diff --git a/src/contract/dynamiccontract.cpp b/src/contract/dynamiccontract.cpp index 8a77fbaf..ef63aeb6 100644 --- a/src/contract/dynamiccontract.cpp +++ b/src/contract/dynamiccontract.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. diff --git a/src/contract/dynamiccontract.h b/src/contract/dynamiccontract.h index b5c8fd45..fd6051b4 100644 --- a/src/contract/dynamiccontract.h +++ b/src/contract/dynamiccontract.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,68 +8,104 @@ See the LICENSE.txt file in the project root for more information. #ifndef DYNAMICCONTRACT_H #define DYNAMICCONTRACT_H -#include "abi.h" -#include "contract.h" -#include "contractmanager.h" -#include "event.h" -#include "../utils/safehash.h" -#include "../utils/utils.h" - -/** - * Template for a smart contract. - * All contracts have to inherit this class. - */ +#include "../utils/evmcconv.h" // getFunctor, getFunctionArgs + +#include "contracthost.h" // contractmanager.h -> contract.h, ...utils/utils.h,safehash.h +#include + + +/// Template for a smart contract. All contracts must inherit this class. class DynamicContract : public BaseContract { private: + /** + * Boolean that accompany a PointerNullifier to identify if it has been registered already + */ + bool nullifiable_ = true; + /** - * Map of non-payable functions that can be called by the contract. + * Set of non-payable functions that can be called by the contract. * The key is the function signature (first 4 hex bytes of keccak). - * The value is a function that takes a vector of bytes (the arguments) and returns a ReturnType. */ - std::unordered_map< - Functor, std::function, SafeHash - > publicFunctions_; + boost::unordered_flat_set< + Functor, SafeHash + > nonpayableFunctions_; /** - * Map of payable functions that can be called by the contract. + * Set of payable functions that can be called by the contract. * The key is the function signature (first 4 hex bytes of keccak). - * The value is a function that takes a vector of bytes (the arguments) and returns a ReturnType. */ - std::unordered_map< - Functor, std::function, SafeHash + boost::unordered_flat_set< + Functor, SafeHash > payableFunctions_; /** - * Map of view functions that can be called by the contract. + * Set of view functions that can be called by the contract. * The key is the function signature (first 4 hex bytes of keccak). - * The value is a function that takes a vector of bytes (the arguments) and returns a ReturnType. - * Function return type is the encoded return value as viewFunctions is only used by eth_call. */ - std::unordered_map< - Functor, std::function, SafeHash + boost::unordered_flat_set< + Functor, SafeHash > viewFunctions_; + /** + * Map for all the functions, regardless of mutability + * The reason for having all these functions in a single mapping is because + * EVM expects a contract call to actually return the ABI serialized data + * So this map is exactly for that, it is a second representation of all the functions + * stored in the previous 3 maps but with the ABI serialized data as the return value + * We still check if we are calling a payable if the evmc_message has value. + */ + boost::unordered_flat_map< + Functor, std::function, SafeHash + > evmFunctions_; + + /** + * Vector containing all the supported ERC-165 interfaces + * See more: https://eips.ethereum.org/EIPS/eip-165 + * ERC-165 Hashes are directly generated while registering contract functiosn + * by calling DynamicContract::registerMemberFunctions(). + * Where all the functors of the functions inside the arguments of registerMemberFunctions + * Are XOR'ed together to generate a single hash compatible with ERC-165. + * 0x01ffc9a7 is the ERC-165 interface ID for ERC-165 itself. + * 0x01ffc9a7 == 33540519 + */ + std::vector supportedInterfaces_ = { Functor(33540519) }; + + std::vector blockNumberObservers_; + + std::vector blockTimestampObservers_; + /** * Register a callable function (a function that is called by a transaction), * adding it to the callable functions map. * @param functor Solidity function signature (first 4 hex bytes of keccak). * @param f Function to be called. + * @param type The type of the function (payable, non-payable or view). */ void registerFunction( - const Functor& functor, const std::function& f + const Functor& functor, const std::function& f, const FunctionTypes& type ) { - publicFunctions_[functor] = f; + this->evmFunctions_[functor] = f; + if (type == FunctionTypes::NonPayable) { + this->nonpayableFunctions_.insert(functor); + } else if (type == FunctionTypes::Payable) { + this->payableFunctions_.insert(functor); + } else if (type == FunctionTypes::View) { + this->viewFunctions_.insert(functor); + } else { + throw DynamicException("Invalid function type for registerFunction."); + } } /** * Register a variable that was used by the contract. * @param variable Reference to the variable. */ - inline void registerVariableUse(SafeBase& variable) { interface_.registerVariableUse(variable); } - - protected: - /// Reference to the contract manager interface. - ContractManagerInterface& interface_; + inline void registerVariableUse(SafeBase& variable) const { + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to register variable use without a host_!"); + } + host_->registerVariableUse(variable); + } /** * Template for registering a const member function with no arguments. @@ -78,36 +114,23 @@ class DynamicContract : public BaseContract { * @param methodMutability The mutability of the function. * @param instance Pointer to the instance of the class. */ - template void registerMemberFunction( + template Functor registerMemberFunction( const std::string& funcSignature, R(T::*memFunc)() const, const FunctionTypes& methodMutability, T* instance ) { std::string functStr = funcSignature + "()"; - switch (methodMutability) { - case FunctionTypes::View: { - this->registerViewFunction(Utils::sha3(Utils::create_view_span(functStr)).view_const(0, 4), [instance, memFunc](const ethCallInfo &callInfo) -> Bytes { - using ReturnType = decltype((instance->*memFunc)()); - return ABI::Encoder::encodeData((instance->*memFunc)()); - }); - break; - } - case FunctionTypes::NonPayable: { - this->registerFunction(Utils::sha3(Utils::create_view_span(functStr)).view_const(0, 4), [instance, memFunc](const ethCallInfo &callInfo) -> void { - (instance->*memFunc)(); - return; - }); - break; - } - case FunctionTypes::Payable: { - this->registerPayableFunction(Utils::sha3(Utils::create_view_span(functStr)).view_const(0, 4), [instance, memFunc](const ethCallInfo &callInfo) -> void { - (instance->*memFunc)(); - return; - }); - break; - } - default: { - throw std::runtime_error("Invalid function signature."); + Functor functor = Utils::makeFunctor(functStr); + auto registerFunction = [this, instance, memFunc]([[maybe_unused]] const evmc_message& callInfo) -> Bytes { + if constexpr (std::is_same_v) { + // If the function's return type is void, return an empty Bytes object + (instance->*memFunc)(); // Call the member function without capturing its return + return Bytes(); // Return an empty Bytes object + } else { + // If the function's return type is not void, encode and return its result + return ABI::Encoder::encodeData((instance->*memFunc)()); } - } + }; + this->registerFunction(functor, registerFunction, methodMutability); + return functor; } /** @@ -117,38 +140,26 @@ class DynamicContract : public BaseContract { * @param methodMutability The mutability of the function. * @param instance Pointer to the instance of the class. */ - template void registerMemberFunction( + template Functor registerMemberFunction( const std::string& funcSignature, R(T::*memFunc)(), const FunctionTypes& methodMutability, T* instance ) { std::string functStr = funcSignature + "()"; - switch (methodMutability) { - case FunctionTypes::View: { - throw std::runtime_error("View must be const because it does not modify the state."); - } - case FunctionTypes::NonPayable: { - this->registerFunction( - Utils::sha3(Utils::create_view_span(functStr)).view_const(0, 4), - [instance, memFunc](const ethCallInfo &callInfo) -> void { - (instance->*memFunc)(); - return; - } - ); - break; - } - case FunctionTypes::Payable: { - this->registerPayableFunction( - Utils::sha3(Utils::create_view_span(functStr)).view_const(0, 4), - [instance, memFunc](const ethCallInfo &callInfo) -> void { - (instance->*memFunc)(); - return; - } - ); - break; - } - default: { - throw std::runtime_error("Invalid function signature."); + Functor functor = Utils::makeFunctor(functStr); + auto registerFunction = [this, instance, memFunc]([[maybe_unused]] const evmc_message& callInfo) -> Bytes { + if constexpr (std::is_same_v) { + // If the function's return type is void, return an empty Bytes object + (instance->*memFunc)(); // Call the member function without capturing its return + return Bytes(); // Return an empty Bytes object + } else { + // If the function's return type is not void, encode and return its result + return ABI::Encoder::encodeData((instance->*memFunc)()); } + }; + if (methodMutability == FunctionTypes::View) { + throw DynamicException("View must be const because it does not modify the state."); } + this->registerFunction(functor, registerFunction, methodMutability); + return functor; } /** @@ -158,29 +169,29 @@ class DynamicContract : public BaseContract { * @param methodMutability The mutability of the function. * @param instance Pointer to the instance of the class. */ - template void registerMemberFunction( + template Functor registerMemberFunction( const std::string& funcSignature, R(T::*memFunc)(Args...), const FunctionTypes& methodMutability, T* instance ) { Functor functor = ABI::FunctorEncoder::encode(funcSignature); - auto registrationFunc = [this, instance, memFunc, funcSignature](const ethCallInfo &callInfo) { + auto registrationFunc = [this, instance, memFunc](const evmc_message &callInfo) -> Bytes { using DecayedArgsTuple = std::tuple...>; - DecayedArgsTuple decodedData = ABI::Decoder::decodeData...>(std::get<6>(callInfo)); - std::apply([instance, memFunc](auto&&... args) { - (instance->*memFunc)(std::forward(args)...); - }, decodedData); + DecayedArgsTuple decodedData = ABI::Decoder::decodeData...>(EVMCConv::getFunctionArgs(callInfo)); + if constexpr (std::is_same_v) { + std::apply([instance, memFunc](auto&&... args) { + (instance->*memFunc)(std::forward(args)...); + }, decodedData); + return Bytes(); + } else { + return std::apply([instance, memFunc](auto&&... args) -> Bytes { + return ABI::Encoder::encodeData((instance->*memFunc)(std::forward(args)...)); + }, decodedData); + } }; - switch (methodMutability) { - case FunctionTypes::View: - throw std::runtime_error("View must be const because it does not modify the state."); - case FunctionTypes::NonPayable: - this->registerFunction(functor, registrationFunc); - break; - case FunctionTypes::Payable: - this->registerPayableFunction(functor, registrationFunc); - break; - default: - throw std::runtime_error("Invalid function signature."); + if (methodMutability == FunctionTypes::View) { + throw DynamicException("View must be const because it does not modify the state."); } + this->registerFunction(functor, registrationFunc, methodMutability); + return functor; } /** @@ -190,132 +201,205 @@ class DynamicContract : public BaseContract { * @param methodMutability The mutability of the function. * @param instance Pointer to the instance of the class. */ - template void registerMemberFunction( + template Functor registerMemberFunction( const std::string& funcSignature, R(T::*memFunc)(Args...) const, const FunctionTypes& methodMutability, T* instance ) { Functor functor = ABI::FunctorEncoder::encode(funcSignature); - auto registrationFunc = [this, instance, memFunc, funcSignature](const ethCallInfo &callInfo) -> Bytes { - using ReturnType = decltype((instance->*memFunc)(std::declval()...)); + auto registrationFunc = [this, instance, memFunc](const evmc_message &callInfo) -> Bytes { using DecayedArgsTuple = std::tuple...>; - DecayedArgsTuple decodedData = ABI::Decoder::decodeData...>(std::get<6>(callInfo)); - // Use std::apply to call the member function and encode its return value - return std::apply([instance, memFunc](Args... args) -> Bytes { - // Call the member function and return its encoded result - return ABI::Encoder::encodeData((instance->*memFunc)(std::forward(args)...)); - }, decodedData); + DecayedArgsTuple decodedData = ABI::Decoder::decodeData...>(EVMCConv::getFunctionArgs(callInfo)); + if constexpr (std::is_same_v) { + // If the function's return type is void, call the member function and return an empty Bytes object + std::apply([instance, memFunc](Args... args) { + (instance->*memFunc)(std::forward(args)...); + }, decodedData); + return Bytes(); // Return an empty Bytes object + } else { + // If the function's return type is not void, call the member function and return its encoded result + return std::apply([instance, memFunc](Args... args) -> Bytes { + if constexpr (std::is_same_v) { + // If the function's return type is void, call the member function and return an empty Bytes object + (instance->*memFunc)(std::forward(args)...); + return Bytes(); // Return an empty Bytes object + } else { + // If the function's return type is not void, call the member function and return its encoded result + return ABI::Encoder::encodeData((instance->*memFunc)(std::forward(args)...)); + } + }, decodedData); + } }; - switch (methodMutability) { - case FunctionTypes::View: - this->registerViewFunction(functor, registrationFunc); - break; - case FunctionTypes::NonPayable: - this->registerFunction(functor, registrationFunc); - break; - case FunctionTypes::Payable: - this->registerPayableFunction(functor, registrationFunc); - break; - } + this->registerFunction(functor, registrationFunc, methodMutability); + return functor; } + protected: /** - * Register a callable and payable function, adding it to the payable functions map. - * @param functor Solidity function signature (first 4 hex bytes of keccak). - * @param f Function to be called. + * Template for registering all functions of a class. + * @param methods The methods to register. */ - void registerPayableFunction( - const Functor& functor, const std::function& f - ) { - payableFunctions_[functor] = f; + template + void registerMemberFunctions(Methods&& ... methods) { + Functor xorFunctor; + // ERC-165 requires that all functions are XOR'ed together to create a single hash + (( + xorFunctor ^= this->registerMemberFunction(std::get<0>(methods), std::get<1>(methods), std::get<2>(methods), std::get<3>(methods)) + ), ...); + this->supportedInterfaces_.push_back(xorFunctor); } - /** - * Register a view/const function, adding it to the view functions map. - * @param functor Solidity function signature (first 4 hex bytes of keccak). - * @param f Function to be called. - */ - void registerViewFunction( - const Functor& functor, const std::function& f - ) { - viewFunctions_[functor] = f; + template + void registerBlockObserver(const std::string& funcName, uint64_t blockCount, void(C::*memFunc)(), C* instance) { + this->registerMemberFunction(funcName, memFunc, FunctionTypes::NonPayable, instance); + + if (blockCount == 0) { + throw DynamicException("Block count for block observer must be greater than 0"); + } + + auto callback = [memFunc, instance] (ContractHost& host) { + Gas gas(5'000'000); + constexpr uint256_t value = 0; + const Address contractAddress = instance->getContractAddress(); + + PackedCallMessage msg{ + contractAddress, + contractAddress, + gas, + value, + memFunc + }; + + host.execute(msg); + }; + + const auto currentBlockNumber = ContractGlobals::getBlockHeight(); + + blockNumberObservers_.emplace_back(callback, currentBlockNumber + blockCount, blockCount); + } + + template + void registerBlockObserver(const std::string& funcName, std::chrono::duration period, void(C::*memFunc)(), C* instance) { + this->registerMemberFunction(funcName, memFunc, FunctionTypes::NonPayable, instance); + + auto callback = [memFunc, instance] (ContractHost& host) { + Gas gas(5'000'000); + constexpr uint256_t value = 0; + const Address contractAddress = instance->getContractAddress(); + + PackedCallMessage msg{ + contractAddress, + contractAddress, + gas, + value, + memFunc + }; + + host.execute(msg); + }; + + const uint64_t periodInMicroseconds = std::chrono::duration_cast(period).count(); + + if (periodInMicroseconds == 0) { + throw DynamicException("Period for block observer must be at least 1 microsecond"); + } + + const auto currentBlockTimestamp = ContractGlobals::getBlockTimestamp(); + + blockTimestampObservers_.emplace_back(callback, currentBlockTimestamp + periodInMicroseconds, periodInMicroseconds); } /** * Template function for calling the register functions. * Should be called by the derived class. - * @throw std::runtime_error if the derived class does not override this. + * @throw DynamicException if the derived class does not override this. */ virtual void registerContractFunctions() { - throw std::runtime_error( - "Derived Class from Contract does not override registerContractFunctions()" - ); + throw DynamicException("Derived Class from Contract does not override registerContractFunctions()"); + } + + /** + * Forcely register a given Functor as a supported interface. + * @param functor The functor to register. + */ + void registerInterface(const Functor& functor) { + this->supportedInterfaces_.push_back(functor); + } + + /** + * receive() is a special function when there is ether + no function signature + * it MUST be overriden by the derived class. + * @param callInfo The evmc_message containing the call information. + */ + virtual void receive(const evmc_message& callInfo) { + // Receive does not exists, so we call fallback... + this->fallback(callInfo); + } + + /** + * fallback() is a special function when there is no function signature. + * it may return or not return anything, thus Bytes (can return empty Bytes) + */ + virtual Bytes fallback(const evmc_message& callInfo) { + Functor funcName = EVMCConv::getFunctor(callInfo); + throw DynamicException("Function not found for evmEthCall: " + funcName.hex().get() + " and fallback OR receive is not implemented for this contract"); } public: /** * Constructor for creating the contract from scratch. - * @param interface Reference to the contract manager interface. * @param contractName The name of the contract. * @param address The address where the contract will be deployed. * @param creator The address of the creator of the contract. * @param chainId The chain where the contract wil be deployed. - * @param db Reference to the database object. */ - DynamicContract( - ContractManagerInterface& interface, - const std::string& contractName, const Address& address, - const Address& creator, const uint64_t& chainId, - const std::unique_ptr& db - ) : BaseContract(contractName, address, creator, chainId, db), interface_(interface) {} + DynamicContract(const std::string& contractName, + const Address& address, const Address& creator, const uint64_t& chainId + ) : BaseContract(contractName, address, creator, chainId) { + // We need to create the ERC-165 function and register it accordingly + this->registerMemberFunction("supportsInterface", &DynamicContract::supportsInterface, FunctionTypes::View, this); + } /** * Constructor for loading the contract from the database. - * @param interface Reference to the contract manager interface. * @param address The address where the contract will be deployed. * @param db Reference to the database object. */ - DynamicContract( - ContractManagerInterface& interface, - const Address& address, const std::unique_ptr& db - ) : BaseContract(address, db), interface_(interface) {} + DynamicContract(const Address& address, const DB& db) : BaseContract(address, db) { + this->registerMemberFunction("supportsInterface", &DynamicContract::supportsInterface, FunctionTypes::View, this); + }; - /** - * Invoke a contract function using a tuple of (from, to, gasLimit, gasPrice, value, data). - * Automatically differs between payable and non-payable functions. - * Used by State when calling `processNewBlock()/validateNewBlock()`. - * @param callInfo Tuple of (from, to, gasLimit, gasPrice, value, data). - * @throw std::runtime_error if the functor is not found or the function throws an exception. - */ - void ethCall(const ethCallInfo& callInfo) override { - try { - Functor funcName = std::get<5>(callInfo); - if (this->isPayableFunction(funcName)) { - auto func = this->payableFunctions_.find(funcName); - if (func == this->payableFunctions_.end()) throw std::runtime_error("Functor not found for payable function"); - func->second(callInfo); - } else { - auto func = this->publicFunctions_.find(funcName); - if (func == this->publicFunctions_.end()) throw std::runtime_error("Functor not found for non-payable function"); - func->second(callInfo); + Bytes evmEthCall(const evmc_message& callInfo, ContractHost* host) final { + // TODO: properly review the evmc_message to call the appropriate functions (fallback, receive, payable, nonpayable and finally view) + this->host_ = host; + PointerNullifier nullifier(this->host_, this->nullifiable_); + Functor funcName = EVMCConv::getFunctor(callInfo); + // Check if functor exists + auto funcIt = this->evmFunctions_.find(funcName); + if (funcIt == this->evmFunctions_.end()) { + if (callInfo.flags == EVMC_STATIC) { + throw DynamicException("Function not found for evmEthCall, cannot call fallback or receive in static context"); } - } catch (const std::exception& e) { - throw std::runtime_error(e.what()); + if (callInfo.input_size == 0) { + this->receive(callInfo); + // DynamicContract::receive will call this->fallback if derived class does not override it + return Bytes(); // the function is not found and the functor is zero, we call fallback + } + return this->fallback(callInfo); // DynamicContract::fallback will throw an exception if not overridden } - }; - - /** - * Do a contract call to a view function. - * @param data Tuple of (from, to, gasLimit, gasPrice, value, data). - * @return The result of the view function. - * @throw std::runtime_error if the functor is not found or the function throws an exception. - */ - const Bytes ethCallView(const ethCallInfo& data) const override { - try { - Functor funcName = std::get<5>(data); - auto func = this->viewFunctions_.find(funcName); - if (func == this->viewFunctions_.end()) throw std::runtime_error("Functor not found"); - return func->second(data); - } catch (std::exception& e) { - throw std::runtime_error(e.what()); + // Now we filter view, payable and non-payable functions + if (callInfo.flags == EVMC_STATIC) { + if (!this->viewFunctions_.contains(funcName)) { + throw DynamicException("Function not found for evmEthCall, cannot call non-view function in static context"); + } + return funcIt->second(callInfo); + } else { + // Delegate call is already treated by whoever calls evmEthCall, so only 0 flags exists + // If it is a nonpayable function, we must check if the value is zero + if (this->nonpayableFunctions_.contains(funcName)) { + if (!evmc::is_zero(callInfo.value)) { + throw DynamicException("Non-payable function called with value"); + } + } + return funcIt->second(callInfo); } } @@ -334,8 +418,10 @@ class DynamicContract : public BaseContract { const std::tuple...>& args = std::make_tuple(), bool anonymous = false ) const { - Event e(name, this->getContractAddress(), args, anonymous); - this->interface_.emitContractEvent(e); + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to emit a event without a host!"); + } + this->host_->emitEvent(name, this->getContractAddress(), args, anonymous); } /** @@ -347,27 +433,6 @@ class DynamicContract : public BaseContract { return this->payableFunctions_.contains(functor); } - /** - * Try to cast a contract to a specific type. - * NOTE: Only const functions can be called on the casted contract. - * @tparam T The type to cast to. - * @param address The address of the contract to cast. - * @return A pointer to the casted contract. - */ - template const T* getContract(const Address& address) const { - return interface_.getContract(address); - } - - /** - * Try to cast a contract to a specific type (non-const). - * @tparam T The type to cast to. - * @param address The address of the contract to cast. - * @return A pointer to the casted contract. - */ - template T* getContract(const Address& address) { - return interface_.getContract(address); - } - /** * Call a contract view function based on the basic requirements of a contract call. * @tparam R The return type of the view function. @@ -382,8 +447,10 @@ class DynamicContract : public BaseContract { R callContractViewFunction( const Address& address, R(C::*func)(const Args&...) const, const Args&... args ) const { - const C* contract = this->getContract(address); - return (contract->*func)(args...); + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to call a contract function without a host!"); + } + return this->host_->callContractViewFunction(this, address, func, args...); } /** @@ -395,9 +462,13 @@ class DynamicContract : public BaseContract { * @return The result of the view function. */ template - R callContractViewFunction(const Address& address, R(C::*func)() const) const { - const C* contract = this->getContract(address); - return (contract->*func)(); + R callContractViewFunction( + const Address& address, R(C::*func)() const + ) const { + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to call a contract function without a host!"); + } + return this->host_->callContractViewFunction(this, address, func); } /** @@ -413,13 +484,14 @@ class DynamicContract : public BaseContract { template R callContractFunction( const Address& targetAddr, R(C::*func)(const Args&...), const Args&... args ) { - return this->interface_.callContractFunction( - this->getOrigin(), this->getContractAddress(), targetAddr, 0, func, args... - ); + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to call a contract function without a host!"); + } + return this->host_->callContractFunction(this, targetAddr, 0, func, args...); } /** - * Call a contract function (non-view) based on the basic requirements of a contract call with the value flag + * Call a contract function (non-view) based on the basic requirements of a contract call with the value flag. * @tparam R The return type of the function. * @tparam C The contract type. * @tparam Args The argument types of the function. @@ -432,29 +504,29 @@ class DynamicContract : public BaseContract { template R callContractFunction( const uint256_t& value, const Address& address, R(C::*func)(const Args&...), const Args&... args ) { - return this->interface_.callContractFunction( - this->getOrigin(), this->getContractAddress(), address, value, func, args... - ); + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to call a contract function without a host!"); + } + return this->host_->callContractFunction(this, address, value, func, args...); } /** - * Call a contract function (non-view) based on the basic requirements of a contract call with no arguments + * Call a contract function (non-view) based on the basic requirements of a contract call with no arguments. * @tparam R The return type of the function. * @tparam C The contract type. * @param targetAddr The address of the contract to call. * @param func The function to call. * @return The result of the function. */ - template R callContractFunction( - const Address& targetAddr, R(C::*func)() - ) { - return this->interface_.callContractFunction( - this->getOrigin(), this->getContractAddress(), targetAddr, 0, func - ); + template R callContractFunction(const Address& targetAddr, R(C::*func)()) { + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to call a contract function without a host!"); + } + return this->host_->callContractFunction(this, targetAddr, 0, func); } /** - * Call a contract function (non-view) based on the basic requirements of a contract call with the value flag and no arguments + * Call a contract function (non-view) based on the basic requirements of a contract call with the value flag and no arguments. * @tparam R The return type of the function. * @tparam C The contract type. * @param value Flag to send value with the call. @@ -465,9 +537,10 @@ class DynamicContract : public BaseContract { template R callContractFunction( const uint256_t& value, const Address& address, R(C::*func)() ) { - return this->interface_.callContractFunction( - this->getOrigin(), this->getContractAddress(), address, value, func - ); + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to create a contract without a host!"); + } + return this->host_->callContractFunction(this, address, value, func); } /** @@ -475,33 +548,43 @@ class DynamicContract : public BaseContract { * @tparam R The return type of the function. * @tparam C The contract type. * @tparam Args The argument types of the function. + * @param contractHost Pointer to the contract host. * @param func The function to call. * @param args The arguments to pass to the function. * @return The result of the function. */ template R callContractFunction( - R (C::*func)(const Args&...), const Args&... args + ContractHost* contractHost, R (C::*func)(const Args&...), const Args&... args ) { - try { - return (static_cast(this)->*func)(args...); - } catch (const std::exception& e) { - throw std::runtime_error(e.what()); + // We don't want to ever overwrite the host_ pointer if it's already set (nested calls) + PointerNullifier nullifier(this->host_, this->nullifiable_); + + if (this->host_ == nullptr) { + this->host_ = contractHost; } + + return (dynamic_cast(this)->*func)(args...); } /** - * Wrapper for calling a contract function (non-view) based on the basic requirements of a contract call with no arguments + * Wrapper for calling a contract function (non-view) based on the basic requirements of a contract call with no arguments. * @tparam R The return type of the function. * @tparam C The contract type. - * @tparam Args The argument types of the function. + * @param contractHost Pointer to the contract host. * @param func The function to call. * @return The result of the function. */ - template R callContractFunction(R (C::*func)()) { + template R callContractFunction(ContractHost* contractHost, R (C::*func)()) { try { - return (static_cast(this)->*func)(); + // We don't want to ever overwrite the host_ pointer if it's already set (nested calls) + if (this->host_ == nullptr) { + this->host_ = contractHost; + PointerNullifier nullifier(this->host_, this->nullifiable_); + return (dynamic_cast(this)->*func)(); + } + return (dynamic_cast(this)->*func)(); } catch (const std::exception& e) { - throw std::runtime_error(e.what()); + throw DynamicException(e.what()); } } @@ -509,25 +592,14 @@ class DynamicContract : public BaseContract { * Call the create function of a contract. * @tparam TContract The contract type. * @tparam Args The arguments of the contract constructor. - * @param gas The gas limit. - * @param gasPrice The gas price. - * @param value The caller value. * @param args The arguments to pass to the constructor. * @return The address of the created contract. */ - template Address callCreateContract( - const uint256_t& gas, const uint256_t& gasPrice, const uint256_t& value, Args&&... args - ) { - Utils::safePrint("CallCreateContract being called..."); - Bytes encoder; - if constexpr (sizeof...(Args) > 0) { - encoder = ABI::Encoder::encodeData(std::forward(args)...); - } else { - encoder = Bytes(32, 0); + template Address callCreateContract(Args&&... args) { + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to create a contract without a host!"); } - return this->interface_.callCreateContract( - this->getOrigin(), this->getContractAddress(), gas, gasPrice, value, std::move(encoder) - ); + return this->host_->callCreateContract(*this, std::forward(args)...); } /** @@ -536,7 +608,10 @@ class DynamicContract : public BaseContract { * @return The balance of the contract. */ uint256_t getBalance(const Address& address) const { - return interface_.getBalanceFromAddress(address); + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to get balance without a host!"); + } + return host_->context().getAccount(address).getBalance(); } /** @@ -545,7 +620,71 @@ class DynamicContract : public BaseContract { * @param amount The amount of tokens to send. */ void sendTokens(const Address& to, const uint256_t& amount) { - interface_.sendTokens(this->getContractAddress(), to, amount); + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to send tokens without a host!"); + } + host_->context().transferBalance(this->getContractAddress(), to, amount); + } + + /** + * Get the cryptographically secure random number. + * @return The random number. + */ + uint256_t getRandom() const { + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to get random number without a host!"); + } + return host_->getRandomValue(); + } + + /** + * Check if the address is a existing contract. + * @return + */ + bool isContract(const Address& address) const { + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to check if address is a contract without a host!"); + } + return host_->context().getAccount(address).getContractType() != ContractType::NOT_A_CONTRACT; + } + + /** + * Implementation for the ERC-165 compliance. + * See more at: https://eips.ethereum.org/EIPS/eip-165 + * @param interfaceId The interface ID to check. + * @return `true` if the contract supports the interface, `false` otherwise. + */ + bool supportsInterface(const Bytes4& interfaceId) const { + Functor interfaceFunctor(UintConv::bytesToUint32(interfaceId)); + return std::ranges::any_of(supportedInterfaces_.begin(), supportedInterfaces_.end(), + [&interfaceFunctor](const Functor& functor) { return functor == interfaceFunctor; }); + } + + + std::span getBlockNumberObservers() const override { + return blockNumberObservers_; + } + + std::span getBlockTimestampObservers() const override { + return blockTimestampObservers_; + } + + /** + * Helper function used by + * This function is utilized to be able to inject a extra function (ERC-165, supportsInterface) + * Into the ABI generator. + * @tparam TContract The contract type. + * @tparam Methods Type to allow template deduction for the methods. + * @param ctorArgs The constructor arguments string names. + * @param methods The methods to register. + */ + template + static void inline registerContractMethods(const std::vector& ctorArgs, Methods&&... methods) { + ContractReflectionInterface::registerContractMethods< + TContract>(ctorArgs, + std::make_tuple("supportsInterface", &TContract::supportsInterface, FunctionTypes::View, std::vector{"interfaceId"}), + methods... + ); } /** diff --git a/src/contract/encodedmessages.h b/src/contract/encodedmessages.h new file mode 100644 index 00000000..a3a3ed86 --- /dev/null +++ b/src/contract/encodedmessages.h @@ -0,0 +1,45 @@ +#ifndef BDK_ENCODEDMESSAGES_H +#define BDK_ENCODEDMESSAGES_H + +#include "contract/concepts.h" +#include "contract/basemessage.h" + +struct EncodedCallMessage : BaseMessage { + using BaseMessage::BaseMessage; +}; + +struct EncodedStaticCallMessage : BaseMessage { + using BaseMessage::BaseMessage; +}; + +struct EncodedCreateMessage : BaseMessage { + using BaseMessage::BaseMessage; +}; + +struct EncodedSaltCreateMessage : BaseMessage { + using BaseMessage::BaseMessage; +}; + +struct EncodedDelegateCallMessage : BaseMessage { + using BaseMessage::BaseMessage; +}; + +struct EncodedCallCodeMessage : EncodedCallMessage { + using EncodedCallMessage::EncodedCallMessage; +}; + +template<> +constexpr bool concepts::EnableDelegate = true; + +template<> +constexpr bool concepts::EnableCallCode = true; + +using EncodedMessageVariant = std::variant< + EncodedCreateMessage, + EncodedSaltCreateMessage, + EncodedCallMessage, + EncodedStaticCallMessage, + EncodedDelegateCallMessage +>; + +#endif // BDK_ENCODEDMESSAGES_H diff --git a/src/contract/event.cpp b/src/contract/event.cpp index 09232167..475ba558 100644 --- a/src/contract/event.cpp +++ b/src/contract/event.cpp @@ -1,31 +1,32 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. */ #include "event.h" +#include "bytes/hex.h" + +#include "../utils/uintconv.h" Event::Event(const std::string& jsonstr) { json obj = json::parse(jsonstr); - this->name_ = obj["name"].get(); this->logIndex_ = obj["logIndex"].get(); - this->txHash_ = Hash(Hex::toBytes(obj["txHash"].get().substr(2))); + this->txHash_ = Hash(Hex::toBytes(std::string_view(obj["txHash"].get()).substr(2))); this->txIndex_ = obj["txIndex"].get(); - this->blockHash_ = Hash(Hex::toBytes(obj["blockHash"].get().substr(2))); + this->blockHash_ = Hash(Hex::toBytes(std::string_view(obj["blockHash"].get()).substr(2))); this->blockIndex_ = obj["blockIndex"].get(); - this->address_ = Address(obj["address"].get(), false); + this->address_ = bytes::hex(obj["address"].get()); this->data_ = obj["data"].get(); - for (std::string topic : obj["topics"]) this->topics_.push_back(Hex::toBytes(topic)); + for (std::string topic : obj["topics"]) this->topics_.emplace_back(Hex::toBytes(topic)); this->anonymous_ = obj["anonymous"].get(); } -std::string Event::serialize() const { +std::string Event::serializeToJson() const { json topicArr = json::array(); for (const Hash& b : this->topics_) topicArr.push_back(b.hex(true).get()); json obj = { - {"name", this->name_}, {"logIndex", this->logIndex_}, {"txHash", this->txHash_.hex(true).get()}, {"txIndex", this->txIndex_}, @@ -39,163 +40,19 @@ std::string Event::serialize() const { return obj.dump(); } -std::string Event::serializeForRPC() const { +json Event::serializeForRPC() const { std::vector topicStr; for (const Hash& b : this->topics_) topicStr.push_back(b.hex(true).get()); json obj = { - {"address", this->address_.hex(true).get()}, + {"address", Address::checksum(this->address_)}, {"blockHash", this->blockHash_.hex(true).get()}, - {"blockNumber", Hex::fromBytes(Utils::uint64ToBytes(this->blockIndex_), true).get()}, + {"blockNumber", Hex::fromBytes(Utils::uintToBytes(this->blockIndex_), true).forRPC()}, {"data", Hex::fromBytes(this->data_, true).get()}, - {"logIndex", Hex::fromBytes(Utils::uint64ToBytes(this->logIndex_), true).get()}, + {"logIndex", Hex::fromBytes(Utils::uintToBytes(this->logIndex_), true).forRPC()}, {"removed", false}, // We don't fake/alter events like Ethereum does {"topics", topicStr}, {"transactionHash", this->txHash_.hex(true).get()}, - {"transactionIndex", Hex::fromBytes(Utils::uint64ToBytes(this->txIndex_), true).get()} + {"transactionIndex", Hex::fromBytes(Utils::uintToBytes(this->txIndex_), true).forRPC()} }; - return obj.dump(); -} - -EventManager::EventManager( - const std::unique_ptr& db, const std::unique_ptr& options -) : db_(db), options_(options) { - std::vector allEvents = this->db_->getBatch(DBPrefix::events); - for (const DBEntry& event : allEvents) { - Event e(Utils::bytesToString(event.value)); // Create a new Event object by deserializing - this->events_.insert(std::move(e)); // Use insert for MultiIndex container - } -} - -EventManager::~EventManager() { - DBBatch batchedOperations; - { - std::unique_lock lock(this->lock_); - for (const auto& e : this->events_) { - // Build the key (block height + tx index + log index + address) - Bytes key; - key.reserve(8 + 8 + 8 + key.size()); - Utils::appendBytes(key, Utils::uint64ToBytes(e.getBlockIndex())); - Utils::appendBytes(key, Utils::uint64ToBytes(e.getTxIndex())); - Utils::appendBytes(key, Utils::uint64ToBytes(e.getLogIndex())); - Utils::appendBytes(key, e.getAddress().asBytes()); - // Serialize the value to a JSON string and insert into the batch - batchedOperations.push_back(key, Utils::stringToBytes(e.serialize()), DBPrefix::events); - } - } - // Batch save to database and clear the list - this->db_->putBatch(batchedOperations); - this->events_.clear(); -} - -const std::vector EventManager::getEvents( - const uint64_t& fromBlock, const uint64_t& toBlock, - const Address& address, const std::vector& topics -) const { - std::vector ret; - // Check if block range is within limits - uint64_t heightDiff = std::max(fromBlock, toBlock) - std::min(fromBlock, toBlock); - if (heightDiff > this->options_->getEventBlockCap()) throw std::out_of_range( - "Block range too large for event querying! Max allowed is " + - std::to_string(this->options_->getEventBlockCap()) - ); - // Fetch from memory, then match topics from memory - for (const Event& e : this->filterFromMemory(fromBlock, toBlock, address)) { - if (this->matchTopics(e, topics) && ret.size() < this->options_->getEventLogCap()) { - ret.push_back(e); - } - } - if (ret.size() >= this->options_->getEventLogCap()) return ret; - // Fetch from database if we have space left - for (const Event& e : this->filterFromDB(fromBlock, toBlock, address, topics)) { - if (ret.size() >= this->options_->getEventLogCap()) break; - ret.push_back(std::move(e)); - } - return ret; -} - -const std::vector EventManager::getEvents( - const Hash& txHash, const uint64_t& blockIndex, const uint64_t& txIndex -) const { - std::vector ret; - // Fetch from memory - const auto& txHashIndex = this->events_.get<2>(); // txHash is the third index - auto [start, end] = txHashIndex.equal_range(txHash); - for (auto it = start; it != end; it++) { - if (ret.size() >= this->options_->getEventLogCap()) break; - const Event& e = *it; - if (e.getBlockIndex() == blockIndex && e.getTxIndex() == txIndex) ret.push_back(e); - } - // Fetch from DB - Bytes fetchBytes = DBPrefix::events; - Utils::appendBytes(fetchBytes, Utils::uint64ToBytes(blockIndex)); - Utils::appendBytes(fetchBytes, Utils::uint64ToBytes(txIndex)); - for (DBEntry entry : this->db_->getBatch(fetchBytes)) { - if (ret.size() >= this->options_->getEventLogCap()) break; - Event e(Utils::bytesToString(entry.value)); - ret.push_back(e); - } - return ret; -} - -const std::vector EventManager::filterFromMemory( - const uint64_t& fromBlock, const uint64_t& toBlock, const Address& address -) const { - std::vector ret; - if (address != Address()) { - auto& addressIndex = this->events_.get<1>(); - for ( - auto it = addressIndex.lower_bound(address); - it != addressIndex.end() && it->getBlockIndex() <= toBlock; - it++ - ) { if (it->getBlockIndex() >= fromBlock) ret.push_back(*it); } - } else { - const auto& blockIndex = this->events_.get<0>(); - for (const Event& e : blockIndex) { - uint64_t idx = e.getBlockIndex(); - if (idx >= fromBlock && idx <= toBlock) ret.push_back(e); - } - } - return ret; -} - -const std::vector EventManager::filterFromDB( - const uint64_t& fromBlock, const uint64_t& toBlock, - const Address& address, const std::vector& topics -) const { - // Filter by block range - std::vector ret; - std::vector dbKeys; - Bytes startBytes; - Bytes endBytes; - Utils::appendBytes(startBytes, Utils::uint64ToBytes(fromBlock)); - Utils::appendBytes(endBytes, Utils::uint64ToBytes(toBlock)); - - // Get the keys first, based on block height, then filter by address if there is one - for (Bytes key : this->db_->getKeys(DBPrefix::events, startBytes, endBytes)) { - uint64_t nHeight = Utils::bytesToUint64(Utils::create_view_span(key, 0, 8)); - Address addr(Utils::create_view_span(key, 24, 20)); - if ( - (fromBlock <= nHeight && nHeight <= toBlock) && - (address == Address() || address == addr) - ) dbKeys.push_back(key); - } - - // Get the key values - for (DBEntry item : this->db_->getBatch(DBPrefix::events, dbKeys)) { - if (ret.size() >= this->options_->getEventLogCap()) break; - Event e(Utils::bytesToString(item.value)); - if (this->matchTopics(e, topics)) ret.push_back(e); - } - return ret; + return obj; } - -bool EventManager::matchTopics( - const Event& event, const std::vector& topics -) const { - if (topics.empty()) return true; // No topic filter applied - const std::vector& eventTopics = event.getTopics(); - if (eventTopics.size() < topics.size()) return false; - for (size_t i = 0; i < topics.size(); i++) if (topics.at(i) != eventTopics[i]) return false; - return true; -} - diff --git a/src/contract/event.h b/src/contract/event.h index bfb1b122..6f20c256 100644 --- a/src/contract/event.h +++ b/src/contract/event.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,26 +8,11 @@ See the LICENSE.txt file in the project root for more information. #ifndef EVENT_H #define EVENT_H -#include -#include -#include -#include - -#include "../libs/json.hpp" - -#include "../utils/db.h" -#include "../utils/options.h" -#include "../utils/strings.h" -#include "../utils/utils.h" - -#include "abi.h" -#include "contract.h" - #include -#include -#include #include +#include "abi.h" // utils.h -> (strings.h -> libs/zpp_bits.h), (libs/json.hpp -> algorithm, string) + namespace bmi = boost::multi_index; using json = nlohmann::ordered_json; @@ -35,9 +20,10 @@ using json = nlohmann::ordered_json; /// Abstraction of a Solidity event. class Event { friend struct event_indices; + friend zpp::bits::access; + using serialize = zpp::bits::members<10>; ///< Typedef for the serialization struct. private: - std::string name_; ///< Event name. uint64_t logIndex_; ///< Position of the event inside the block it was emitted from. Hash txHash_; ///< Hash of the transaction that emitted the event. uint64_t txIndex_; ///< Position of the transaction inside the block the event was emitted from. @@ -45,24 +31,54 @@ class Event { uint64_t blockIndex_; ///< Height of the block that the event was emitted from. Address address_; ///< Address that emitted the event. Bytes data_; ///< Non-indexed arguments of the event. - std::vector topics_; ///< Indexed arguments of the event, limited to a max of 3 (4 for anonymous events). Topics are Hashes since they are all 32 bytes. + std::vector topics_; ///< Indexed arguments of the event, limited to a max of 3 (4 for anonymous events). Topics are Hashes since they are always 32 bytes. bool anonymous_; ///< Whether the event is anonymous or not (its signature is indexed and searchable). + friend struct event_indices; public: + Event() = default; + + /** + * Constructor for EVM events. + * @param name The event's name. + * @param logIndex The event's position on the block. + * @param txHash The hash of the transaction that emitted the event. + * @param txIndex The position of the transaction in the block. + * @param blockHash The hash of the block that emitted the event. + * @param blockIndex The height of the block. + * @param address The address that emitted the event. + * @param data The event's arguments. + * @param topics The event's indexed arguments. + * @param anonymous Whether the event is anonymous or not. + */ + Event(uint64_t logIndex, const Hash& txHash, uint64_t txIndex, + const Hash& blockHash, uint64_t blockIndex, Address address, Bytes data, + std::vector topics, bool anonymous) : + logIndex_(logIndex), txHash_(txHash), txIndex_(txIndex), + blockHash_(blockHash), blockIndex_(blockIndex), address_(address), + data_(std::move(data)), topics_(std::move(topics)), anonymous_(anonymous) {} + /** - * Constructor. Only sets data partially, setStateData() should be called - * after creating a new Event so the rest of the data can be set. + * Constructor for C++ events. + * After creating a new Event so the rest of the data can be set. * @tparam Args The types of the event's arguments. * @param name The event's name. + * @param logIndex The log index of the event. + * @param txHash The hash of the transaction that emitted the event. + * @param txIndex The index of the transaction that emitted the event. + * @param blockHash The hash of the block that emitted the event. + * @param blockIndex The index of the block that emitted the event. * @param address The address that emitted the event. * @param params The event's arguments. (a tuple of N std::pair where T is the type and bool is whether it's indexed or not) * @param anonymous Whether the event is anonymous or not. Defaults to false. */ template Event( - const std::string& name, Address address, + const std::string& name, uint64_t logIndex, const Hash& txHash, uint64_t txIndex, + const Hash& blockHash, uint64_t blockIndex, Address address, const std::tuple...>& params, bool anonymous = false - ) : name_(name), address_(address), anonymous_(anonymous) { + ) : logIndex_(logIndex), txHash_(txHash), txIndex_(txIndex), + blockHash_(blockHash), blockIndex_(blockIndex), address_(address), anonymous_(anonymous) { // Get the event's signature auto eventSignature = ABI::EventEncoder::encodeSignature(name); std::vector topics; @@ -77,7 +93,7 @@ class Event { // object that should be appended to the data vector. // We use std::apply for indexed parameters because we need to iterate over the tuple. Bytes encodedNonIndexed; - std::apply([&](const auto&... param) { + std::apply([&topics](const auto&... param) { (..., (param.isIndexed ? topics.push_back(ABI::EventEncoder::encodeTopicSignature(param.value)) : void())); }, params); @@ -87,9 +103,7 @@ class Event { if (!anonymous) this->topics_.push_back(eventSignature); for (const auto& topic : topics) { if (this->topics_.size() >= 4) { - Logger::logToDebug(LogType::WARNING, Log::event, std::source_location::current().function_name(), - "Attention! Event " + name + " has more than 3 indexed parameters. Only the first 3 will be indexed." - ); + LOGWARNING("Attention! Event " + name + " has more than 3 indexed parameters. Only the first 3 will be indexed."); break; } this->topics_.push_back(topic); @@ -102,54 +116,18 @@ class Event { */ explicit Event(const std::string& jsonstr); - /** - * Set data from the block and transaction that is supposed to emit the event. - * @param logIndex The event's position on the block. - * @param txHash The hash of the transaction that emitted the event. - * @param txIndex The position of the transaction in the block. - * @param blockHash The hash of the block that emitted the event. - * @param blockIndex The height of the block. - */ - void setStateData( - uint64_t logIndex, const Hash& txHash, uint64_t txIndex, - const Hash& blockHash, uint64_t blockIndex - ) { - this->logIndex_ = logIndex; - this->txHash_ = txHash; - this->txIndex_ = txIndex; - this->blockHash_ = blockHash; - this->blockIndex_ = blockIndex; - } - - /// Getter for `name_`. - const std::string& getName() const { return this->name_; } - - /// Getter for `logIndex_`. + ///@{ + /** Getter. */ const uint64_t& getLogIndex() const { return this->logIndex_; } - - /// Getter for `txHash_`. const Hash& getTxHash() const { return this->txHash_; } - - /// Getter for `txIndex_`. const uint64_t& getTxIndex() const { return this->txIndex_; } - - /// Getter for `blockHash_`. const Hash& getBlockHash() const { return this->blockHash_; } - - /// Getter for `blockIndex_`. const uint64_t& getBlockIndex() const { return this->blockIndex_; } - - /// Getter for `address_`. const Address& getAddress() const { return this->address_; } - - /// Getter for `data_`. const Bytes& getData() const { return this->data_; } - - /// Getter for `topics_`. const std::vector& getTopics() const { return this->topics_; } - - /// Getter for `anonymous_`. bool isAnonymous() const { return this->anonymous_; } + ///@} /** * Get the event's selector (keccak hash of its signature). @@ -159,14 +137,13 @@ class Event { if (!this->anonymous_) return this->topics_[0]; else return Hash(); } - /// Serialize event data from the object to a JSON string. - std::string serialize() const; + std::string serializeToJson() const; ///< Serialize event data from the object to a JSON string. /** - * Serialize event data to a JSON string, formatted to RPC response standards: - * https://medium.com/alchemy-api/deep-dive-into-eth-getlogs-5faf6a66fd81 + * Serialize event data to a JSON string, formatted to RPC response standards + * @see https://medium.com/alchemy-api/deep-dive-into-eth-getlogs-5faf6a66fd81 */ - std::string serializeForRPC() const; + json serializeForRPC() const; }; /// Multi-index container used for storing events in memory. @@ -179,128 +156,4 @@ struct event_indices : bmi::indexed_by< bmi::ordered_non_unique> > {}; -/// Alias for the event multi-index container. -using EventContainer = bmi::multi_index_container; - -/** - * Class that holds all events emitted by contracts in the blockchain. - * Responsible for registering, managing and saving/loading events to/from the database. - */ -class EventManager { - private: - // TODO: keep up to 1000 (maybe 10000? 100000? 1M seems too much) events in memory, dump older ones to DB (this includes checking save/load - maybe this should be a deque?) - EventContainer events_; ///< List of all emitted events in memory. Older ones FIRST, newer ones LAST. - EventContainer tempEvents_; ///< List of temporary events waiting to be commited or reverted. - const std::unique_ptr& db_; ///< Reference pointer to the database. - const std::unique_ptr& options_; ///< Reference pointer to the Options singleton. - mutable std::shared_mutex lock_; ///< Mutex for managing read/write access to the permanent events vector. - - public: - /** - * Constructor; Automatically loads events from the database. - * @param db The database to use. - * @param options The Options singleton to use (for event caps). - */ - EventManager(const std::unique_ptr& db, const std::unique_ptr& options); - - /// Destructor. Automatically saves events to the database. - ~EventManager(); - - // TODO: maybe a periodicSaveToDB() just like on Storage? - - /** - * Get all the events emitted under the given inputs. - * Used by "eth_getLogs", from where parameters are defined on an HTTP request - * (directly from the http/jsonrpc submodules, through handle_request() on httpparser). - * @param fromBlock The initial block height to look for. - * @param toBlock The final block height to look for. - * @param address The address to look for. Defaults to empty (look for all available addresses). - * @param topics The topics to filter by. Defaults to empty (look for all available topics). - * @return A list of matching events, limited by the block and/or log caps set above. - * @throw std::out_of_range if specified block range exceeds the limit set in Options. - */ - const std::vector getEvents( - const uint64_t& fromBlock, const uint64_t& toBlock, - const Address& address = Address(), const std::vector& topics = {} - ) const; - - /** - * Overload of getEvents() used by "eth_getTransactionReceipts", where - * parameters are filtered differently (by exact tx, not a range). - * @param txHash The hash of the transaction to look for events. - * @param blockIndex The height of the block to look for events. - * @param txIndex The index of the transaction to look for events. - * @return A list of matching events, limited by the block and/or log caps set above. - */ - const std::vector getEvents( - const Hash& txHash, const uint64_t& blockIndex, const uint64_t& txIndex - ) const; - - /** - * Filter events in memory. Used by getEvents(). - * @param fromBlock The starting block range to query. - * @param toBlock Tne ending block range to query. - * @param address The address to look for. Defaults to empty (look for all available addresses). - * @return A list of found events. - */ - const std::vector filterFromMemory( - const uint64_t& fromBlock, const uint64_t& toBlock, const Address& address = Address() - ) const; - - /** - * Filter events in the database. Used by getEvents(). - * @param fromBlock The starting block range to query. - * @param toBlock Tne ending block range to query. - * @param address The address to look for. Defaults to empty (look for all available addresses). - * @param topics The topics to filter by. Defaults to empty (look for all available topics). - * @return A list of found events. - */ - const std::vector filterFromDB( - const uint64_t& fromBlock, const uint64_t& toBlock, - const Address& address = Address(), const std::vector& topics = {} - ) const; - - /** - * Check if all topics of an event match the desired topics from a query. - * TOPIC ORDER AND QUANTITY MATTERS. e.g.: - * - event is {"a", "b", "c", "d"}, filter is {"a", "b", "c"} = MATCH - * - event is {"a", "b", "c"}, filter is {"a", "c", "b"} = NO MATCH - * - event is {"a", "b"}, filter is {"a", "b", "c"} = NO MATCH - * @param event The event to match. - * @param topics The queried topics to match for. Defaults to empty (no filter). - * @return `true` if all topics match, `false` otherwise. - */ - bool matchTopics(const Event& event, const std::vector& topics = {}) const; - - /** - * Register the event in the temporary list. - * Keep in mind the original Event object is MOVED to the list. - * @param event The event to register. - */ - void registerEvent(Event&& event) { this->tempEvents_.insert(std::move(event)); } - - /** - * Actually register events in the permanent list. - * @param txHash The hash of the transaction that emitted the events. - * @param txIndex The index of the transaction inside the block that emitted the events. - */ - void commitEvents(const Hash& txHash, const uint64_t txIndex) { - uint64_t logIndex = 0; - auto it = tempEvents_.begin(); // Use iterators to loop through the MultiIndex container - while (it != tempEvents_.end()) { - // Since we can't modify the element directly because iterators are const... - Event e = *it; // ...we make a copy... - e.setStateData(logIndex, txHash, txIndex, - ContractGlobals::getBlockHash(), ContractGlobals::getBlockHeight() - ); // ...modify it... - events_.insert(std::move(e)); // ...move it to the permanent container... - it = tempEvents_.erase(it); // ...erase the original from the temp... - logIndex++; // ...and move on to the next - } - } - - /// Discard events in the temporary list. - void revertEvents() { this->tempEvents_.clear(); } -}; - #endif // EVENT_H diff --git a/src/contract/evmcontractexecutor.cpp b/src/contract/evmcontractexecutor.cpp new file mode 100644 index 00000000..4d3558b8 --- /dev/null +++ b/src/contract/evmcontractexecutor.cpp @@ -0,0 +1,408 @@ +#include "evmcontractexecutor.h" + +#include "bytes/cast.h" +#include "bytes/hex.h" +#include "common.h" +#include "outofgas.h" +#include "utils/evmcconv.h" +#include "contract/costs.h" + +constexpr decltype(auto) getAndThen(auto&& map, const auto& key, auto&& andThen, auto&& orElse) { + const auto it = map.find(key); + + if (it == map.end()) { + return std::invoke(orElse); + } else { + return std::invoke(andThen, it->second); + } +} + +constexpr auto getAndThen(auto&& map, const auto& key, auto&& andThen) { + using Result = std::invoke_result_tsecond)>; + return getAndThen(map, key, andThen, [] () { return Result{}; }); +} + + +template +constexpr evmc_call_kind getEvmcKind(const M& msg) { + if constexpr (concepts::SaltMessage) { + return EVMC_CREATE2; + } else if (concepts::CreateMessage) { + return EVMC_CREATE; + } else if (concepts::DelegateCallMessage) { + return EVMC_DELEGATECALL; + } else { + return EVMC_CALL; + } + + std::unreachable(); +} + +template +constexpr evmc_flags getEvmcFlags(const M& msg) { + if constexpr (concepts::StaticCallMessage) { + return EVMC_STATIC; + } else { + return evmc_flags{}; + } +} + +template +constexpr evmc_message makeEvmcMessage(const M& msg, uint64_t depth) { + return evmc_message{ + .kind = getEvmcKind(msg), + .flags = getEvmcFlags(msg), + .depth = int32_t(depth), + .gas = int64_t(msg.gas()), + .recipient = bytes::cast(messageRecipientOrDefault(msg)), + .sender = bytes::cast(msg.from()), + .input_data = msg.input().data(), + .input_size = msg.input().size(), + .value = EVMCConv::uint256ToEvmcUint256(messageValueOrZero(msg)), + .create2_salt = evmc_bytes32{}, + .code_address = bytes::cast(messageCodeAddress(msg)) + }; +} + +template +constexpr evmc_message makeEvmcMessage(const M& msg, uint64_t depth, View
contractAddress) { + return evmc_message{ + .kind = getEvmcKind(msg), + .flags = 0, + .depth = int32_t(depth), + .gas = int64_t(msg.gas()), + .recipient = bytes::cast(contractAddress), + .sender = bytes::cast(msg.from()), + .input_data = nullptr, + .input_size = 0, + .value = EVMCConv::uint256ToEvmcUint256(messageValueOrZero(msg)), + .create2_salt = bytes::cast(messageSaltOrDefault(msg)), + .code_address = evmc_address{} + }; +} + +Bytes EvmContractExecutor::executeEvmcMessage(evmc_vm* vm, const evmc_host_interface* host, evmc_host_context* context, const evmc_message& msg, Gas& gas, View code) { + evmc::Result result(::evmc_execute( + vm, + host, + context, + evmc_revision::EVMC_PRAGUE, + &msg, + code.data(), + code.size())); + + gas = Gas(result.gas_left); + + if (result.status_code == EVMC_SUCCESS) { + return Bytes(result.output_data, result.output_data + result.output_size); + } else if (result.status_code == EVMC_OUT_OF_GAS) { + throw OutOfGas(); + } else { + if (result.output_size > 0) { + if (this->deepestError_) { + throw *this->deepestError_; + } else { + auto reason = ABI::Decoder::decodeError(View(result.output_data, result.output_size)); + this->deepestError_ = std::make_unique( + -32000, + "EVM Contract execution failed with " + + ((!reason.empty()) ? "\"" + reason + "\" " : "unknown reason ") + + "with the deepest " + + ((msg.recipient.bytes != msg.code_address.bytes) ? "DELEGATED CALL " : "CALL ") + + "fail from: " + + Address(msg.sender).hex(true).get() + + " to: " + Address(msg.recipient).hex(true).get() + + " code address: " + Address(msg.code_address).hex(true).get() + + " and input: " + Hex::fromBytes(View(msg.input_data, msg.input_size), true).get(), + Bytes(result.output_data, result.output_data + result.output_size) + ); + throw *this->deepestError_; + } + } + + this->deepestError_ = std::make_unique( + -32000, + std::string("EVM Contract execution failed with no specified reason with the deepest ") + + ((msg.recipient.bytes != msg.code_address.bytes) ? "DELEGATED CALL " : "CALL ") + + "fail from: " + + Address(msg.sender).hex(true).get() + + " to: " + Address(msg.recipient).hex(true).get() + + " code address: " + Address(msg.code_address).hex(true).get(), + Bytes() + ); + throw *this->deepestError_; + } +} + +void EvmContractExecutor::createContractImpl(auto& msg, ExecutionContext& context, View
contractAddress, evmc_vm *vm, evmc::Host& host, uint64_t depth) { + Bytes code = executeEvmcMessage(vm, &evmc::Host::get_interface(), host.to_context(), + makeEvmcMessage(msg, depth, contractAddress), msg.gas(), msg.code()); + + auto account = context.getAccount(contractAddress); + account.setNonce(1); + account.setCode(std::move(code)); + account.setContractType(ContractType::EVM); + account.setBalance(account.getBalance() + msg.value()); + context.notifyNewContract(contractAddress, nullptr); +} + +Bytes EvmContractExecutor::execute(EncodedCallMessage& msg) { + // msg.gas().use(EVM_CONTRACT_CALL_COST); When executing a EVM contract + // We let the VM handle the gas usage + auto depthGuard = transactional::copy(depth_); // TODO: checkpoint (and deprecate copy) + ++depth_; + + const Bytes output = executeEvmcMessage(this->vm_, &this->get_interface(), this->to_context(), + makeEvmcMessage(msg, depth_), msg.gas(), context_.getAccount(msg.to()).getCode()); + + return output; +} + +Bytes EvmContractExecutor::execute(EncodedStaticCallMessage& msg) { + // msg.gas().use(EVM_CONTRACT_CALL_COST); When executing a EVM contract + // We let the VM handle the gas usage + View code = context_.getAccount(msg.to()).getCode(); + + auto depthGuard = transactional::copy(depth_); + ++depth_; + + return executeEvmcMessage(this->vm_, &this->get_interface(), this->to_context(), + makeEvmcMessage(msg, depth_), msg.gas(), code); +} + +Bytes EvmContractExecutor::execute(EncodedDelegateCallMessage& msg) { + // msg.gas().use(EVM_CONTRACT_CALL_COST); When executing a EVM contract + // We let the VM handle the gas usage + auto depthGuard = transactional::copy(depth_); + ++depth_; + + const Bytes output = executeEvmcMessage(this->vm_, &this->get_interface(), this->to_context(), + makeEvmcMessage(msg, depth_), msg.gas(), context_.getAccount(msg.codeAddress()).getCode()); + + return output; +} + +Address EvmContractExecutor::execute(EncodedCreateMessage& msg) { + msg.gas().use(EVM_CONTRACT_CREATION_COST); + auto depthGuard = transactional::copy(depth_); + auto account = context_.getAccount(msg.from()); + const Address contractAddress = generateContractAddress(account.getNonce(), msg.from()); + createContractImpl(msg, context_, contractAddress, vm_, *this, ++depth_); + if (account.getContractType() != ContractType::NOT_A_CONTRACT) { + account.setNonce(account.getNonce() + 1); + } + return contractAddress; +} + +Address EvmContractExecutor::execute(EncodedSaltCreateMessage& msg) { + msg.gas().use(EVM_CONTRACT_CREATION_COST); + auto depthGuard = transactional::copy(depth_); + const Address contractAddress = generateContractAddress(msg.from(), msg.salt(), msg.code()); + createContractImpl(msg, context_, contractAddress, vm_, *this, ++depth_); + return contractAddress; +} + +bool EvmContractExecutor::account_exists(const evmc::address& addr) const noexcept { + return context_.accountExists(addr); +} + +evmc::bytes32 EvmContractExecutor::get_storage(const evmc::address& addr, const evmc::bytes32& key) const noexcept { + return bytes::cast(context_.retrieve(addr, key)); +} + +evmc_storage_status EvmContractExecutor::set_storage(const evmc::address& addr, const evmc::bytes32& key, const evmc::bytes32& value) noexcept { + context_.store(addr, key, value); + return EVMC_STORAGE_MODIFIED; +} + +evmc::uint256be EvmContractExecutor::get_balance(const evmc::address& addr) const noexcept { + try { + return EVMCConv::uint256ToEvmcUint256(context_.getAccount(addr).getBalance()); + } catch (const std::exception&) { + return evmc::uint256be{}; + } +} + +size_t EvmContractExecutor::get_code_size(const evmc::address& addr) const noexcept { + try { + auto account = context_.getAccount(addr); + if (account.getContractType() == ContractType::CPP) { + auto contract = context_.getContract(addr); + return (19 + contract.getContractName().size()); + } + return account.getCode().size(); + } catch (const std::exception&) { + return 0; + } +} + +evmc::bytes32 EvmContractExecutor::get_code_hash(const evmc::address& addr) const noexcept { + try { + auto account = context_.getAccount(addr); + if (account.getContractType() == ContractType::CPP) { + auto contract = context_.getContract(addr); + std::string precompileContract = "PrecompileContract-"; + precompileContract.append(contract.getContractName()); + Bytes code(precompileContract.begin(), precompileContract.end()); + Hash codeHash = Utils::sha3(code); + return bytes::cast(codeHash); + } + return bytes::cast(account.getCodeHash()); + } catch (const std::exception&) { + return evmc::bytes32{}; + } +} + +size_t EvmContractExecutor::copy_code(const evmc::address& addr, size_t code_offset, uint8_t* buffer_data, size_t buffer_size) const noexcept { + try { + auto account = context_.getAccount(addr); + if (account.getContractType() == ContractType::EVM) { + // Insert up to 19 bytes of the "PrecompileContract-" string and the contract name + std::string precompileContract = "PrecompileContract-"; + auto contract = context_.getContract(addr); + precompileContract.append(contract.getContractName()); + Bytes code(precompileContract.begin(), precompileContract.end()); + if (code_offset < code.size()) { + const auto n = std::min(buffer_size, code.size() - code_offset); + if (n > 0) + std::copy_n(&code[code_offset], n, buffer_data); + return n; + } + } + View code = context_.getAccount(addr).getCode(); + if (code_offset < code.size()) { + const auto n = std::min(buffer_size, code.size() - code_offset); + if (n > 0) + std::copy_n(&code[code_offset], n, buffer_data); + return n; + } + } catch (const std::exception&) { + // TODO: makes sense to just ignore? + } + return 0; +} + +bool EvmContractExecutor::selfdestruct(const evmc::address& addr, const evmc::address& beneficiary) noexcept { + // SELFDESTRUCT is not allowed in the current implementation + return false; +} + +evmc_tx_context EvmContractExecutor::get_tx_context() const noexcept { + return evmc_tx_context{ + .tx_gas_price = EVMCConv::uint256ToEvmcUint256(context_.getTxGasPrice()), + .tx_origin = bytes::cast(context_.getTxOrigin()), + .block_coinbase = bytes::cast(context_.getBlockCoinbase()), + .block_number = context_.getBlockNumber(), + .block_timestamp = context_.getBlockTimestamp(), + .block_gas_limit = context_.getBlockGasLimit(), + .block_prev_randao = {}, + .chain_id = EVMCConv::uint256ToEvmcUint256(context_.getChainId()), + .block_base_fee = {}, + .blob_base_fee = {}, + .blob_hashes = nullptr, + .blob_hashes_count = 0, + .initcodes = nullptr, + .initcodes_count = 0 + }; +} + +evmc::bytes32 EvmContractExecutor::get_block_hash(int64_t number) const noexcept { + return EVMCConv::uint256ToEvmcUint256(number); +} + +void EvmContractExecutor::emit_log(const evmc::address& addr, const uint8_t* data, size_t dataSize, const evmc::bytes32 topics[], size_t topicsCount) noexcept { + if (indexingMode_ != IndexingMode::RPC_TRACE) { + return; + } + try { + // We need the following arguments to build a event: + // (std::string) name The event's name. + // (uint64_t) logIndex The event's position on the block. + // (Hash) txHash The hash of the transaction that emitted the event. + // (uint64_t) txIndex The position of the transaction in the block. + // (Hash) blockHash The hash of the block that emitted the event. + // (uint64_t) blockIndex The height of the block. + // (Address) address The address that emitted the event. + // (Bytes) data The event's arguments. + // (std::vector) topics The event's indexed arguments. + // (bool) anonymous Whether the event is anonymous or not. + std::vector topicsVec; + topicsVec.reserve(topicsCount); + for (uint64_t i = 0; i < topicsCount; i++) { + topicsVec.emplace_back(topics[i]); + } + + context_.addEvent(addr, View(data, dataSize), std::move(topicsVec)); + + } catch (const std::exception& ignored) { + // TODO: log errors + } +} + +evmc_access_status EvmContractExecutor::access_account(const evmc::address& addr) noexcept { + return EVMC_ACCESS_WARM; +} + +evmc_access_status EvmContractExecutor::access_storage(const evmc::address& addr, const evmc::bytes32& key) noexcept { + return EVMC_ACCESS_WARM; +} + +evmc::bytes32 EvmContractExecutor::get_transient_storage(const evmc::address &addr, const evmc::bytes32 &key) const noexcept { + return getAndThen(transientStorage_, StorageKeyView(addr, key), [] (const auto& result) { return bytes::cast(result); }); +} + +void EvmContractExecutor::set_transient_storage(const evmc::address &addr, const evmc::bytes32 &key, const evmc::bytes32 &value) noexcept { + // TODO: This also must be controlled by transactions + transientStorage_.emplace(StorageKeyView{addr, key}, value); +} + +evmc::Result EvmContractExecutor::call(const evmc_message& msg) noexcept { + Gas gas(msg.gas); + const uint256_t value = EVMCConv::evmcUint256ToUint256(msg.value); + + const auto process = [&] (auto& msg) { + try { + const auto output = messageHandler_.onMessage(msg); + + if constexpr (concepts::CreateMessage) { + return evmc::Result(EVMC_SUCCESS, int64_t(gas), 0, bytes::cast(output)); + } else { + return evmc::Result(EVMC_SUCCESS, int64_t(gas), 0, output.data(), output.size()); + } + } catch (const OutOfGas&) { + // TODO: ExecutionReverted exception is important + return evmc::Result(EVMC_OUT_OF_GAS); + } catch (const VMExecutionError& err) { + if (!deepestError_) { + // Make sure that if C++ throws VMExecutionError, we store it so if there is another EVM on top of this call + // it can access the true deepest error. + deepestError_ = std::make_unique(err); + } + return evmc::Result(EVMC_REVERT, int64_t(gas), 0, err.data().data(), err.data().size()); + } catch (const std::exception& err) { + Bytes output; + if (err.what() != nullptr) { + output = ABI::Encoder::encodeError(err.what()); // TODO: this may throw... + } + return evmc::Result(EVMC_REVERT, int64_t(gas), 0, output.data(), output.size()); + } + }; + if (msg.kind == EVMC_DELEGATECALL) { + EncodedDelegateCallMessage encodedMessage(msg.sender, msg.recipient, gas, value, View(msg.input_data, msg.input_size), msg.code_address); + return process(encodedMessage); + } else if (msg.kind == EVMC_CALL && msg.flags == EVMC_STATIC) { + EncodedStaticCallMessage encodedMessage(msg.sender, msg.recipient, gas, View(msg.input_data, msg.input_size)); + return process(encodedMessage); + } else if (msg.kind == EVMC_CALL) { + EncodedCallMessage encodedMessage(msg.sender, msg.recipient, gas, value, View(msg.input_data, msg.input_size)); + return process(encodedMessage); + } else if (msg.kind == EVMC_CREATE) { + EncodedCreateMessage encodedMessage(msg.sender, gas, value, View(msg.input_data, msg.input_size)); + return process(encodedMessage); + } else if (msg.kind == EVMC_CREATE2) { + EncodedSaltCreateMessage encodedMessage(msg.sender, gas, value, View(msg.input_data, msg.input_size), msg.create2_salt); + return process(encodedMessage); + } + + std::unreachable(); +} diff --git a/src/contract/evmcontractexecutor.h b/src/contract/evmcontractexecutor.h new file mode 100644 index 00000000..795800a1 --- /dev/null +++ b/src/contract/evmcontractexecutor.h @@ -0,0 +1,103 @@ +#ifndef BDK_MESSAGES_EVMCONTRACTEXECUTOR_H +#define BDK_MESSAGES_EVMCONTRACTEXECUTOR_H + +#include +#include "utils/hash.h" +#include "utils/contractreflectioninterface.h" +#include "contract/contractstack.h" +#include "anyencodedmessagehandler.h" +#include "executioncontext.h" +#include "traits.h" + +class EvmContractExecutor : public evmc::Host { +public: + EvmContractExecutor( + AnyEncodedMessageHandler messageHandler, ExecutionContext& context, evmc_vm *vm, IndexingMode indexingMode) + : messageHandler_(messageHandler), context_(context), vm_(vm), transientStorage_(), depth_(0), indexingMode_(indexingMode), deepestError_(nullptr) {} + + EvmContractExecutor(ExecutionContext& context, evmc_vm *vm, IndexingMode indexingMode) + : context_(context), vm_(vm), transientStorage_(), depth_(0), indexingMode_(indexingMode), deepestError_(nullptr) {} + + void setMessageHandler(AnyEncodedMessageHandler messageHandler) { messageHandler_ = messageHandler; } + + Bytes execute(EncodedCallMessage& msg); + + Bytes execute(EncodedStaticCallMessage& msg); + + Bytes execute(EncodedDelegateCallMessage& msg); + + template + requires concepts::CallMessage + auto execute(M&& msg) -> traits::MessageResult { + const Bytes input = messageInputEncoded(msg); + Bytes output; + + if constexpr (concepts::StaticCallMessage) { + EncodedStaticCallMessage encodedMessage(msg.from(), msg.to(), msg.gas(), input); + output = this->execute(encodedMessage); + } else if constexpr (concepts::DelegateCallMessage) { + EncodedDelegateCallMessage encodedMessage(msg.from(), msg.to(), msg.gas(), msg.value(), input, msg.codeAddress()); + output = this->execute(encodedMessage); + } else { + EncodedCallMessage encodedMessage(msg.from(), msg.to(), msg.gas(), msg.value(), input); + output = this->execute(encodedMessage); + } + + if constexpr (not std::same_as, void>) { + return std::get<0>(ABI::Decoder::decodeData>(output)); + } + } + + Address execute(EncodedCreateMessage& msg); + + Address execute(EncodedSaltCreateMessage& msg); + + decltype(auto) execute(auto&& msg) { + return execute(msg); + } + + bool account_exists(const evmc::address& addr) const noexcept override final; + + evmc::bytes32 get_storage(const evmc::address& addr, const evmc::bytes32& key) const noexcept override final; + + evmc_storage_status set_storage(const evmc::address& addr, const evmc::bytes32& key, const evmc::bytes32& value) noexcept override final; + + evmc::uint256be get_balance(const evmc::address& addr) const noexcept override final; + + size_t get_code_size(const evmc::address& addr) const noexcept override final; + + evmc::bytes32 get_code_hash(const evmc::address& addr) const noexcept override final; + + size_t copy_code(const evmc::address& addr, size_t code_offset, uint8_t* buffer_data, size_t buffer_size) const noexcept override final; + + bool selfdestruct(const evmc::address& addr, const evmc::address& beneficiary) noexcept override final; + + evmc_tx_context get_tx_context() const noexcept override final; + + evmc::bytes32 get_block_hash(int64_t number) const noexcept override final; + + void emit_log(const evmc::address& addr, const uint8_t* data, size_t data_size, const evmc::bytes32 topics[], size_t topics_count) noexcept override final; + + evmc_access_status access_account(const evmc::address& addr) noexcept override final; + + evmc_access_status access_storage(const evmc::address& addr, const evmc::bytes32& key) noexcept override final; + + evmc::bytes32 get_transient_storage(const evmc::address &addr, const evmc::bytes32 &key) const noexcept override final; + + void set_transient_storage(const evmc::address &addr, const evmc::bytes32 &key, const evmc::bytes32 &value) noexcept override final; + + evmc::Result call(const evmc_message& msg) noexcept override final; + +private: + AnyEncodedMessageHandler messageHandler_; + ExecutionContext& context_; + evmc_vm *vm_; + boost::unordered_flat_map transientStorage_; + IndexingMode indexingMode_; + uint64_t depth_; + std::unique_ptr deepestError_; + Bytes executeEvmcMessage(evmc_vm* vm, const evmc_host_interface* host, evmc_host_context* context, const evmc_message& msg, Gas& gas, View code); + void createContractImpl(auto& msg, ExecutionContext& context, View
contractAddress, evmc_vm *vm, evmc::Host& host, uint64_t depth); +}; + +#endif // BDK_MESSAGES_EVMCONTRACTEXECUTOR_H diff --git a/src/contract/executioncontext.cpp b/src/contract/executioncontext.cpp new file mode 100644 index 00000000..c078f248 --- /dev/null +++ b/src/contract/executioncontext.cpp @@ -0,0 +1,196 @@ +#include "executioncontext.h" + +void ExecutionContext::addEvent(Event event) { + transactional::AnyTransactional emplaceTransaction(transactional::emplaceBack(events_, std::move(event))); + transactions_.push(std::move(emplaceTransaction)); +} + +void ExecutionContext::addEvent(View
address, View data, std::vector topics) { + this->addEvent(Event(events_.size(), txHash_, txIndex_, blockHash_, blockNumber_, Address(address), Bytes(data), std::move(topics), !topics.empty())); +} + + +ExecutionContext::AccountPointer ExecutionContext::getAccount(View
accountAddress) { + return ExecutionContext::AccountPointer(*accounts_[accountAddress], transactions_); +} + +BaseContract& ExecutionContext::getContract(View
contractAddress) { + const auto it = contracts_.find(contractAddress); + + if (it == contracts_.end()) { + throw DynamicException("contract not found"); + } + + if (it->second == nullptr) { + throw DynamicException("not a C++ contract"); + } + + return *it->second; +} + +const BaseContract& ExecutionContext::getContract(View
contractAddress) const { + const auto it = contracts_.find(contractAddress); + + if (it == contracts_.end()) { + throw DynamicException("contract not found"); + } + + if (it->second == nullptr) { + throw DynamicException("not a C++ contract"); + } + + return *it->second; +} + +Account& ExecutionContext::getMutableAccount(View
accountAddress) { + const auto iterator = accounts_.find(accountAddress); + + if (iterator == accounts_.end()) { + throw DynamicException("account not found"); + } + + return *iterator->second; +} + +bool ExecutionContext::accountExists(View
accountAddress) const { + return accounts_.contains(accountAddress); +} + +void ExecutionContext::addContract(View
address, std::unique_ptr contract) { + if (contract == nullptr) { + throw DynamicException("attempt to insert null contract"); + } + + const auto [iterator, inserted] = contracts_.emplace(address, std::move(contract)); + + if (!inserted) { + throw DynamicException("contract already exists"); + } + + auto account = getAccount(address); + account.setNonce(1); + account.setContractType(ContractType::CPP); + + notifyNewContract(iterator->first, iterator->second.get()); +} + +void ExecutionContext::notifyNewContract(View
address, BaseContract* contract) { + using ContractsTuple = std::tuple>&>; + + newContracts_.emplace_back(address, contract); + + transactions_.push(transactional::AnyTransactional(transactional::BasicTransactional(contracts_, [contractAddress = Address(address)] (auto& contracts) { + contracts.erase(contractAddress); + }))); + + transactions_.push(transactional::AnyTransactional(transactional::BasicTransactional(newContracts_, [] (auto& newContracts) { + newContracts.pop_back(); + }))); +} + +void ExecutionContext::transferBalance(View
fromAddress, View
toAddress, const uint256_t& amount) { + auto sender = getAccount(fromAddress); + auto recipient = getAccount(toAddress); + + if (sender.getBalance() < amount) { + throw DynamicException("insufficient founds"); + } + + sender.setBalance(sender.getBalance() - amount); + recipient.setBalance(recipient.getBalance() + amount); +} + +void ExecutionContext::store(View
addr, View slot, View data) { + transactional::AnyTransactional transaction(transactional::emplaceOrAssign(storage_, StorageKeyView(addr, slot), data)); + transactions_.push(std::move(transaction)); +} + +Hash ExecutionContext::retrieve(View
addr, View slot) const { + const auto iterator = storage_.find(StorageKeyView(addr, slot)); + return (iterator == storage_.end()) ? Hash() : iterator->second; +} + +void ExecutionContext::commit() { + while (!transactions_.empty()) { + transactions_.top().commit(); + transactions_.pop(); + } + + events_.clear(); + newContracts_.clear(); +} + +void ExecutionContext::revert() { + while (!transactions_.empty()) { + transactions_.pop(); // transactions revert on destructor (by default) + } + + events_.clear(); + newContracts_.clear(); +} + +ExecutionContext::Checkpoint ExecutionContext::checkpoint() { + return ExecutionContext::Checkpoint(transactions_); +} + +ExecutionContext::AccountPointer::AccountPointer(Account& account, std::stack& transactions) + : account_(account), transactions_(transactions) {} + +const uint256_t& ExecutionContext::AccountPointer::getBalance() const{ + return account_.balance; +} + +uint64_t ExecutionContext::AccountPointer::getNonce() const { + return account_.nonce; +} + +View ExecutionContext::AccountPointer::getCodeHash() const { + return account_.codeHash; +} + +View ExecutionContext::AccountPointer::getCode() const { + return account_.code; +} + +ContractType ExecutionContext::AccountPointer::getContractType() const { + return account_.contractType; +} + +void ExecutionContext::AccountPointer::setBalance(const uint256_t& amount) { + transactions_.push(transactional::AnyTransactional(transactional::copy(account_.balance))); + account_.balance = amount; +} + +void ExecutionContext::AccountPointer::setNonce(uint64_t nonce) { + transactions_.push(transactional::AnyTransactional(transactional::copy(account_.nonce))); + account_.nonce = nonce; +} + +void ExecutionContext::AccountPointer::setCode(Bytes code) { + transactions_.push(transactional::AnyTransactional(transactional::copy(account_.codeHash))); + transactions_.push(transactional::AnyTransactional(transactional::copy(account_.code))); + account_.codeHash = Utils::sha3(code); + account_.code = std::move(code); +} + +void ExecutionContext::AccountPointer::setContractType(ContractType type) { + transactions_.push(transactional::AnyTransactional(transactional::copy(account_.contractType))); + account_.contractType = type; +} + +ExecutionContext::Checkpoint::Checkpoint(std::stack& transactions) + : transactions_(&transactions), checkpoint_(transactions.size()) {} + +void ExecutionContext::Checkpoint::commit() { + transactions_ = nullptr; +} + +void ExecutionContext::Checkpoint::revert() { + if (transactions_ == nullptr) + return; + + while (transactions_->size() > checkpoint_) + transactions_->pop(); + + transactions_ = nullptr; +} diff --git a/src/contract/executioncontext.h b/src/contract/executioncontext.h new file mode 100644 index 00000000..a0dabf18 --- /dev/null +++ b/src/contract/executioncontext.h @@ -0,0 +1,232 @@ +#ifndef BDK_EXECUTIONCONTEXT_H +#define BDK_EXECUTIONCONTEXT_H + +#include +#include "utils/hash.h" +#include "utils/address.h" +#include "utils/utils.h" +#include "utils/transactional.h" +#include "utils/safehash.h" +#include "contract/contract.h" +#include "contract/event.h" + +class ExecutionContext { +public: + using Storage = boost::unordered_flat_map; + using Accounts = boost::unordered_flat_map, SafeHash, SafeCompare>; + using Contracts = boost::unordered_flat_map, SafeHash, SafeCompare>; + + class Checkpoint; + + class Builder; + + class AccountPointer; + + ExecutionContext( + Accounts& accounts, Storage& storage, Contracts& contracts, + int64_t blockGasLimit, int64_t blockNumber, int64_t blockTimestamp, int64_t txIndex, + View
blockCoinbase, View
txOrigin, View blockHash, View txHash, + const uint256_t& chainId, const uint256_t& txGasPrice) : + accounts_(accounts), storage_(storage), contracts_(contracts), newContracts_(), + blockGasLimit_(blockGasLimit), blockNumber_(blockNumber), blockTimestamp_(blockTimestamp), txIndex_(txIndex), + blockCoinbase_(blockCoinbase), txOrigin_(txOrigin), blockHash_(blockHash), txHash_(txHash), + chainId_(chainId), txGasPrice_(txGasPrice) {} + + ~ExecutionContext() { revert(); } + + ExecutionContext(const ExecutionContext&) = delete; + + ExecutionContext(ExecutionContext&&) = delete; + + ExecutionContext& operator=(const ExecutionContext&) = delete; + + ExecutionContext& operator=(ExecutionContext&&) = delete; + + View getBlockHash() const { return blockHash_; } + + View getTxHash() const { return txHash_; } + + View
getTxOrigin() const { return txOrigin_; } + + View
getBlockCoinbase() const { return blockCoinbase_; } + + int64_t getBlockGasLimit() const { return blockGasLimit_; } + + int64_t getBlockNumber() const { return blockNumber_; } + + int64_t getBlockTimestamp() const { return blockTimestamp_; } + + int64_t getTxIndex() const { return txIndex_; } + + const uint256_t& getTxGasPrice() const { return txGasPrice_; } + + const uint256_t& getChainId() const { return chainId_; } + + void addEvent(Event event); + + void addEvent(std::string_view name, View
address, const auto& args, bool anonymous) { + Event event(std::string(name), events_.size(), Hash(this->getTxHash()), this->getTxIndex(), Hash(this->getBlockHash()), + this->getBlockNumber(), Address(address), args, anonymous); + + this->addEvent(std::move(event)); + } + + void addEvent(View
address, View data, std::vector topics); + + bool accountExists(View
accountAddress) const; + + AccountPointer getAccount(View
accountAddress); + + BaseContract& getContract(View
contractAddress); + + const BaseContract& getContract(View
contractAddress) const; + + const auto& getEvents() const { return events_; } + + const auto& getNewContracts() const { return newContracts_; } + + void addContract(View
address, std::unique_ptr contract); + + void notifyNewContract(View
address, BaseContract* contract); + + void transferBalance(View
fromAddress, View
toAddress, const uint256_t& amount); + + void store(View
addr, View slot, View data); + + Hash retrieve(View
addr, View slot) const; + + void commit(); + + void revert(); + + Checkpoint checkpoint(); + +private: + Account& getMutableAccount(View
accountAddress); + + Accounts& accounts_; + Storage& storage_; + Contracts& contracts_; + int64_t blockGasLimit_; + int64_t blockNumber_; + int64_t blockTimestamp_; + int64_t txIndex_; + Address blockCoinbase_; + Address txOrigin_; + Hash blockHash_; + Hash txHash_; + uint256_t chainId_; + uint256_t txGasPrice_; + size_t eventIndex_ = 0; + std::vector events_; + std::vector> newContracts_; + std::stack transactions_; +}; + +class ExecutionContext::AccountPointer { +public: + AccountPointer(Account& account, std::stack& transactions); + + const uint256_t& getBalance() const; + + uint64_t getNonce() const; + + View getCodeHash() const; + + View getCode() const; + + ContractType getContractType() const; + + void setBalance(const uint256_t& amount); + + void setNonce(uint64_t nonce); + + void setCode(Bytes code); + + void setContractType(ContractType type); + +private: + Account& account_; + std::stack& transactions_; +}; + +class ExecutionContext::Checkpoint { +public: + explicit Checkpoint(std::stack& transactions); + + Checkpoint(const Checkpoint&) = delete; + Checkpoint(Checkpoint&&) noexcept = delete; + Checkpoint& operator=(const Checkpoint&) = delete; + Checkpoint& operator=(Checkpoint&&) noexcept = delete; + + ~Checkpoint() { revert(); } + + void commit(); + + void revert(); + +private: + std::stack* transactions_; + size_t checkpoint_; +}; + +class ExecutionContext::Builder { +public: + + Builder() {} + + Builder& storage(ExecutionContext::Storage& storage) { storage_ = &storage; return *this; } + + Builder& accounts(ExecutionContext::Accounts& accounts) { accounts_ = &accounts; return *this; } + + Builder& contracts(ExecutionContext::Contracts& contracts) { contracts_ = &contracts; return *this; } + + Builder& blockHash(View blockHash) { blockHash_ = Hash(blockHash); return *this; } + + Builder& txHash(View txHash) { txHash_ = Hash(txHash); return *this; } + + Builder& txOrigin(View
txOrigin) { txOrigin_ = Address(txOrigin); return *this; } + + Builder& blockCoinbase(View
blockCoinbase) { blockCoinbase_ = Address(blockCoinbase); return *this; } + + Builder& txIndex(int64_t txIndex) { txIndex_ = txIndex; return *this; } + + Builder& blockNumber(int64_t blockNumber) { blockNumber_ = blockNumber; return *this; } + + Builder& blockTimestamp(int64_t blockTimestamp) { blockTimestamp_ = blockTimestamp; return *this; } + + Builder& blockGasLimit(int64_t blockGasLimit) { blockGasLimit_ = blockGasLimit; return *this; } + + Builder& txGasPrice(const uint256_t& txGasPrice) { txGasPrice_ = txGasPrice; return *this; } + + Builder& chainId(const uint256_t& chainId) { chainId_ = chainId; return *this; } + + ExecutionContext build() { + return ExecutionContext( + *accounts_, *storage_, *contracts_, blockGasLimit_, blockNumber_, blockTimestamp_, txIndex_, + blockCoinbase_, txOrigin_, blockHash_, txHash_, chainId_, txGasPrice_); + } + + std::unique_ptr buildPtr() { + return std::make_unique( + *accounts_, *storage_, *contracts_, blockGasLimit_, blockNumber_, blockTimestamp_, txIndex_, + blockCoinbase_, txOrigin_, blockHash_, txHash_, chainId_, txGasPrice_); + } + +private: + ExecutionContext::Accounts* accounts_; + ExecutionContext::Storage* storage_; + ExecutionContext::Contracts* contracts_; + int64_t blockGasLimit_; + int64_t blockNumber_; + int64_t blockTimestamp_; + int64_t txIndex_; + Address blockCoinbase_; + Address txOrigin_; + Hash blockHash_; + Hash txHash_; + uint256_t chainId_; + uint256_t txGasPrice_; +}; + +#endif // BDK_EXECUTIONCONTEXT_H diff --git a/src/contract/executionreverted.h b/src/contract/executionreverted.h new file mode 100644 index 00000000..05fce1e7 --- /dev/null +++ b/src/contract/executionreverted.h @@ -0,0 +1,15 @@ +#ifndef BDK_MESSAGES_EXECUTIONREVERTED_H +#define BDK_MESSAGES_EXECUTIONREVERTED_H + +#include "utils/dynamicexception.h" + +namespace messages { + +struct ExecutionReverted : std::runtime_error { + explicit ExecutionReverted(std::string_view msg) + : std::runtime_error(str.data()) {} +}; + +} // namespace messages + +#endif // BDK_MESSAGES_EXECUTIONREVERTED_H diff --git a/src/contract/gas.h b/src/contract/gas.h new file mode 100644 index 00000000..cc921296 --- /dev/null +++ b/src/contract/gas.h @@ -0,0 +1,34 @@ +#ifndef BDK_MESSAGES_GAS_H +#define BDK_MESSAGES_GAS_H + +#include "utils/evmcconv.h" +#include "outofgas.h" + +class Gas { +public: + constexpr explicit Gas(uint256_t value = 0) : value_(value) {} + + constexpr void use(const uint256_t& amount) { + if (amount > value_) { + value_ = 0; + throw OutOfGas(); + } + + value_ -= amount; + } + + constexpr void refund(const uint256_t& amount) { + value_ += amount; + } + + template + requires std::constructible_from + explicit constexpr operator T() const { + return static_cast(value_); + } + +private: + uint256_t value_; +}; + +#endif // BDK_MESSAGES_GAS_H diff --git a/src/contract/messagedispatcher.h b/src/contract/messagedispatcher.h new file mode 100644 index 00000000..965dbfb7 --- /dev/null +++ b/src/contract/messagedispatcher.h @@ -0,0 +1,138 @@ +#ifndef BDK_MESSAGES_MESSAGEDISPATCHER_H +#define BDK_MESSAGES_MESSAGEDISPATCHER_H + +#include "common.h" +#include "bytes/hex.h" +#include "utils/utils.h" +#include "concepts.h" +#include "executioncontext.h" +#include "evmcontractexecutor.h" +#include "cppcontractexecutor.h" +#include "precompiledcontractexecutor.h" + +class MessageDispatcher { +public: + MessageDispatcher(ExecutionContext& context, CppContractExecutor cppExecutor, EvmContractExecutor evmExecutor, PrecompiledContractExecutor precompiledExecutor) + : context_(context), cppExecutor_(std::move(cppExecutor)), evmExecutor_(std::move(evmExecutor)), precompiledExecutor_(std::move(precompiledExecutor)) {} + + template> + R onMessage(M&& msg) { + auto checkpoint = context_.checkpoint(); + + if constexpr (std::same_as) { + dispatchMessage(std::forward(msg)); + checkCppContractReverted(); + checkpoint.commit(); + } else { + R result = dispatchMessage(std::forward(msg)); + checkCppContractReverted(); + checkpoint.commit(); + return result; + } + } + + Address dispatchMessage(concepts::CreateMessage auto&& msg) requires concepts::EncodedMessage { + return evmExecutor_.execute(std::forward(msg)); + } + + Address dispatchMessage(concepts::CreateMessage auto&& msg) requires concepts::PackedMessage { + return cppExecutor_.execute(std::forward(msg)); + } + + decltype(auto) dispatchMessage(concepts::CallMessage auto&& msg) { + if constexpr (!concepts::DelegateCallMessage) { + // If it is NOT a DELEGATECALL + // We need to transfer the funds before executing the message + transferFunds(msg); + } + + if (precompiledExecutor_.isPrecompiled(msg.to())) { + return precompiledExecutor_.execute(std::forward(msg)); + } + auto account = context_.getAccount(messageCodeAddress(msg)); + + switch (account.getContractType()) { + case ContractType::CPP: + return dispatchCppCall(std::forward(msg)); + break; + + case ContractType::EVM: + return dispatchEvmCall(std::forward(msg)); + break; + default: {} // Do nothing + } + + if constexpr (concepts::EncodedMessage) { + if (isPayment(msg)) { + return Bytes(); + } + } + + if constexpr (concepts::DelegateCallMessage) { + // Delegate calls can never be Packed so we dont need to check for PackedMessage + throw DynamicException("Attempt to invoke non-contract or inexistent address from: " + msg.to().hex(true).get() + " to: " + msg.from().hex(true).get() + " calldata: " + Hex::fromBytes(msg.input()).forRPC() + " this was a delegated call with code address: " + msg.codeAddress().hex(true).get()); + } + if constexpr (concepts::HasInputField) { + throw DynamicException("Attempt to invoke non-contract or inexistent address from: " + msg.to().hex(true).get() + " to: " + msg.from().hex(true).get() + " calldata: " + Hex::fromBytes(msg.input()).forRPC()); + } + if constexpr (concepts::PackedMessage) { + // If it is a packed message, we need to decode the packed arguments + if constexpr (std::is_same_v>) { + throw DynamicException("Attempt to invoke non-contract or inexistent address from: " + msg.to().hex(true).get() + " to: " + msg.from().hex(true).get() + " apparently no calldata/arguments"); + } else { + Bytes packedArgs = ABI::Encoder::encodeData(msg.args()); + throw DynamicException("Attempt to invoke non-contract or inexistent address from: " + msg.to().hex(true).get() + " to: " + msg.from().hex(true).get() + " calldata: " + Hex::fromBytes(packedArgs).forRPC()); + } + } + throw DynamicException("Attempt to invoke non-contract or inexistent address from: " + msg.to().hex(true).get() + " to: " + msg.from().hex(true).get() + " apparently no calldata/arguments"); + } + + decltype(auto) dispatchCppCall(auto&& msg) { + try { + return cppExecutor_.execute(std::forward(msg)); + } catch (const std::exception&) { + cppContractReverted_ = true; + throw; + } + } + + decltype(auto) dispatchEvmCall(auto&& msg) { + return evmExecutor_.execute(std::forward(msg)); + } + + CppContractExecutor& cppExecutor() { return cppExecutor_; } + + EvmContractExecutor& evmExecutor() { return evmExecutor_; } + + PrecompiledContractExecutor& precompiledExecutor() { return precompiledExecutor_; } + +private: + void transferFunds(const concepts::Message auto& msg) { + const uint256_t value = messageValueOrZero(msg); + if (value > 0) { + context_.transferBalance(msg.from(), msg.to(), value); + } + } + + bool isPayment(const concepts::EncodedMessage auto& msg) const { + return msg.input().size() == 0; + } + + bool isPayment(const concepts::PackedMessage auto& msg) const { + return false; + } + + void checkCppContractReverted() const { + if (cppContractReverted_) [[unlikely]] { + throw DynamicException("Reverted due to C++ call failure"); + } + } + + ExecutionContext& context_; + CppContractExecutor cppExecutor_; + EvmContractExecutor evmExecutor_; + PrecompiledContractExecutor precompiledExecutor_; + bool cppContractReverted_ = false; +}; + +#endif // BDK_MESSAGES_MESSAGEDISPATCHER_H diff --git a/src/contract/outofgas.h b/src/contract/outofgas.h new file mode 100644 index 00000000..69b82de5 --- /dev/null +++ b/src/contract/outofgas.h @@ -0,0 +1,8 @@ +#ifndef BDK_MESSAGES_OUTOFGAS_H +#define BDK_MESSAGES_OUTOFGAS_H + +struct OutOfGas : std::runtime_error { + OutOfGas() : std::runtime_error("Out of gas") {} +}; + +#endif // BDK_MESSAGES_OUTOFGAS_H diff --git a/src/contract/packedmessages.h b/src/contract/packedmessages.h new file mode 100644 index 00000000..63cb6efc --- /dev/null +++ b/src/contract/packedmessages.h @@ -0,0 +1,36 @@ +#ifndef BDK_PACKEDMESSAGES_H +#define BDK_PACKEDMESSAGES_H + +#include "contract/concepts.h" +#include "contract/basemessage.h" + +template +struct PackedCallMessage : BaseMessage< + FromField, + ToField, + GasField, + ValueField, + MethodField, + ArgsField> { + + using BaseMessage, ArgsField>::BaseMessage; +}; + +template +struct PackedStaticCallMessage : BaseMessage< + FromField, + ToField, + GasField, + MethodField, + ArgsField> { + + using BaseMessage, ArgsField>::BaseMessage; +}; + +template +struct PackedCreateMessage : BaseMessage> { + using BaseMessage>::BaseMessage; + using ContractType = C; +}; + +#endif // BDK_PACKEDMESSAGES_H diff --git a/src/contract/precompiledcontractexecutor.cpp b/src/contract/precompiledcontractexecutor.cpp new file mode 100644 index 00000000..5617b331 --- /dev/null +++ b/src/contract/precompiledcontractexecutor.cpp @@ -0,0 +1,53 @@ +#include "precompiledcontractexecutor.h" +#include "precompiles/precompiles.h" +#include "bytes/hex.h" +#include +#include + +constexpr Address RANDOM_GENERATOR_ADDRESS = bytes::hex("0x1000000000000000000000000000100000000001"); + +Bytes PrecompiledContractExecutor::execute(EncodedStaticCallMessage& msg) { + if (msg.to() == RANDOM_GENERATOR_ADDRESS) { + return Utils::makeBytes(UintConv::uint256ToBytes(std::invoke(randomGen_))); + } + + // assuming isPrecompiled() was already called + switch (msg.to()[19]) { + case 0x01: + return precompiles::ecrecover(msg.input(), msg.gas()); + + case 0x02: + return precompiles::sha256(msg.input(), msg.gas()); + + case 0x03: + return ABI::Encoder::encodeData(Address(precompiles::ripemd160(msg.input(), msg.gas()))); + + case 0x04: { + const uint64_t dynamicGasCost = ((msg.input().size() + 31) / 32) * 3; + msg.gas().use(15 + dynamicGasCost); + return Bytes(msg.input()); + return Bytes(msg.input()); + } + + case 0x05: + return precompiles::modexp(msg.input(), msg.gas()); + + case 0x09: + return precompiles::blake2f(msg.input(), msg.gas()); + + default: + throw DynamicException("Precompiled contract not found"); + } +} + +bool PrecompiledContractExecutor::isPrecompiled(View
address) const { + if (address == RANDOM_GENERATOR_ADDRESS) { + return true; + } + + if (std::ranges::any_of(address | std::views::take(19), [] (Byte b) { return b != 0; })) { + return false; + } + + return address[19] <= 0x05 || address[19] == 0x09; +} diff --git a/src/contract/precompiledcontractexecutor.h b/src/contract/precompiledcontractexecutor.h new file mode 100644 index 00000000..7d6a1af0 --- /dev/null +++ b/src/contract/precompiledcontractexecutor.h @@ -0,0 +1,46 @@ +#ifndef BDK_CONTRACT_PRECOMPILEDCONTRACTEXECUTOR_H +#define BDK_CONTRACT_PRECOMPILEDCONTRACTEXECUTOR_H + +#include "utils/randomgen.h" +#include "traits.h" +#include "contract/encodedmessages.h" +#include "contract/abi.h" +#include "contract/costs.h" + +class PrecompiledContractExecutor { +public: + explicit PrecompiledContractExecutor(RandomGen randomGen) : randomGen_(std::move(randomGen)) {} + + Bytes execute(EncodedStaticCallMessage& msg); + + template> + Result execute(Message&& msg) { + if constexpr (concepts::DelegateCallMessage) { + throw DynamicException("Delegate calls not allowed for precompiled contracts"); + } + + msg.gas().use(CPP_CONTRACT_CALL_COST); + + const Bytes input = messageInputEncoded(msg); + EncodedStaticCallMessage encodedMessage(msg.from(), msg.to(), msg.gas(), input); + + if constexpr (concepts::PackedMessage) { + if constexpr (std::same_as) { + this->execute(encodedMessage); + } else { + return std::get<0>(ABI::Decoder::decodeData(this->execute(encodedMessage))); + } + } else { + return this->execute(encodedMessage); + } + } + + RandomGen& randomGenerator() { return randomGen_; } + + bool isPrecompiled(View
address) const; + +private: + RandomGen randomGen_; +}; + +#endif // BDK_CONTRACT_PRECOMPILEDCONTRACTEXECUTOR_H diff --git a/src/contract/precompiles/blake2f.cpp b/src/contract/precompiles/blake2f.cpp new file mode 100644 index 00000000..edd375cc --- /dev/null +++ b/src/contract/precompiles/blake2f.cpp @@ -0,0 +1,224 @@ +#include "precompiles.h" +#include +#include + +namespace { + +constexpr uint64_t iv[8] = { + 0x6A09E667F3BCC908, 0xBB67AE8584CAA73B, 0x3C6EF372FE94F82B, 0xA54FF53A5F1D36F1, + 0x510E527FADE682D1, 0x9B05688C2B3E6C1F, 0x1F83D9ABFB41BD6B, 0x5BE0CD19137E2179 +}; + +constexpr Byte precomputed[10][16] = { + {0, 2, 4, 6, 1, 3, 5, 7, 8, 10, 12, 14, 9, 11, 13, 15}, + {14, 4, 9, 13, 10, 8, 15, 6, 1, 0, 11, 5, 12, 2, 7, 3}, + {11, 12, 5, 15, 8, 0, 2, 13, 10, 3, 7, 9, 14, 6, 1, 4}, + {7, 3, 13, 11, 9, 1, 12, 14, 2, 5, 4, 15, 6, 10, 0, 8}, + {9, 5, 2, 10, 0, 7, 4, 15, 14, 11, 6, 3, 1, 12, 8, 13}, + {2, 6, 0, 8, 12, 10, 11, 3, 4, 7, 15, 1, 13, 5, 14, 9}, + {12, 1, 14, 4, 5, 15, 13, 10, 0, 6, 9, 8, 7, 3, 2, 11}, + {13, 7, 12, 3, 11, 14, 1, 9, 5, 15, 8, 2, 0, 4, 6, 10}, + {6, 14, 11, 0, 15, 9, 3, 8, 12, 13, 1, 10, 2, 7, 4, 5}, + {10, 8, 7, 1, 2, 4, 6, 5, 15, 9, 3, 13, 11, 14, 12, 0} +}; + +constexpr size_t sizeInBytes(const std::ranges::contiguous_range auto& range) { + return std::ranges::size(range) * sizeof(std::ranges::range_value_t); +} + +} // namespace + +void precompiles::blake2f(std::span h, std::span m, + uint64_t c0, uint64_t c1, bool flag, uint32_t rounds) { + + uint64_t v0 = h[0]; + uint64_t v1 = h[1]; + uint64_t v2 = h[2]; + uint64_t v3 = h[3]; + uint64_t v4 = h[4]; + uint64_t v5 = h[5]; + uint64_t v6 = h[6]; + uint64_t v7 = h[7]; + uint64_t v8 = iv[0]; + uint64_t v9 = iv[1]; + uint64_t v10 = iv[2]; + uint64_t v11 = iv[3]; + uint64_t v12 = iv[4]; + uint64_t v13 = iv[5]; + uint64_t v14 = iv[6]; + uint64_t v15 = iv[7]; + + v12 ^= c0; + v13 ^= c1; + + if (flag) { + v14 ^= 0xFFFFFFFFFFFFFFFF; + } + + for (uint32_t i = 0; i < rounds; i++) { + const auto* s = precomputed[i % 10]; + + v0 += m[s[0]]; + v0 += v4; + v12 ^= v0; + v12 = std::rotr(v12, 32); + v8 += v12; + v4 ^= v8; + v4 = std::rotr(v4, 24); + v1 += m[s[1]]; + v1 += v5; + v13 ^= v1; + v13 = std::rotr(v13, 32); + v9 += v13; + v5 ^= v9; + v5 = std::rotr(v5, 24); + v2 += m[s[2]]; + v2 += v6; + v14 ^= v2; + v14 = std::rotr(v14, 32); + v10 += v14; + v6 ^= v10; + v6 = std::rotr(v6, 24); + v3 += m[s[3]]; + v3 += v7; + v15 ^= v3; + v15 = std::rotr(v15, 32); + v11 += v15; + v7 ^= v11; + v7 = std::rotr(v7, 24); + + v0 += m[s[4]]; + v0 += v4; + v12 ^= v0; + v12 = std::rotr(v12, 16); + v8 += v12; + v4 ^= v8; + v4 = std::rotr(v4, 63); + v1 += m[s[5]]; + v1 += v5; + v13 ^= v1; + v13 = std::rotr(v13, 16); + v9 += v13; + v5 ^= v9; + v5 = std::rotr(v5, 63); + v2 += m[s[6]]; + v2 += v6; + v14 ^= v2; + v14 = std::rotr(v14, 16); + v10 += v14; + v6 ^= v10; + v6 = std::rotr(v6, 63); + v3 += m[s[7]]; + v3 += v7; + v15 ^= v3; + v15 = std::rotr(v15, 16); + v11 += v15; + v7 ^= v11; + v7 = std::rotr(v7, 63); + + v0 += m[s[8]]; + v0 += v5; + v15 ^= v0; + v15 = std::rotr(v15, 32); + v10 += v15; + v5 ^= v10; + v5 = std::rotr(v5, 24); + v1 += m[s[9]]; + v1 += v6; + v12 ^= v1; + v12 = std::rotr(v12, 32); + v11 += v12; + v6 ^= v11; + v6 = std::rotr(v6, 24); + v2 += m[s[10]]; + v2 += v7; + v13 ^= v2; + v13 = std::rotr(v13, 32); + v8 += v13; + v7 ^= v8; + v7 = std::rotr(v7, 24); + v3 += m[s[11]]; + v3 += v4; + v14 ^= v3; + v14 = std::rotr(v14, 32); + v9 += v14; + v4 ^= v9; + v4 = std::rotr(v4, 24); + + v0 += m[s[12]]; + v0 += v5; + v15 ^= v0; + v15 = std::rotr(v15, 16); + v10 += v15; + v5 ^= v10; + v5 = std::rotr(v5, 63); + v1 += m[s[13]]; + v1 += v6; + v12 ^= v1; + v12 = std::rotr(v12, 16); + v11 += v12; + v6 ^= v11; + v6 = std::rotr(v6, 63); + v2 += m[s[14]]; + v2 += v7; + v13 ^= v2; + v13 = std::rotr(v13, 16); + v8 += v13; + v7 ^= v8; + v7 = std::rotr(v7, 63); + v3 += m[s[15]]; + v3 += v4; + v14 ^= v3; + v14 = std::rotr(v14, 16); + v9 += v14; + v4 ^= v9; + v4 = std::rotr(v4, 63); + } + + h[0] ^= v0 ^ v8; + h[1] ^= v1 ^ v9; + h[2] ^= v2 ^ v10; + h[3] ^= v3 ^ v11; + h[4] ^= v4 ^ v12; + h[5] ^= v5 ^ v13; + h[6] ^= v6 ^ v14; + h[7] ^= v7 ^ v15; +} + +Bytes precompiles::blake2f(View input, Gas& gas) { + if (input.size() != 213) { + throw std::invalid_argument("Blake2F requires exacly 213 bytes"); + } + + std::array h; + std::array m; + std::array t; + uint8_t flag; + uint32_t rounds; + + const Byte *in = input.data(); + + std::memcpy(&rounds, in, sizeof(rounds)); + in += sizeof(rounds); + + std::memcpy(&h, in, sizeInBytes(h)); + in += sizeInBytes(h); + + std::memcpy(&m, in, sizeInBytes(m)); + in += sizeInBytes(m); + + std::memcpy(&t, in, sizeInBytes(t)); + in += sizeInBytes(t); + + std::memcpy(&flag, in, sizeof(flag)); + + std::reverse(reinterpret_cast(&rounds), reinterpret_cast(&rounds + 1)); + + gas.use(rounds); + + blake2f(h, m, t[0], t[1], flag, rounds); + + Bytes output(sizeInBytes(h)); + std::memcpy(output.data(), h.data(), output.size()); + return output; +} diff --git a/src/contract/precompiles/ecrecover.cpp b/src/contract/precompiles/ecrecover.cpp new file mode 100644 index 00000000..cd3a5667 --- /dev/null +++ b/src/contract/precompiles/ecrecover.cpp @@ -0,0 +1,45 @@ +#include "precompiles.h" +#include "contract/abi.h" +#include "utils/signature.h" +#include "utils/ecdsa.h" + +namespace { + +constexpr auto ECRECOVER_COST = 3'000; + +} // namespace + +namespace precompiles { + +Address ecrecover(View hash, uint8_t v, View r, View s) { + if (v == 27) { + v = 0; + } else if (v == 28) { + v = 1; + } else { + return Address{}; + } + + Signature sig; + *std::ranges::copy(s, std::ranges::copy(r, sig.begin()).out).out = v; + + const auto pubkey = Secp256k1::recover(sig, Hash(hash)); + + if (!pubkey) { + return Address{}; + } + + return Secp256k1::toAddress(pubkey); +} + +Bytes ecrecover(View input, Gas& gas) { + gas.use(ECRECOVER_COST); + try { + const auto [hash, v, r, s] = ABI::Decoder::decodeData(input); + return ABI::Encoder::encodeData(ecrecover(hash, v, r, s)); + } catch (...) { + return ABI::Encoder::encodeData(Address{}); + } +} + +} // namespace precompiles diff --git a/src/contract/precompiles/modexp.cpp b/src/contract/precompiles/modexp.cpp new file mode 100644 index 00000000..e74ee586 --- /dev/null +++ b/src/contract/precompiles/modexp.cpp @@ -0,0 +1,118 @@ +#include "precompiles.h" +#include +#include "contract/abi.h" + +namespace { + +using BigInt = boost::multiprecision::cpp_int; +using boost::multiprecision::import_bits; +using boost::multiprecision::export_bits; + +constexpr int bitLength(auto value) { + int count = 0; + while (value) { + value >>= 1; + ++count; + } + return count; +} + +constexpr uint32_t multiplicationComplexity(uint16_t bSize, uint16_t mSize) { + const uint16_t maxSize = std::max(bSize, mSize); + const uint32_t words = (maxSize / 8) + bool(maxSize % 8); + return words * words; +} + +constexpr uint32_t iterationCount(uint16_t eSize, const BigInt& exp) { + uint32_t count; + + if (eSize <= 32 && exp == 0) { + count = 0; + } else if (eSize <= 32) { + count = bitLength(exp) - 1; + } else { + count = (8 * (eSize - 32)) + (bitLength(exp & ((BigInt(1) << 256) - 1)) - 1); + } + + return std::max(count, uint32_t(1)); +} + +inline uint64_t gasRequired(uint16_t bSize, uint16_t mSize, uint16_t eSize, const BigInt& exp) { + const uint64_t complexity = multiplicationComplexity(bSize, mSize); + const uint64_t count = iterationCount(eSize, exp); + return std::max(uint64_t(200), complexity * count / 3); +} + +std::tuple decodeSizes(View input) { + const auto [baseSize, expSize, modSize] = ABI::Decoder::decodeData(input); + + const uint256_t expectedSize = (32 * 3) + baseSize + expSize + modSize; + + if (input.size() < expectedSize) { + throw std::invalid_argument("not enough bytes given"); + } else if (input.size() > expectedSize) { + throw std::invalid_argument("too much bytes given"); + } + + if (baseSize > std::numeric_limits::max()) { + throw std::invalid_argument("base size is too big"); + } + + if (expSize > std::numeric_limits::max()) { + throw std::invalid_argument("exp size is too big"); + } + + if (modSize > std::numeric_limits::max()) { + throw std::invalid_argument("mod size is too big"); + } + + return std::make_tuple(uint16_t(baseSize), uint16_t(expSize), uint16_t(modSize)); +} + +} // namespace + + +Bytes precompiles::modexp(View input, Gas& gas) { + const auto [baseSize, expSize, modSize] = decodeSizes(input); + + auto in = input.begin() + (32 * 3); + + BigInt base = 0, exp = 0, mod = 0, result = 1; + + if (baseSize > 0) { + import_bits(base, in, in + baseSize); + in += baseSize; + } + + if (expSize > 0) { + import_bits(exp, in, in + expSize); + in += expSize; + } + + if (modSize > 0) { + import_bits(mod, in, in + modSize); + } + + gas.use(gasRequired(baseSize, modSize, expSize, exp)); + + Bytes output; + output.resize(modSize); + + if (mod == 1) { + return output; // means: return 0 + } + + base = base % mod; + + while (exp > 0) { + if (exp & 1 != 0) { + result = (result * base) % mod; + } + + exp >>= 1; + base = (base * base) % mod; + } + + export_bits(result, output.begin(), 8); + return output; +} diff --git a/src/contract/precompiles/precompiles.h b/src/contract/precompiles/precompiles.h new file mode 100644 index 00000000..0ea1be7f --- /dev/null +++ b/src/contract/precompiles/precompiles.h @@ -0,0 +1,29 @@ +#ifndef BDK_PRECOMPILES_H +#define BDK_PRECOMPILES_H + +#include "utils/bytes.h" +#include "utils/view.h" +#include "contract/gas.h" + +namespace precompiles { + +Address ecrecover(View hash, uint8_t v, View r, View s); + +Bytes ecrecover(View input, Gas& gas); + +Bytes sha256(View input, Gas& gas); + +Bytes ripemd160(View input, Gas& gas); + +Bytes modexp(View input, Gas& gas); + +void blake2f(std::span h, std::span m, + uint64_t c0, uint64_t c1, bool flag, uint32_t rounds); + +Bytes blake2f(View input, Gas& gas); + +Bytes modexp(View input, Gas& gas); + +} // namespace precompiles + +#endif // BDK_PRECOMPILES_H diff --git a/src/contract/precompiles/ripemd160.cpp b/src/contract/precompiles/ripemd160.cpp new file mode 100644 index 00000000..b654d6db --- /dev/null +++ b/src/contract/precompiles/ripemd160.cpp @@ -0,0 +1,21 @@ +#include "precompiles.h" +#include "utils/address.h" +#include "ripemd160.hpp" +#include "contract/abi.h" + +namespace { + +constexpr size_t OUTPUT_SIZE = 20; + +constexpr uint64_t gasRequired(size_t size) { + return ((size + 31) / 32) * 120 + 600; +} + +} // namespace + +Bytes precompiles::ripemd160(View input, Gas& gas) { + gas.use(gasRequired(input.size())); + Bytes output(OUTPUT_SIZE); + RIPEMD160::ripemd160(input.data(), input.size(), output.data()); + return output; +} diff --git a/src/contract/precompiles/ripemd160.hpp b/src/contract/precompiles/ripemd160.hpp new file mode 100644 index 00000000..3168fee7 --- /dev/null +++ b/src/contract/precompiles/ripemd160.hpp @@ -0,0 +1,344 @@ +/** + * Copyright (c) 2015 Jonas Schnelli + * Copyright (c) 2013-2014 Tomas Dzetkulic + * Copyright (c) 2013-2014 Pavol Rusnak + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef RIPMD160_HPP +#define RIPMD160_HPP + + +#include +#include + +// ================================================== + +namespace RIPEMD160 { +#define ROL_RIPEMD160(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + +#define F_RIPEMD160(x, y, z) ((x) ^ (y) ^ (z)) +#define G_RIPEMD160(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define H_RIPEMD160(x, y, z) (((x) | ~(y)) ^ (z)) +#define IQ_RIPEMD160(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define J_RIPEMD160(x, y, z) ((x) ^ ((y) | ~(z))) + +#define FF(a, b, c, d, e, x, s) \ +{ \ + (a) += F_RIPEMD160((b), (c), (d)) + (x); \ + (a) = ROL_RIPEMD160((a), (s)) + (e); \ + (c) = ROL_RIPEMD160((c), 10); \ +} +#define GG(a, b, c, d, e, x, s) \ +{ \ + (a) += G_RIPEMD160((b), (c), (d)) + (x) + 0x5a827999UL; \ + (a) = ROL_RIPEMD160((a), (s)) + (e); \ + (c) = ROL_RIPEMD160((c), 10); \ +} +#define HH(a, b, c, d, e, x, s) \ +{ \ + (a) += H_RIPEMD160((b), (c), (d)) + (x) + 0x6ed9eba1UL; \ + (a) = ROL_RIPEMD160((a), (s)) + (e); \ + (c) = ROL_RIPEMD160((c), 10); \ +} +#define II(a, b, c, d, e, x, s) \ +{ \ + (a) += IQ_RIPEMD160((b), (c), (d)) + (x) + 0x8f1bbcdcUL; \ + (a) = ROL_RIPEMD160((a), (s)) + (e); \ + (c) = ROL_RIPEMD160((c), 10); \ +} +#define JJ(a, b, c, d, e, x, s) \ +{ \ + (a) += J_RIPEMD160((b), (c), (d)) + (x) + 0xa953fd4eUL; \ + (a) = ROL_RIPEMD160((a), (s)) + (e); \ + (c) = ROL_RIPEMD160((c), 10); \ +} + +#define FFF(a, b, c, d, e, x, s) \ +{ \ + (a) += F_RIPEMD160((b), (c), (d)) + (x); \ + (a) = ROL_RIPEMD160((a), (s)) + (e); \ + (c) = ROL_RIPEMD160((c), 10); \ +} +#define GGG(a, b, c, d, e, x, s) \ +{ \ + (a) += G_RIPEMD160((b), (c), (d)) + (x) + 0x7a6d76e9UL; \ + (a) = ROL_RIPEMD160((a), (s)) + (e); \ + (c) = ROL_RIPEMD160((c), 10); \ +} +#define HHH(a, b, c, d, e, x, s) \ +{ \ + (a) += H_RIPEMD160((b), (c), (d)) + (x) + 0x6d703ef3UL; \ + (a) = ROL_RIPEMD160((a), (s)) + (e); \ + (c) = ROL_RIPEMD160((c), 10); \ +} +#define III(a, b, c, d, e, x, s) \ +{ \ + (a) += IQ_RIPEMD160((b), (c), (d)) + (x) + 0x5c4dd124UL; \ + (a) = ROL_RIPEMD160((a), (s)) + (e); \ + (c) = ROL_RIPEMD160((c), 10); \ +} +#define JJJ(a, b, c, d, e, x, s) \ +{ \ + (a) += J_RIPEMD160((b), (c), (d)) + (x) + 0x50a28be6UL; \ + (a) = ROL_RIPEMD160((a), (s)) + (e); \ + (c) = ROL_RIPEMD160((c), 10); \ +} + static void compress(uint32_t* MDbuf, uint32_t* X) + { + uint32_t aa = MDbuf[0], bb = MDbuf[1], cc = MDbuf[2], dd = MDbuf[3], ee = MDbuf[4]; + uint32_t aaa = MDbuf[0], bbb = MDbuf[1], ccc = MDbuf[2], ddd = MDbuf[3], eee = MDbuf[4]; + + /* round 1 */ + FF(aa, bb, cc, dd, ee, X[0], 11); + FF(ee, aa, bb, cc, dd, X[1], 14); + FF(dd, ee, aa, bb, cc, X[2], 15); + FF(cc, dd, ee, aa, bb, X[3], 12); + FF(bb, cc, dd, ee, aa, X[4], 5); + FF(aa, bb, cc, dd, ee, X[5], 8); + FF(ee, aa, bb, cc, dd, X[6], 7); + FF(dd, ee, aa, bb, cc, X[7], 9); + FF(cc, dd, ee, aa, bb, X[8], 11); + FF(bb, cc, dd, ee, aa, X[9], 13); + FF(aa, bb, cc, dd, ee, X[10], 14); + FF(ee, aa, bb, cc, dd, X[11], 15); + FF(dd, ee, aa, bb, cc, X[12], 6); + FF(cc, dd, ee, aa, bb, X[13], 7); + FF(bb, cc, dd, ee, aa, X[14], 9); + FF(aa, bb, cc, dd, ee, X[15], 8); + + /* round 2 */ + GG(ee, aa, bb, cc, dd, X[7], 7); + GG(dd, ee, aa, bb, cc, X[4], 6); + GG(cc, dd, ee, aa, bb, X[13], 8); + GG(bb, cc, dd, ee, aa, X[1], 13); + GG(aa, bb, cc, dd, ee, X[10], 11); + GG(ee, aa, bb, cc, dd, X[6], 9); + GG(dd, ee, aa, bb, cc, X[15], 7); + GG(cc, dd, ee, aa, bb, X[3], 15); + GG(bb, cc, dd, ee, aa, X[12], 7); + GG(aa, bb, cc, dd, ee, X[0], 12); + GG(ee, aa, bb, cc, dd, X[9], 15); + GG(dd, ee, aa, bb, cc, X[5], 9); + GG(cc, dd, ee, aa, bb, X[2], 11); + GG(bb, cc, dd, ee, aa, X[14], 7); + GG(aa, bb, cc, dd, ee, X[11], 13); + GG(ee, aa, bb, cc, dd, X[8], 12); + + /* round 3 */ + HH(dd, ee, aa, bb, cc, X[3], 11); + HH(cc, dd, ee, aa, bb, X[10], 13); + HH(bb, cc, dd, ee, aa, X[14], 6); + HH(aa, bb, cc, dd, ee, X[4], 7); + HH(ee, aa, bb, cc, dd, X[9], 14); + HH(dd, ee, aa, bb, cc, X[15], 9); + HH(cc, dd, ee, aa, bb, X[8], 13); + HH(bb, cc, dd, ee, aa, X[1], 15); + HH(aa, bb, cc, dd, ee, X[2], 14); + HH(ee, aa, bb, cc, dd, X[7], 8); + HH(dd, ee, aa, bb, cc, X[0], 13); + HH(cc, dd, ee, aa, bb, X[6], 6); + HH(bb, cc, dd, ee, aa, X[13], 5); + HH(aa, bb, cc, dd, ee, X[11], 12); + HH(ee, aa, bb, cc, dd, X[5], 7); + HH(dd, ee, aa, bb, cc, X[12], 5); + + /* round 4 */ + II(cc, dd, ee, aa, bb, X[1], 11); + II(bb, cc, dd, ee, aa, X[9], 12); + II(aa, bb, cc, dd, ee, X[11], 14); + II(ee, aa, bb, cc, dd, X[10], 15); + II(dd, ee, aa, bb, cc, X[0], 14); + II(cc, dd, ee, aa, bb, X[8], 15); + II(bb, cc, dd, ee, aa, X[12], 9); + II(aa, bb, cc, dd, ee, X[4], 8); + II(ee, aa, bb, cc, dd, X[13], 9); + II(dd, ee, aa, bb, cc, X[3], 14); + II(cc, dd, ee, aa, bb, X[7], 5); + II(bb, cc, dd, ee, aa, X[15], 6); + II(aa, bb, cc, dd, ee, X[14], 8); + II(ee, aa, bb, cc, dd, X[5], 6); + II(dd, ee, aa, bb, cc, X[6], 5); + II(cc, dd, ee, aa, bb, X[2], 12); + + /* round 5 */ + JJ(bb, cc, dd, ee, aa, X[4], 9); + JJ(aa, bb, cc, dd, ee, X[0], 15); + JJ(ee, aa, bb, cc, dd, X[5], 5); + JJ(dd, ee, aa, bb, cc, X[9], 11); + JJ(cc, dd, ee, aa, bb, X[7], 6); + JJ(bb, cc, dd, ee, aa, X[12], 8); + JJ(aa, bb, cc, dd, ee, X[2], 13); + JJ(ee, aa, bb, cc, dd, X[10], 12); + JJ(dd, ee, aa, bb, cc, X[14], 5); + JJ(cc, dd, ee, aa, bb, X[1], 12); + JJ(bb, cc, dd, ee, aa, X[3], 13); + JJ(aa, bb, cc, dd, ee, X[8], 14); + JJ(ee, aa, bb, cc, dd, X[11], 11); + JJ(dd, ee, aa, bb, cc, X[6], 8); + JJ(cc, dd, ee, aa, bb, X[15], 5); + JJ(bb, cc, dd, ee, aa, X[13], 6); + + /* parallel round 1 */ + JJJ(aaa, bbb, ccc, ddd, eee, X[5], 8); + JJJ(eee, aaa, bbb, ccc, ddd, X[14], 9); + JJJ(ddd, eee, aaa, bbb, ccc, X[7], 9); + JJJ(ccc, ddd, eee, aaa, bbb, X[0], 11); + JJJ(bbb, ccc, ddd, eee, aaa, X[9], 13); + JJJ(aaa, bbb, ccc, ddd, eee, X[2], 15); + JJJ(eee, aaa, bbb, ccc, ddd, X[11], 15); + JJJ(ddd, eee, aaa, bbb, ccc, X[4], 5); + JJJ(ccc, ddd, eee, aaa, bbb, X[13], 7); + JJJ(bbb, ccc, ddd, eee, aaa, X[6], 7); + JJJ(aaa, bbb, ccc, ddd, eee, X[15], 8); + JJJ(eee, aaa, bbb, ccc, ddd, X[8], 11); + JJJ(ddd, eee, aaa, bbb, ccc, X[1], 14); + JJJ(ccc, ddd, eee, aaa, bbb, X[10], 14); + JJJ(bbb, ccc, ddd, eee, aaa, X[3], 12); + JJJ(aaa, bbb, ccc, ddd, eee, X[12], 6); + + /* parallel round 2 */ + III(eee, aaa, bbb, ccc, ddd, X[6], 9); + III(ddd, eee, aaa, bbb, ccc, X[11], 13); + III(ccc, ddd, eee, aaa, bbb, X[3], 15); + III(bbb, ccc, ddd, eee, aaa, X[7], 7); + III(aaa, bbb, ccc, ddd, eee, X[0], 12); + III(eee, aaa, bbb, ccc, ddd, X[13], 8); + III(ddd, eee, aaa, bbb, ccc, X[5], 9); + III(ccc, ddd, eee, aaa, bbb, X[10], 11); + III(bbb, ccc, ddd, eee, aaa, X[14], 7); + III(aaa, bbb, ccc, ddd, eee, X[15], 7); + III(eee, aaa, bbb, ccc, ddd, X[8], 12); + III(ddd, eee, aaa, bbb, ccc, X[12], 7); + III(ccc, ddd, eee, aaa, bbb, X[4], 6); + III(bbb, ccc, ddd, eee, aaa, X[9], 15); + III(aaa, bbb, ccc, ddd, eee, X[1], 13); + III(eee, aaa, bbb, ccc, ddd, X[2], 11); + + /* parallel round 3 */ + HHH(ddd, eee, aaa, bbb, ccc, X[15], 9); + HHH(ccc, ddd, eee, aaa, bbb, X[5], 7); + HHH(bbb, ccc, ddd, eee, aaa, X[1], 15); + HHH(aaa, bbb, ccc, ddd, eee, X[3], 11); + HHH(eee, aaa, bbb, ccc, ddd, X[7], 8); + HHH(ddd, eee, aaa, bbb, ccc, X[14], 6); + HHH(ccc, ddd, eee, aaa, bbb, X[6], 6); + HHH(bbb, ccc, ddd, eee, aaa, X[9], 14); + HHH(aaa, bbb, ccc, ddd, eee, X[11], 12); + HHH(eee, aaa, bbb, ccc, ddd, X[8], 13); + HHH(ddd, eee, aaa, bbb, ccc, X[12], 5); + HHH(ccc, ddd, eee, aaa, bbb, X[2], 14); + HHH(bbb, ccc, ddd, eee, aaa, X[10], 13); + HHH(aaa, bbb, ccc, ddd, eee, X[0], 13); + HHH(eee, aaa, bbb, ccc, ddd, X[4], 7); + HHH(ddd, eee, aaa, bbb, ccc, X[13], 5); + + /* parallel round 4 */ + GGG(ccc, ddd, eee, aaa, bbb, X[8], 15); + GGG(bbb, ccc, ddd, eee, aaa, X[6], 5); + GGG(aaa, bbb, ccc, ddd, eee, X[4], 8); + GGG(eee, aaa, bbb, ccc, ddd, X[1], 11); + GGG(ddd, eee, aaa, bbb, ccc, X[3], 14); + GGG(ccc, ddd, eee, aaa, bbb, X[11], 14); + GGG(bbb, ccc, ddd, eee, aaa, X[15], 6); + GGG(aaa, bbb, ccc, ddd, eee, X[0], 14); + GGG(eee, aaa, bbb, ccc, ddd, X[5], 6); + GGG(ddd, eee, aaa, bbb, ccc, X[12], 9); + GGG(ccc, ddd, eee, aaa, bbb, X[2], 12); + GGG(bbb, ccc, ddd, eee, aaa, X[13], 9); + GGG(aaa, bbb, ccc, ddd, eee, X[9], 12); + GGG(eee, aaa, bbb, ccc, ddd, X[7], 5); + GGG(ddd, eee, aaa, bbb, ccc, X[10], 15); + GGG(ccc, ddd, eee, aaa, bbb, X[14], 8); + + /* parallel round 5 */ + FFF(bbb, ccc, ddd, eee, aaa, X[12], 8); + FFF(aaa, bbb, ccc, ddd, eee, X[15], 5); + FFF(eee, aaa, bbb, ccc, ddd, X[10], 12); + FFF(ddd, eee, aaa, bbb, ccc, X[4], 9); + FFF(ccc, ddd, eee, aaa, bbb, X[1], 12); + FFF(bbb, ccc, ddd, eee, aaa, X[5], 5); + FFF(aaa, bbb, ccc, ddd, eee, X[8], 14); + FFF(eee, aaa, bbb, ccc, ddd, X[7], 6); + FFF(ddd, eee, aaa, bbb, ccc, X[6], 8); + FFF(ccc, ddd, eee, aaa, bbb, X[2], 13); + FFF(bbb, ccc, ddd, eee, aaa, X[13], 6); + FFF(aaa, bbb, ccc, ddd, eee, X[14], 5); + FFF(eee, aaa, bbb, ccc, ddd, X[0], 15); + FFF(ddd, eee, aaa, bbb, ccc, X[3], 13); + FFF(ccc, ddd, eee, aaa, bbb, X[9], 11); + FFF(bbb, ccc, ddd, eee, aaa, X[11], 11); + + /* combine results */ + ddd += cc + MDbuf[1]; + MDbuf[1] = MDbuf[2] + dd + eee; + MDbuf[2] = MDbuf[3] + ee + aaa; + MDbuf[3] = MDbuf[4] + aa + bbb; + MDbuf[4] = MDbuf[0] + bb + ccc; + MDbuf[0] = ddd; + } + + inline void ripemd160(const uint8_t* msg, uint32_t msg_len, uint8_t* hash) + { + uint32_t i; + int j; + uint32_t digest[5] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0UL}; + + for (i = 0; i < (msg_len >> 6); ++i) { + uint32_t chunk[16]; + + for (j = 0; j < 16; ++j) { + chunk[j] = static_cast(*(msg++)); + chunk[j] |= static_cast((*(msg++))) << 8; + chunk[j] |= static_cast((*(msg++))) << 16; + chunk[j] |= static_cast((*(msg++))) << 24; + } + + compress(digest, chunk); + } + + // Last chunk + { + uint32_t chunk[16] = {0}; + + for (i = 0; i < (msg_len & 63); ++i) { + chunk[i >> 2] ^= static_cast(*msg++) << ((i & 3) << 3); + } + + chunk[(msg_len >> 2) & 15] ^= static_cast(1) << (8 * (msg_len & 3) + 7); + + if ((msg_len & 63) > 55) { + compress(digest, chunk); + memset(chunk, 0, 64); + } + + chunk[14] = msg_len << 3; + chunk[15] = (msg_len >> 29); + compress(digest, chunk); + } + + for (i = 0; i < 5; ++i) { + *(hash++) = digest[i]; + *(hash++) = digest[i] >> 8; + *(hash++) = digest[i] >> 16; + *(hash++) = digest[i] >> 24; + } + } +} // namespace RIPEMD160 +#endif // RIPMD160_HPP \ No newline at end of file diff --git a/src/contract/precompiles/sha256.cpp b/src/contract/precompiles/sha256.cpp new file mode 100644 index 00000000..d93c4a3c --- /dev/null +++ b/src/contract/precompiles/sha256.cpp @@ -0,0 +1,19 @@ +#include "precompiles.h" +#include + +namespace { + +constexpr size_t OUTPUT_SIZE = 32; + +constexpr uint64_t gasRequired(size_t size) { + return ((size + 31) / 32) * 12 + 60; +} + +} // namespace + +Bytes precompiles::sha256(View input, Gas& gas) { + gas.use(gasRequired(input.size())); + Bytes output(OUTPUT_SIZE); + SHA256(input.data(), input.size(), output.data()); + return output; +} diff --git a/src/contract/templates/btvcommon.cpp b/src/contract/templates/btvcommon.cpp new file mode 100644 index 00000000..dbf1153c --- /dev/null +++ b/src/contract/templates/btvcommon.cpp @@ -0,0 +1,285 @@ +#include "btvcommon.h" + + +namespace BTVUtils { + /** + * Check if a given block is close to another block + * a block is to be considered placed on the "middle" of the area based on distance + * For example, if distance is 1, then it will check a 3x3x3 area around the block with 'a' as the center + * If distance is 2, then it will check a 5x5x5 area around the block with 'a' as the center + */ + bool isBlockClose(const WorldBlockPos& a, const WorldBlockPos& b, int distance) { + return std::abs(a.x - b.x) <= distance && + std::abs(a.y - b.y) <= distance && + std::abs(a.z - b.z) <= distance; + } + + World::World() { + const int half = NUM_CHUNKS / 2; // 32 + const int minC = -half; // -32 + const int maxC = minC + NUM_CHUNKS; // -32 + 64 = 32 + + for (int cx = minC; cx < maxC; cx++) { + for (int cy = minC; cy < maxC; cy++) { + ChunkCoord2D cc{ (int32_t)cx, (int32_t)cy }; + Chunk chunk; // defaults to AIR + chunks.emplace(cc, std::move(chunk)); + } + } + for (int lx = 0; lx < 10; lx++) { + for (int lz = 0; lz < 10; lz++) { + // Fill 10x10 area at y=5 with SURFACE + WorldBlockPos pos{ lx, 5, lz }; + LocalBlockPos lpos = worldToLocal(pos); + chunks.at({ lpos.cx, lpos.cy }).blocks[lpos.x][lpos.y][lpos.z].type = BlockType::SURFACE; + } + } + } + // Constructor (with DynamicContract* as argument to initialize this->chunks) + World::World(DynamicContract* contract) : chunks(contract) { + const int half = NUM_CHUNKS / 2; // 32 + const int minC = -half; // -32 + const int maxC = minC + NUM_CHUNKS; // -32 + 64 = 32 + + for (int cx = minC; cx < maxC; cx++) { + for (int cy = minC; cy < maxC; cy++) { + ChunkCoord2D cc{ (int32_t)cx, (int32_t)cy }; + Chunk chunk; // defaults to AIR + + // Special case chunk(0,0): fill 10x10 area at y=5 with SURFACE + if (cx == 0 && cy == 0) { + for (int lx = 0; lx < 10; lx++) { + for (int lz = 0; lz < 10; lz++) { + chunk.blocks[lx][5][lz].type = BlockType::SURFACE; + } + } + } + chunks.emplace(cc, std::move(chunk)); + } + } + } + + LocalBlockPos World::worldToLocal(const WorldBlockPos& wpos) { + auto floorDiv = [](int64_t val, int64_t size) { + if (val >= 0) { + return val / size; + } else { + return (val - (size - 1)) / size; + } + }; + int64_t cx = floorDiv(wpos.x, CHUNK_SIZE_X); + int64_t cy = floorDiv(wpos.z, CHUNK_SIZE_Z); + + int64_t lx = wpos.x - (cx * CHUNK_SIZE_X); + int64_t lz = wpos.z - (cy * CHUNK_SIZE_Z); + int64_t ly = wpos.y; + + LocalBlockPos lpos; + lpos.cx = cx; + lpos.cy = cy; + lpos.x = lx; + lpos.y = ly; + lpos.z = lz; + return lpos; + } + + WorldBlockPos World::localToWorld(const LocalBlockPos& lpos) { + WorldBlockPos wpos; + wpos.x = lpos.cx * CHUNK_SIZE_X + lpos.x; + wpos.y = lpos.y; + wpos.z = lpos.cy * CHUNK_SIZE_Z + lpos.z; + return wpos; + } + + const Block *const World::getBlock(const LocalBlockPos& lp) const { + ChunkCoord2D key{ (int32_t)lp.cx, (int32_t)lp.cy }; + auto it = chunks.find(key); + if (it == chunks.cend()) { + return nullptr; + } + const Chunk& c = it->second; + + if (lp.x < 0 || lp.x >= Chunk::WIDTH) return nullptr; + if (lp.y < 0 || lp.y >= Chunk::HEIGHT) return nullptr; + if (lp.z < 0 || lp.z >= Chunk::LENGTH) return nullptr; + + return &c.blocks[lp.x][lp.y][lp.z]; + } + + const Block *const World::getBlock(const WorldBlockPos& wp) const { + LocalBlockPos lp = worldToLocal(wp); + return getBlock(lp); + } + + Block *const World::getBlock(const LocalBlockPos& lp) { + ChunkCoord2D key{ (int32_t)lp.cx, (int32_t)lp.cy }; + auto it = chunks.find(key); + if (it == chunks.end()) { + return nullptr; + } + Chunk& c = it->second; + + if (lp.x < 0 || lp.x >= Chunk::WIDTH) return nullptr; + if (lp.y < 0 || lp.y >= Chunk::HEIGHT) return nullptr; + if (lp.z < 0 || lp.z >= Chunk::LENGTH) return nullptr; + + return &c.blocks[lp.x][lp.y][lp.z]; + } + + Block *const World::getBlock(const WorldBlockPos& wp) { + LocalBlockPos lp = worldToLocal(wp); + return getBlock(lp); + } + + ConstNeighborBlocksLocal World::getNeighborsLocal(const LocalBlockPos& base) const { + return { + { getBlock(shiftLocalPos(base, +1, 0, 0)), shiftLocalPos(base, +1, 0, 0) }, + { getBlock(shiftLocalPos(base, -1, 0, 0)), shiftLocalPos(base, -1, 0, 0) }, + { getBlock(shiftLocalPos(base, 0, +1, 0)), shiftLocalPos(base, 0, +1, 0) }, + { getBlock(shiftLocalPos(base, 0, -1, 0)), shiftLocalPos(base, 0, -1, 0) }, + { getBlock(shiftLocalPos(base, 0, 0, -1)), shiftLocalPos(base, 0, 0, -1) }, + { getBlock(shiftLocalPos(base, 0, 0, +1)), shiftLocalPos(base, 0, 0, +1) } + }; + } + + ConstNeighborBlocksWorld World::getNeighborsWorld(const WorldBlockPos& wpos) const { + return { + { getBlock(WorldBlockPos{wpos.x + 1, wpos.y, wpos.z}), {wpos.x + 1, wpos.y, wpos.z} }, + { getBlock(WorldBlockPos{wpos.x - 1, wpos.y, wpos.z}), {wpos.x - 1, wpos.y, wpos.z} }, + { getBlock(WorldBlockPos{wpos.x, wpos.y + 1, wpos.z}), {wpos.x, wpos.y + 1, wpos.z} }, + { getBlock(WorldBlockPos{wpos.x, wpos.y - 1, wpos.z}), {wpos.x, wpos.y - 1, wpos.z} }, + { getBlock(WorldBlockPos{wpos.x, wpos.y, wpos.z - 1}), {wpos.x, wpos.y, wpos.z - 1} }, + { getBlock(WorldBlockPos{wpos.x, wpos.y, wpos.z + 1}), {wpos.x, wpos.y, wpos.z + 1} } + }; + } + + ConstNeighborBlocksLocal World::getNeighborsLocal(const WorldBlockPos& wpos) const { + LocalBlockPos lp = worldToLocal(wpos); + return getNeighborsLocal(lp); + } + + NeighborBlocksLocal World::getNeighborsLocal(const WorldBlockPos& wpos) { + LocalBlockPos lp = worldToLocal(wpos); + return getNeighborsLocal(lp); + } + + NeighborBlocksLocal World::getNeighborsLocal(const LocalBlockPos& base) { + return { + { getBlock(shiftLocalPos(base, +1, 0, 0)), shiftLocalPos(base, +1, 0, 0) }, + { getBlock(shiftLocalPos(base, -1, 0, 0)), shiftLocalPos(base, -1, 0, 0) }, + { getBlock(shiftLocalPos(base, 0, +1, 0)), shiftLocalPos(base, 0, +1, 0) }, + { getBlock(shiftLocalPos(base, 0, -1, 0)), shiftLocalPos(base, 0, -1, 0) }, + { getBlock(shiftLocalPos(base, 0, 0, -1)), shiftLocalPos(base, 0, 0, -1) }, + { getBlock(shiftLocalPos(base, 0, 0, +1)), shiftLocalPos(base, 0, 0, +1) } + }; + } + NeighborBlocksWorld World::getNeighborsWorld(const WorldBlockPos& wpos) { + return { + { getBlock(WorldBlockPos{wpos.x + 1, wpos.y, wpos.z}), {wpos.x + 1, wpos.y, wpos.z} }, + { getBlock(WorldBlockPos{wpos.x - 1, wpos.y, wpos.z}), {wpos.x - 1, wpos.y, wpos.z} }, + { getBlock(WorldBlockPos{wpos.x, wpos.y + 1, wpos.z}), {wpos.x, wpos.y + 1, wpos.z} }, + { getBlock(WorldBlockPos{wpos.x, wpos.y - 1, wpos.z}), {wpos.x, wpos.y - 1, wpos.z} }, + { getBlock(WorldBlockPos{wpos.x, wpos.y, wpos.z - 1}), {wpos.x, wpos.y, wpos.z - 1} }, + { getBlock(WorldBlockPos{wpos.x, wpos.y, wpos.z + 1}), {wpos.x, wpos.y, wpos.z + 1} } + }; + } + + LocalBlockPos World::shiftLocalPos(LocalBlockPos base, int dx, int dy, int dz) { + base.x += dx; + base.y += dy; + base.z += dz; + + if (base.x < 0) { + base.x += Chunk::WIDTH; + base.cx -= 1; + } else if (base.x >= Chunk::WIDTH) { + base.x -= Chunk::WIDTH; + base.cx += 1; + } + if (base.z < 0) { + base.z += Chunk::LENGTH; + base.cy -= 1; + } else if (base.z >= Chunk::LENGTH) { + base.z -= Chunk::LENGTH; + base.cy += 1; + } + return base; + } + + bool World::isOutOfBounds(const LocalBlockPos& lp) { + // We have both cx and cy, and the in-chunk position itself. + // Check if the chunk is out of bounds + if (lp.cx < -NUM_CHUNKS / 2 || lp.cx >= NUM_CHUNKS / 2 || lp.cy < -NUM_CHUNKS / 2 || lp.cy >= NUM_CHUNKS / 2) { + return true; + } + // Also check if the in-chunk position is out of bounds + if (lp.x < 0 || lp.x >= Chunk::WIDTH || lp.y < 0 || lp.y >= Chunk::HEIGHT || lp.z < 0 || lp.z >= Chunk::LENGTH) { + return true; + } + return false; + } + + bool World::isOutOfBounds(const WorldBlockPos &wp) { + return World::isOutOfBounds(World::worldToLocal(wp)); + } + + bool World::hasBlockUnder(const WorldBlockPos& wp) const { + if (isOutOfBounds(wp)) { + return false; + } + // Transform the world position to a local position + LocalBlockPos lp = worldToLocal(wp); + // Now, loop all the way down to the bottom, checking if there is a non-air block + for (int y = lp.y - 1; y >= 0; y--) { + if (this->chunks.at({ lp.cx, lp.cy }).blocks[lp.x][y][lp.z].type != BlockType::AIR) { + return true; + } + } + return false; + } + + bool World::hasBlockOver(const WorldBlockPos &wp) const { + if (isOutOfBounds(wp)) { + return false; + } + // Transform the world position to a local position + LocalBlockPos lp = worldToLocal(wp); + // Now, loop all the way up to the top, checking if there is a non-air block + for (int y = lp.y + 1; y < Chunk::HEIGHT; y++) { + if (this->chunks.at({ lp.cx, lp.cy }).blocks[lp.x][y][lp.z].type != BlockType::AIR) { + return true; + } + } + return false; + } + + + const SafeUnorderedMap& World::getChunks() const { + return chunks; + } + + SafeUnorderedMap& World::getChunks() { + return chunks; + } + + const Chunk* World::getChunk(const ChunkCoord2D& coord) const { + auto it = chunks.find(coord); + if (it == chunks.cend()) { + return nullptr; + } + return &it->second; + } + + Chunk* World::getChunk(const ChunkCoord2D& coord) { + auto it = chunks.find(coord); + if (it == chunks.end()) { + return nullptr; + } + return &it->second; + } + + void World::commitAndEnable() { + this->chunks.commit(); + this->chunks.enableRegister(); + } +}; diff --git a/src/contract/templates/btvcommon.h b/src/contract/templates/btvcommon.h new file mode 100644 index 00000000..ca80255e --- /dev/null +++ b/src/contract/templates/btvcommon.h @@ -0,0 +1,281 @@ +/* +Copyright (c) [2023-2025] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include +#include +#include +#include + +#include "utils/uintconv.h" +#include "../variables/safeunorderedmap.h" + +#ifndef BTVCOMMON_H +#define BTVCOMMON_H + +// Forward declaration +class DynamicContract; + +namespace BTVUtils { + + /** + * Basic Block Types + */ + enum class BlockType { + AIR, + SURFACE, + WALL, + ENERGYCHEST + }; + + /** + * World positioning struct + */ + struct WorldBlockPos { + int32_t x = 0; // global X + int32_t y = 0; // global Y (vertical) + int32_t z = 0; // global Z + bool operator==(const WorldBlockPos &) const = default; + }; + + struct LocalBlockPos { + // chunk coordinates (2D, i.e. chunkX, chunkZ) + int32_t cx = 0; + int32_t cy = 0; + // local positions within that chunk + int32_t x = 0; + int32_t y = 0; + int32_t z = 0; + bool operator==(const LocalBlockPos &) const = default; + }; + + using PlayerInformationData = std::tuple< + uint64_t, // playerId + std::tuple< // WorldBlockPos + int32_t, // x + int32_t, // y + int32_t // z + >, + uint256_t, // energy + uint64_t // lastUpdate + >; + + enum class PlayerStatus { + NEVER_JOINED, + INACTIVE, + ACTIVE, + DEAD + }; + + struct PlayerInformation { + WorldBlockPos position{0,0,0}; + uint256_t energy = 0; + uint64_t lastUpdate = 0; + }; + + struct Block { + BlockType type = BlockType::AIR; + std::optional placer_ = std::nullopt; + uint64_t modificationTimestamp = 0; + inline const BlockType& getBlockType() const { return type; } + inline void setBlockType(BlockType t) { type = t; } + inline bool hasPlacer() const { return placer_.has_value(); } + inline void setPlacer(uint64_t placer) { placer_ = placer; } + inline std::optional getPlacer() const { return placer_; } + inline void setModificationTimestamp(uint64_t timestamp) { modificationTimestamp = timestamp; } + inline const uint64_t& getModificationTimestamp() const { return modificationTimestamp; } + bool operator==(const Block &) const = default; + }; + + // + // We store neighbors as pairs of (BlockType*, Position). + // If the neighbor chunk/block doesn't exist, the BlockType* will be nullptr. + // + // front = +x + // back = -x + // top = +y + // bottom= -y + // left = -z + // right = +z + // + struct ConstNeighborBlocksLocal { + std::pair front; + std::pair back; + std::pair top; + std::pair bottom; + std::pair left; + std::pair right; + }; + + struct ConstNeighborBlocksWorld { + std::pair front; + std::pair back; + std::pair top; + std::pair bottom; + std::pair left; + std::pair right; + }; + + struct NeighborBlocksLocal { + std::pair front; + std::pair back; + std::pair top; + std::pair bottom; + std::pair left; + std::pair right; + }; + + struct NeighborBlocksWorld { + std::pair front; + std::pair back; + std::pair top; + std::pair bottom; + std::pair left; + std::pair right; + }; + + + template + using ChunkData = std::array, HEIGHT>, WIDTH>; + + struct Chunk { + static constexpr int WIDTH = 16; + static constexpr int HEIGHT = 64; + static constexpr int LENGTH = 16; + + // blocks[x][y][z] + ChunkData blocks; + + Bytes serialize() const { + Bytes data; + for (int x = 0; x < WIDTH; x++) { + for (int y = 0; y < HEIGHT; y++) { + for (int z = 0; z < LENGTH; z++) { + data.push_back(static_cast(blocks[x][y][z].type)); + // We need to push information about the placer (if it exists) + // and if it does, we need to push the placer's ID + if (blocks[x][y][z].getPlacer().has_value()) { + data.push_back(static_cast(blocks[x][y][z].hasPlacer())); + Utils::appendBytes(data, (UintConv::uint64ToBytes(blocks[x][y][z].getPlacer().value()))); + } else { + data.push_back(static_cast(0)); + } + Utils::appendBytes(data, (UintConv::uint64ToBytes(blocks[x][y][z].getModificationTimestamp()))); + } + } + } + return data; + } + + static Chunk deserialize(Bytes data) { + Chunk chunk; + uint64_t i = 0; + View dataView(data); + for (int x = 0; x < WIDTH; x++) { + for (int y = 0; y < HEIGHT; y++) { + for (int z = 0; z < LENGTH; z++) { + chunk.blocks[x][y][z].setBlockType(static_cast(dataView[i])); + i++; + if (dataView[i] == 1) { + i++; + chunk.blocks[x][y][z].setPlacer(UintConv::bytesToUint64(dataView.subspan(i, 8))); + i += 8; + } else { + i++; + } + chunk.blocks[x][y][z].setModificationTimestamp(UintConv::bytesToUint64(dataView.subspan(i, 8))); + i += 8; + } + } + } + return chunk; + } + bool operator==(const Chunk &) const = default; + // operator= + Chunk& operator=(const Chunk& other) = default; + }; + + /** + * A 2D key for chunk lookup (cx, cy) + * We'll treat 'cx' as chunk-X, 'cy' as chunk-Z + */ + using ChunkCoord2D = std::pair; + + /** + * Check if a given block is close to another block + * a block is to be considered placed on the "middle" of the area based on distance + * For example, if distance is 1, then it will check a 3x3x3 area around the block with 'a' as the center + * If distance is 2, then it will check a 5x5x5 area around the block with 'a' as the center + */ + bool isBlockClose(const WorldBlockPos& a, const WorldBlockPos& b, int distance); + + /** + * World class + * - 1024x1024 area => 64x64 chunks + * - Each chunk is 16x64x16 + * - chunk coords in range [-32..31] + */ + class World + { + private: + // Our chunk map + SafeUnorderedMap chunks; + public: + static constexpr int WORLD_SIZE = 1024; // total dimension in X, Z + static constexpr int CHUNK_SIZE_X = 16; + static constexpr int CHUNK_SIZE_Y = 64; + static constexpr int CHUNK_SIZE_Z = 16; + static constexpr int NUM_CHUNKS = WORLD_SIZE / CHUNK_SIZE_X; // 64 + + // Constructor: build all chunks from -32..31 in X and Z + World(); + // Constructor (with DynamicContract* as argument to initialize this->chunks) + World(DynamicContract* contract); + // Convert a world position to a local position + static LocalBlockPos worldToLocal(const WorldBlockPos& wpos); + // Convert a local (in-chunk) position to a world position + static WorldBlockPos localToWorld(const LocalBlockPos& lpos); + // Get a block directly from a local position + const Block *const getBlock(const LocalBlockPos& lp) const; + // Get a block directly from a world position + const Block *const getBlock(const WorldBlockPos& wp) const; + // Get a block directly from a local position (non-const version) + Block *const getBlock(const LocalBlockPos& lp); + // Get a block directly from a world position + Block *const getBlock(const WorldBlockPos& wp); + // Get neighbors of a local block (using a world position) + ConstNeighborBlocksLocal getNeighborsLocal(const WorldBlockPos& wpos) const; + // Get neighbors of a local block (using a local position) + ConstNeighborBlocksLocal getNeighborsLocal(const LocalBlockPos& base) const; + // Get neighbors of a world block + ConstNeighborBlocksWorld getNeighborsWorld(const WorldBlockPos& wpos) const; + // Get neighbors of a local block (using a world position, non-const version) + NeighborBlocksLocal getNeighborsLocal(const WorldBlockPos& wpos); + // Get neighbors of a local block (using a local position, non-const version) + NeighborBlocksLocal getNeighborsLocal(const LocalBlockPos& base); + // Get neighbors of a world block (non-const version) + NeighborBlocksWorld getNeighborsWorld(const WorldBlockPos& wpos); + // Shifts a given position to the neighbor chunk if needed + static LocalBlockPos shiftLocalPos(LocalBlockPos base, int dx, int dy, int dz); + // Out of bounds check + static bool isOutOfBounds(const LocalBlockPos& lp); + static bool isOutOfBounds(const WorldBlockPos& wp); + bool hasBlockUnder(const WorldBlockPos& wp) const; + bool hasBlockOver(const WorldBlockPos& wp) const; + + // Getter for chunks + const SafeUnorderedMap& getChunks() const; + SafeUnorderedMap& getChunks(); + const Chunk* getChunk(const ChunkCoord2D& coord) const; + Chunk* getChunk(const ChunkCoord2D& coord); + void commitAndEnable(); + bool operator==(const World &) const = default; + }; +} + + + +#endif // BTVCOMMON_H \ No newline at end of file diff --git a/src/contract/templates/btvenergy.cpp b/src/contract/templates/btvenergy.cpp new file mode 100644 index 00000000..9f86ba07 --- /dev/null +++ b/src/contract/templates/btvenergy.cpp @@ -0,0 +1,52 @@ +#include "btvenergy.h" + + +BTVEnergy::BTVEnergy(const Address &address, const DB &db) : + DynamicContract(address, db), + ERC20(address, db), + Ownable(address, db) { + this->registerContractFunctions(); +} + + +BTVEnergy::BTVEnergy(const std::string &erc20_name, const std::string &erc20_symbol, const uint8_t &erc20_decimals, const Address &address, const Address &creator, const uint64_t &chainId) : + DynamicContract("BTVEnergy", address, creator, chainId), + ERC20("BTVEnergy", erc20_name, erc20_symbol, erc20_decimals, 0, address, creator, chainId), + Ownable("BTVEnergy", creator, address, creator, chainId) { +#ifdef BUILD_TESTNET + if (creator != Address(Hex::toBytes("0xc2f2ba5051975004171e6d4781eeda927e884024"))) { + throw DynamicException("Only the Chain Owner can create this contract"); + } +#endif + + this->registerContractFunctions(); +} + + +void BTVEnergy::mint(const Address &to, const uint256_t& value) { + this->onlyOwner(); + this->mintValue_(to, value); +} + +void BTVEnergy::burn(const Address& from, const uint256_t& value) { + this->onlyOwner(); + this->burnValue_(from, value); +} + +void BTVEnergy::registerContractFunctions() { + this->registerMemberFunctions( + std::make_tuple("mint", &BTVEnergy::mint, FunctionTypes::NonPayable, this), + std::make_tuple("burn", &BTVEnergy::burn, FunctionTypes::NonPayable, this) + ); +} + +DBBatch BTVEnergy::dump() const { + DBBatch dbBatch = ERC20::dump(); + const auto ownableDump = Ownable::dump(); + for (const auto& dbItem : ownableDump.getPuts()) { + dbBatch.push_back(dbItem); + } + return dbBatch; +} + + diff --git a/src/contract/templates/btvenergy.h b/src/contract/templates/btvenergy.h new file mode 100644 index 00000000..c282d888 --- /dev/null +++ b/src/contract/templates/btvenergy.h @@ -0,0 +1,54 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef BTVENERGY_H +#define BTVENERGY_Hq + +#include "standards/erc20.h" +#include "ownable.h" +#include "../variables/safeunorderedmap.h" +#include "../variables/safeuint.h" + +class BTVEnergy : public virtual ERC20, public virtual Ownable { + private: + + void registerContractFunctions() override; + public: + /// ConstructorArguments is a tuple of the contract constructor arguments in the order they appear in the constructor. + using ConstructorArguments = std::tuple< + const std::string &, const std::string &, const uint8_t & + >; + + BTVEnergy(const Address& address, const DB& db); + + BTVEnergy( + const std::string &erc20_name, const std::string &erc20_symbol, + const uint8_t &erc20_decimals, + const Address &address, const Address &creator, + const uint64_t &chainId + ); + + void mint(const Address &to, const uint256_t &value); + void burn(const Address &from, const uint256_t &value); + + /// Register contract using ContractReflectionInterface. + static void registerContract() { + static std::once_flag once; + std::call_once(once, [](){ + DynamicContract::registerContractMethods( + std::vector{"erc20_name", "erc20_symbol", "erc20_decimals"}, + std::make_tuple("mint", &BTVEnergy::mint, FunctionTypes::NonPayable, std::vector{"to","value"}), + std::make_tuple("burn", &BTVEnergy::burn, FunctionTypes::NonPayable, std::vector{"from","value"}) + ); + }); + } + DBBatch dump() const override; +}; + + + +#endif // BTVENERGY_H diff --git a/src/contract/templates/btvplayer.cpp b/src/contract/templates/btvplayer.cpp new file mode 100644 index 00000000..4a632e13 --- /dev/null +++ b/src/contract/templates/btvplayer.cpp @@ -0,0 +1,298 @@ +#include "btvplayer.h" +#include "standards/erc20.h" +#include "btvproposals.h" + +BTVPlayer::BTVPlayer(const Address &address, const DB &db) : + DynamicContract(address, db), + ERC721(address, db), + Ownable(address, db), + proposalContract_(this), + energyContract_(this), + worldContract_(this), + playerNames_(this), + playerToTokens_(this), + energyBalance_(this), + addressToPlayers_(this), + tokenCounter_(this) { + + this->proposalContract_ = Address(db.get(std::string("proposalContract_"), this->getDBPrefix())); + this->energyContract_ = Address(db.get(std::string("energyContract_"), this->getDBPrefix())); + this->worldContract_ = Address(db.get(std::string("worldContract_"), this->getDBPrefix())); + for (const auto &dbEntry : db.getBatch(this->getNewPrefix("playerNames_"))) { + this->playerNames_[StrConv::bytesToString(dbEntry.key)] = UintConv::bytesToUint64(dbEntry.value); + } + for (const auto &dbEntry : db.getBatch(this->getNewPrefix("playerToTokens_"))) { + this->playerToTokens_[UintConv::bytesToUint64(dbEntry.key)] = StrConv::bytesToString(dbEntry.value); + } + for (const auto &dbEntry : db.getBatch(this->getNewPrefix("energyBalance_"))) { + this->energyBalance_[UintConv::bytesToUint64(dbEntry.key)] = UintConv::bytesToUint256(dbEntry.value); + } + for (const auto &dbEntry : db.getBatch(this->getNewPrefix("addressToPlayers_"))) { + this->addressToPlayers_[Address(dbEntry.key)].insert(UintConv::bytesToUint64(dbEntry.value)); + } + this->tokenCounter_ = UintConv::bytesToUint256(db.get(std::string("tokenCounter_"), this->getDBPrefix())); + this->energyContract_.commit(); + this->proposalContract_.commit(); + this->worldContract_.commit(); + this->playerNames_.commit(); + this->playerToTokens_.commit(); + this->energyBalance_.commit(); + this->addressToPlayers_.commit(); + this->tokenCounter_.commit(); + + this->registerContractFunctions(); + + this->energyContract_.enableRegister(); + this->proposalContract_.enableRegister(); + this->worldContract_.enableRegister(); + this->playerNames_.enableRegister(); + this->playerToTokens_.enableRegister(); + this->energyBalance_.enableRegister(); + this->addressToPlayers_.enableRegister(); + this->tokenCounter_.enableRegister(); +} + +BTVPlayer::BTVPlayer(const std::string &erc721_name, const std::string &erc721_symbol, const Address &address, const Address &creator, const uint64_t &chainId) : + DynamicContract("BTVPlayer", address, creator, chainId), + ERC721("BTVPlayer", erc721_name, erc721_symbol, address, creator, chainId), + Ownable("BTVEnergy", creator, address, creator, chainId), + proposalContract_(this), + energyContract_(this), + worldContract_(this), + playerNames_(this), + playerToTokens_(this), + energyBalance_(this), + addressToPlayers_(this), + tokenCounter_(this) { + #ifdef BUILD_TESTNET + if (creator != Address(Hex::toBytes("0xc2f2ba5051975004171e6d4781eeda927e884024"))) { + throw DynamicException("Only the Chain Owner can create this contract"); + } + #endif + this->proposalContract_.commit(); + this->energyContract_.commit(); + this->worldContract_.commit(); + this->playerNames_.commit(); + this->playerToTokens_.commit(); + this->energyBalance_.commit(); + this->addressToPlayers_.commit(); + this->tokenCounter_.commit(); + this->registerContractFunctions(); + this->proposalContract_.enableRegister(); + this->energyContract_.enableRegister(); + this->worldContract_.enableRegister(); + this->playerNames_.enableRegister(); + this->playerToTokens_.enableRegister(); + this->energyBalance_.enableRegister(); + this->addressToPlayers_.enableRegister(); + this->tokenCounter_.enableRegister(); +} + +Address BTVPlayer::update_(const Address &to, const uint256_t &tokenId, const Address &auth) { + auto prevAddress = ERC721::update_(to, tokenId, auth); + // We only need to update the addressToPlayers_ map if the token is being transferred + if (prevAddress != to) { + auto it = this->addressToPlayers_.find(prevAddress); + if (it != this->addressToPlayers_.cend()) { + it->second.erase(static_cast(tokenId)); + if (it->second.empty()) { + this->addressToPlayers_.erase(prevAddress); + } + } + this->addressToPlayers_[to].insert(static_cast(tokenId)); + } + return prevAddress; +} + + +std::string BTVPlayer::getPlayerName(const uint64_t &tokenId) const { + auto it = this->playerToTokens_.find(tokenId); + if (it == this->playerToTokens_.cend()) { + throw DynamicException("Player does not exist"); + } + return it->second; +} + +bool BTVPlayer::playerExists(const std::string &playerName) const { + return this->playerNames_.contains(playerName); +} + +void BTVPlayer::mintPlayer(const std::string &name, const Address& to) { + if (this->playerNames_.contains(name)) { + throw DynamicException("Player already exists"); + } + uint256_t tokenId = this->totalSupply(); + this->mint_(to, tokenId); + this->playerNames_[name] = static_cast(tokenId); + this->playerToTokens_[static_cast(tokenId)] = name; + if (this->worldContract_.get()) { + this->tokenApprovals_[static_cast(tokenId)] = this->worldContract_.get(); + } + ++this->tokenCounter_; +} + +void BTVPlayer::setProposalContract(const Address &proposalContract) { + this->onlyOwner(); + this->proposalContract_ = proposalContract; +} + +Address BTVPlayer::getProposalContract() const { + return this->proposalContract_.get(); +} + +void BTVPlayer::setEnergyContract(const Address &energyContract) { + this->onlyOwner(); + this->energyContract_ = energyContract; +} + +Address BTVPlayer::getEnergyContract() const { + return this->energyContract_.get(); +} + +void BTVPlayer::setWorldContract(const Address &worldContract) { + this->onlyOwner(); + this->worldContract_ = worldContract; +} + +Address BTVPlayer::getWorldContract() const { + return this->worldContract_.get(); +} + +uint256_t BTVPlayer::totalSupply() const { + return this->tokenCounter_.get(); +} + +uint256_t BTVPlayer::getPlayerEnergy(const uint64_t &tokenId) const { + auto it = this->energyBalance_.find(tokenId); + if (it == this->energyBalance_.cend()) { + return 0; + } + return it->second; +} + +void BTVPlayer::addPlayerEnergy(const uint64_t &tokenId, const uint256_t &energy) { + this->callContractFunction(this->energyContract_.get(), &ERC20::transferFrom, this->getCaller(), this->getContractAddress(), energy); + this->energyBalance_[tokenId] += energy; +} + +void BTVPlayer::takePlayerEnergy(const uint64_t &tokenId, const uint256_t &energy) { + // Only the owner of the token OR the world contract can take energy from a player + if (this->getCaller() != this->owner() && this->getCaller() != this->ownerOf(tokenId)) { + throw DynamicException("BTVPlayer::takePlayerEnergy: Caller is not the owner of the token or the world contract"); + } + auto it = this->energyBalance_.find(tokenId); + if (it == this->energyBalance_.cend()) { + throw DynamicException("BTVPlayer::takePlayerEnergy: Player does not exist"); + } + if (it->second < energy) { + throw DynamicException("BTVPlayer::takePlayerEnergy: Not enough energy"); + } + this->callContractFunction(this->energyContract_.get(), &ERC20::transfer, this->getContractAddress(), it->second); + it->second -= energy; +} + +std::vector BTVPlayer::getPlayerTokens(const Address& player) const { + auto it = this->addressToPlayers_.find(player); + if (it == this->addressToPlayers_.cend()) { + return {}; + } + std::vector tokens(it->second.cbegin(), it->second.cend()); + return tokens; +} + +void BTVPlayer::createProposal(const uint64_t &tokenId, const std::string &title, const std::string &description) { + if (this->getCaller() != this->ownerOf(tokenId)) { + throw DynamicException("BTVPlayer::createProposal: caller is not the owner of the token"); + } + // Check if the token has enough energy to create a proposal + auto it = this->energyBalance_.find(tokenId); + auto requiredEnergy = this->callContractViewFunction(this->proposalContract_.get(), &BTVProposals::getProposalPrice); + if (it == this->energyBalance_.cend() || it->second < requiredEnergy) { + throw DynamicException("BTVPlayer::createProposal: not enough energy to create a proposal"); + } + this->callContractFunction(this->proposalContract_.get(), &BTVProposals::createProposal, title, description); + this->energyBalance_[tokenId] -= requiredEnergy; +} + +void BTVPlayer::voteOnProposal(const uint64_t &tokenId, const uint64_t &proposalId, const uint256_t &energy) { + + if (this->getCaller() != this->ownerOf(tokenId)) { + throw DynamicException("BTVPlayer::voteOnProposal: caller is not the owner of the token"); + } + auto it = this->energyBalance_.find(tokenId); + if (it == this->energyBalance_.cend() || it->second < energy) { + throw DynamicException("BTVPlayer::voteOnProposal: not enough energy to vote"); + } + this->callContractFunction(this->proposalContract_.get(), &BTVProposals::voteOnProposal, tokenId, proposalId, energy); + it->second -= energy; +} + +void BTVPlayer::removeVote(const uint64_t& tokenId, const uint64_t& proposalId, const uint256_t& energy) { + if (this->getCaller() != this->ownerOf(tokenId)) { + throw DynamicException("BTVPlayer::removeVote: caller is not the owner of the token"); + } + auto it = this->energyBalance_.find(tokenId); + if (it == this->energyBalance_.cend()) { + throw DynamicException("BTVPlayer::removeVote: player does not exist"); + } + this->callContractFunction(this->proposalContract_.get(), &BTVProposals::removeVote, tokenId, proposalId, energy); + it->second += energy; +} + +void BTVPlayer::approveProposalSpend() { + this->onlyOwner(); + this->callContractFunction(energyContract_.get(), &ERC20::approve, proposalContract_.get(), std::numeric_limits::max()); +} + +void BTVPlayer::registerContractFunctions() { + this->registerMemberFunctions( + std::make_tuple("getPlayerName", &BTVPlayer::getPlayerName, FunctionTypes::View, this), + std::make_tuple("playerExists", &BTVPlayer::playerExists, FunctionTypes::View, this), + std::make_tuple("mintPlayer", &BTVPlayer::mintPlayer, FunctionTypes::NonPayable, this), + std::make_tuple("setProposalContract", &BTVPlayer::setProposalContract, FunctionTypes::NonPayable, this), + std::make_tuple("getProposalContract", &BTVPlayer::getProposalContract, FunctionTypes::View, this), + std::make_tuple("setEnergyContract", &BTVPlayer::setEnergyContract, FunctionTypes::NonPayable, this), + std::make_tuple("getEnergyContract", &BTVPlayer::getEnergyContract, FunctionTypes::View, this), + std::make_tuple("setWorldContract", &BTVPlayer::setWorldContract, FunctionTypes::NonPayable, this), + std::make_tuple("getWorldContract", &BTVPlayer::getWorldContract, FunctionTypes::View, this), + std::make_tuple("totalSupply", &BTVPlayer::totalSupply, FunctionTypes::View, this), + std::make_tuple("getPlayerEnergy", &BTVPlayer::getPlayerEnergy, FunctionTypes::View, this), + std::make_tuple("addPlayerEnergy", &BTVPlayer::addPlayerEnergy, FunctionTypes::NonPayable, this), + std::make_tuple("takePlayerEnergy", &BTVPlayer::takePlayerEnergy, FunctionTypes::NonPayable, this), + std::make_tuple("getPlayerTokens", &BTVPlayer::getPlayerTokens, FunctionTypes::View, this), + std::make_tuple("createProposal", &BTVPlayer::createProposal, FunctionTypes::NonPayable, this), + std::make_tuple("voteOnProposal", &BTVPlayer::voteOnProposal, FunctionTypes::NonPayable, this), + std::make_tuple("removeVote", &BTVPlayer::removeVote, FunctionTypes::NonPayable, this), + std::make_tuple("approveProposalSpend", &BTVPlayer::approveProposalSpend, FunctionTypes::NonPayable, this) + ); +} + +DBBatch BTVPlayer::dump() const { + DBBatch dbBatch = ERC721::dump(); + const auto ownableDump = Ownable::dump(); + for (const auto& dbItem : ownableDump.getPuts()) { + dbBatch.push_back(dbItem); + } + dbBatch.push_back(StrConv::stringToBytes("proposalContract_"), this->proposalContract_.get(), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("energyContract_"), this->energyContract_.get(), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("worldContract_"), this->worldContract_.get(), this->getDBPrefix()); + for (auto it = this->playerNames_.cbegin(); it != this->playerNames_.cend(); ++it) { + dbBatch.push_back(StrConv::stringToBytes(it->first), UintConv::uint64ToBytes(it->second), this->getNewPrefix("playerNames_")); + } + for (auto it = this->playerToTokens_.cbegin(); it != this->playerToTokens_.cend(); ++it) { + dbBatch.push_back(UintConv::uint64ToBytes(it->first), StrConv::stringToBytes(it->second), this->getNewPrefix("playerToTokens_")); + } + for (auto it = this->energyBalance_.cbegin(); it != this->energyBalance_.cend(); ++it) { + dbBatch.push_back(UintConv::uint64ToBytes(it->first), UintConv::uint256ToBytes(it->second), this->getNewPrefix("energyBalance_")); + } + for (auto it = this->addressToPlayers_.cbegin(); it != this->addressToPlayers_.cend(); ++it) { + for (const auto& tokenId : it->second) { + dbBatch.push_back(it->first, UintConv::uint64ToBytes(tokenId), this->getNewPrefix("addressToPlayers_")); + } + } + dbBatch.push_back(StrConv::stringToBytes("tokenCounter_"), UintConv::uint256ToBytes(this->tokenCounter_.get()), this->getDBPrefix()); + return dbBatch; +} + + + diff --git a/src/contract/templates/btvplayer.h b/src/contract/templates/btvplayer.h new file mode 100644 index 00000000..856e7d48 --- /dev/null +++ b/src/contract/templates/btvplayer.h @@ -0,0 +1,104 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef BTVPLAYER_H +#define BTVPLAYER_H + +#include "standards/erc721uristorage.h" +#include "ownable.h" +#include "../variables/safeunorderedmap.h" +#include "../variables/safeuint.h" + +class BTVPlayer : public virtual ERC721, public virtual Ownable { + private: + SafeAddress proposalContract_; + SafeAddress energyContract_; + SafeAddress worldContract_; + SafeUnorderedMap playerNames_; + SafeUnorderedMap playerToTokens_; + SafeUnorderedMap energyBalance_; + SafeUnorderedMap> addressToPlayers_; + SafeUint256_t tokenCounter_; + + void registerContractFunctions() override; + + protected: + + // Override ERC721::update_ to add player to addressToPlayers_ map + Address update_(const Address& to, const uint256_t& tokenId, const Address& auth) override; + + public: + /** + * ConstructorArguments is a tuple of the contract constructor arguments in the order they appear in the constructor. + */ + using ConstructorArguments = std::tuple; + + BTVPlayer(const Address& address, const DB& db); + + BTVPlayer( + const std::string &erc721_name, const std::string &erc721_symbol, + const Address &address, const Address &creator, const uint64_t &chainId + ); + + + std::string getPlayerName(const uint64_t& tokenId) const; + bool playerExists(const std::string& playerName) const; + void mintPlayer(const std::string &name, const Address& to); + void setProposalContract(const Address& proposalContract); + void setEnergyContract(const Address& energyContract); + void setWorldContract(const Address& worldContract); + Address getProposalContract() const; + Address getEnergyContract() const; + Address getWorldContract() const; + uint256_t totalSupply() const; + uint256_t getPlayerEnergy(const uint64_t& tokenId) const; + void addPlayerEnergy(const uint64_t& tokenId, const uint256_t& energy); + void takePlayerEnergy(const uint64_t& tokenId, const uint256_t& energy); + std::vector getPlayerTokens(const Address& player) const; + + void createProposal(const uint64_t& tokenId, const std::string& title, const std::string& description); + void voteOnProposal(const uint64_t& tokenId, const uint64_t& proposalId, const uint256_t& energy); + void removeVote(const uint64_t& tokenId, const uint64_t& proposalId, const uint256_t& energy); + + + void approveProposalSpend(); + + + /// Register contract class via ContractReflectionInterface. + static void registerContract() { + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{"erc721_name", "erc721_symbol"}, + std::make_tuple("getPlayerName", &BTVPlayer::getPlayerName, FunctionTypes::View, std::vector{"tokenId"}), + std::make_tuple("playerExists", &BTVPlayer::playerExists, FunctionTypes::View, std::vector{"playerName"}), + std::make_tuple("mintPlayer", &BTVPlayer::mintPlayer, FunctionTypes::NonPayable, std::vector{"name"}), + std::make_tuple("setProposalContract", &BTVPlayer::setProposalContract, FunctionTypes::NonPayable, std::vector{"proposalContract"}), + std::make_tuple("getProposalContract", &BTVPlayer::getProposalContract, FunctionTypes::View, std::vector{}), + std::make_tuple("setEnergyContract", &BTVPlayer::setEnergyContract, FunctionTypes::NonPayable, std::vector{"energyContract"}), + std::make_tuple("getEnergyContract", &BTVPlayer::getEnergyContract, FunctionTypes::View, std::vector{}), + std::make_tuple("setWorldContract", &BTVPlayer::setWorldContract, FunctionTypes::NonPayable, std::vector{"worldContract"}), + std::make_tuple("getWorldContract", &BTVPlayer::getWorldContract, FunctionTypes::View, std::vector{}), + std::make_tuple("totalSupply", &BTVPlayer::totalSupply, FunctionTypes::View, std::vector{}), + std::make_tuple("getPlayerEnergy", &BTVPlayer::getPlayerEnergy, FunctionTypes::View, std::vector{"tokenId"}), + std::make_tuple("addPlayerEnergy", &BTVPlayer::addPlayerEnergy, FunctionTypes::NonPayable, std::vector{"tokenId", "energy"}), + std::make_tuple("takePlayerEnergy", &BTVPlayer::takePlayerEnergy, FunctionTypes::NonPayable, std::vector{"tokenId", "energy"}), + std::make_tuple("getPlayerTokens", &BTVPlayer::getPlayerTokens, FunctionTypes::View, std::vector{"player"}), + std::make_tuple("createProposal", &BTVPlayer::createProposal, FunctionTypes::NonPayable, std::vector{"tokenId", "title", "description"}), + std::make_tuple("voteOnProposal", &BTVPlayer::voteOnProposal, FunctionTypes::NonPayable, std::vector{"tokenId", "proposalId", "energy"}), + std::make_tuple("removeVote", &BTVPlayer::removeVote, FunctionTypes::NonPayable, std::vector{"tokenId", "proposalId", "energy"}), + std::make_tuple("approveProposalSpend", &BTVPlayer::approveProposalSpend, FunctionTypes::NonPayable, std::vector{}) + ); + }); + } + + /// Dump method + DBBatch dump() const override; +}; + + +#endif // BTVPLAYER_H \ No newline at end of file diff --git a/src/contract/templates/btvproposals.cpp b/src/contract/templates/btvproposals.cpp new file mode 100644 index 00000000..d52c5b75 --- /dev/null +++ b/src/contract/templates/btvproposals.cpp @@ -0,0 +1,299 @@ +#include "btvproposals.h" +#include "btvplayer.h" +#include "standards/erc20.h" + + +BTVProposals::BTVProposals(const Address& address, const DB& db) : + DynamicContract(address, db), + Ownable(address, db), + proposalCount_(this), + activeProposals_(this), + completedProposals_(this), + playerContract_(this), + energyContract_(this), + proposalVotes_(this), + proposalPrice_(this) { + + + this->proposalCount_ = UintConv::bytesToUint64(db.get(std::string("proposalCount_"), this->getDBPrefix())); + + // A proposal is stored in the DB in the following format: + // Key = ID (8 bytes) + // Value = Energy (32 bytes) + Title (variable length) + Description (variable length) + // Value = 32 bytes + 8 Bytes (Title Size) + Title + Description + for (const auto &dbEntry : db.getBatch(this->getNewPrefix("activeProposals_"))) { + uint64_t id = UintConv::bytesToUint64(dbEntry.key); + View value(dbEntry.value); + uint256_t energy = UintConv::bytesToUint256(value.subspan(0, 32)); + uint64_t titleSize = UintConv::bytesToUint64(value.subspan(32, 8)); + std::string title = StrConv::bytesToString(value.subspan(40, titleSize)); + std::string description = StrConv::bytesToString(value.subspan(40 + titleSize)); + this->activeProposals_[id] = std::make_tuple(energy, title, description); + } + + // Same for completed proposals + for (const auto &dbEntry : db.getBatch(this->getNewPrefix("completedProposals_"))) { + uint64_t id = UintConv::bytesToUint64(dbEntry.key); + View value(dbEntry.value); + uint256_t energy = UintConv::bytesToUint256(value.subspan(0, 32)); + uint64_t titleSize = UintConv::bytesToUint64(value.subspan(32, 8)); + std::string title = StrConv::bytesToString(value.subspan(40, titleSize)); + std::string description = StrConv::bytesToString(value.subspan(40 + titleSize)); + this->completedProposals_[id] = std::make_tuple(energy, title, description); + } + + this->playerContract_ = Address(db.get(std::string("playerContract_"), this->getDBPrefix())); + this->energyContract_ = Address(db.get(std::string("energyContract_"), this->getDBPrefix())); + + // Here is different + // Key = Proposal ID (8 bytes) + Token ID (8 bytes) + // Value = Energy (32 bytes) + for (const auto &dbEntry : db.getBatch(this->getNewPrefix("proposalVotes_"))) { + View key(dbEntry.key); + uint64_t proposalId = UintConv::bytesToUint64(key.subspan(0, 8)); + uint64_t tokenId = UintConv::bytesToUint64(key.subspan(8, 8)); + uint256_t energy = UintConv::bytesToUint256(dbEntry.value); + this->proposalVotes_[proposalId][tokenId] = energy; + } + + this->proposalPrice_ = UintConv::bytesToUint256(db.get(std::string("proposalPrice_"), this->getDBPrefix())); + + this->proposalCount_.commit(); + this->activeProposals_.commit(); + this->completedProposals_.commit(); + this->playerContract_.commit(); + this->energyContract_.commit(); + this->proposalVotes_.commit(); + this->proposalPrice_.commit(); + this->registerContractFunctions(); + this->proposalCount_.enableRegister(); + this->activeProposals_.enableRegister(); + this->completedProposals_.enableRegister(); + this->playerContract_.enableRegister(); + this->energyContract_.enableRegister(); + this->proposalVotes_.enableRegister(); + this->proposalPrice_.enableRegister(); +} + +BTVProposals::BTVProposals(const Address &address, const Address &creator, const uint64_t &chainId) : + DynamicContract("BTVProposals", address, creator, chainId), + Ownable("BTVProposals", creator, address, creator, chainId), + proposalCount_(this), + activeProposals_(this), + completedProposals_(this), + playerContract_(this), + energyContract_(this), + proposalVotes_(this), + proposalPrice_(this) { + +#ifdef BUILD_TESTNET + if (creator != Address(Hex::toBytes("0xc2f2ba5051975004171e6d4781eeda927e884024"))) { + throw DynamicException("Only the Chain Owner can create this contract"); + } +#endif + + this->proposalCount_.commit(); + this->activeProposals_.commit(); + this->completedProposals_.commit(); + this->playerContract_.commit(); + this->energyContract_.commit(); + this->proposalVotes_.commit(); + this->proposalPrice_.commit(); + this->registerContractFunctions(); + this->proposalCount_.enableRegister(); + this->activeProposals_.enableRegister(); + this->completedProposals_.enableRegister(); + this->playerContract_.enableRegister(); + this->energyContract_.enableRegister(); + this->proposalVotes_.enableRegister(); + this->proposalPrice_.enableRegister(); +} + +void BTVProposals::registerContractFunctions() { + this->registerMemberFunctions( + std::make_tuple("createProposal", &BTVProposals::createProposal, FunctionTypes::NonPayable, this), + std::make_tuple("voteOnProposal", &BTVProposals::voteOnProposal, FunctionTypes::NonPayable, this), + std::make_tuple("removeVote", &BTVProposals::removeVote, FunctionTypes::NonPayable, this), + std::make_tuple("completeProposal", &BTVProposals::completeProposal, FunctionTypes::NonPayable, this), + std::make_tuple("setProposalPrice", &BTVProposals::setProposalPrice, FunctionTypes::NonPayable, this), + std::make_tuple("setPlayerContract", &BTVProposals::setPlayerContract, FunctionTypes::NonPayable, this), + std::make_tuple("setEnergyContract", &BTVProposals::setEnergyContract, FunctionTypes::NonPayable, this), + std::make_tuple("getActiveProposals", &BTVProposals::getActiveProposals, FunctionTypes::View, this), + std::make_tuple("getCompletedProposals", &BTVProposals::getCompletedProposals, FunctionTypes::View, this), + std::make_tuple("getProposalVotes", &BTVProposals::getProposalVotes, FunctionTypes::View, this), + std::make_tuple("getProposalPrice", &BTVProposals::getProposalPrice, FunctionTypes::View, this), + std::make_tuple("getProposalEnergy", &BTVProposals::getProposalEnergy, FunctionTypes::View, this), + std::make_tuple("getProposalCount", &BTVProposals::getProposalCount, FunctionTypes::View, this) + ); +} + +void BTVProposals::onlyPlayer() const { + if (this->getCaller() != this->playerContract_.get()) { + throw DynamicException("BTVProposals: caller is not the player contract"); + } +} + + +void BTVProposals::createProposal(const std::string& title, const std::string& description) { + this->onlyPlayer(); + if (this->proposalPrice_.get() != 0) { + this->callContractFunction(this->energyContract_.get(), &ERC20::transferFrom, this->getCaller(), this->getContractAddress(), this->proposalPrice_.get()); + } + BTVProposal proposal; + auto& [energy, titl, desc] = proposal; + energy = this->proposalPrice_.get(); + titl = title; + desc = description; + this->activeProposals_[this->proposalCount_.get()] = proposal; + ++this->proposalCount_; +} + +void BTVProposals::voteOnProposal(const uint64_t &tokenId, const uint64_t &proposalId, const uint256_t &energy) { + this->onlyPlayer(); + auto proposalIt = this->activeProposals_.find(proposalId); + if (proposalIt == this->activeProposals_.end()) { + throw DynamicException("BTVProposals::voteOnProposal : proposal does not exist"); + } + this->callContractFunction(this->energyContract_.get(), &ERC20::transferFrom, this->getCaller(), this->getContractAddress(), energy); + this->proposalVotes_[proposalId][tokenId] += energy; + std::get<0>(proposalIt->second) += energy; +} + +void BTVProposals::removeVote(const uint64_t &tokenId, const uint64_t &proposalId, const uint256_t &energy) { + this->onlyPlayer(); + if (!this->proposalVotes_.contains(proposalId)) { + throw DynamicException("BTVProposals::removeVote : proposal does not exist"); + } + auto voteIt = this->proposalVotes_[proposalId].find(tokenId); + if (voteIt == this->proposalVotes_[proposalId].end()) { + throw DynamicException("BTVProposals::removeVote : token vote on specific proposal doesnt exist"); + } + if (voteIt->second < energy) { + throw DynamicException("BTVProposals::removeVote : not enough energy to remove"); + } + // If we are taking out ALL the energy, we can remove the vote. + if (voteIt->second == energy) { + this->proposalVotes_[proposalId].erase(voteIt); + } else { + voteIt->second -= energy; + } + this->callContractFunction(this->energyContract_.get(), &ERC20::transfer, this->getCaller(), energy); + // Take the vote out of proposal if its active. + auto proposalIt = this->activeProposals_.find(proposalId); + if (proposalIt != this->activeProposals_.end()) { + std::get<0>(proposalIt->second) -= energy; + } +} + +void BTVProposals::completeProposal(const uint64_t& proposalId) { + this->onlyOwner(); + auto proposalIt = this->activeProposals_.find(proposalId); + if (proposalIt == this->activeProposals_.end()) { + throw DynamicException("BTVProposals::completeProposal : proposal does not exist"); + } + this->completedProposals_[proposalId] = proposalIt->second; + this->activeProposals_.erase(proposalIt); +} + +void BTVProposals::setProposalPrice(const uint256_t &price) { + this->onlyOwner(); + this->proposalPrice_ = price; +} + +void BTVProposals::setPlayerContract(const Address &playerContract) { + this->onlyOwner(); + this->playerContract_ = playerContract; +} + +void BTVProposals::setEnergyContract(const Address &energyContract) { + this->onlyOwner(); + this->energyContract_ = energyContract; +} + +std::vector BTVProposals::getActiveProposals() const { + std::vector proposals; + for (auto it = this->activeProposals_.cbegin(); it != this->activeProposals_.cend(); ++it) { + proposals.emplace_back(it->second); + } + return proposals; +} + +std::vector BTVProposals::getCompletedProposals() const { + std::vector proposals; + for (auto it = this->completedProposals_.cbegin(); it != this->completedProposals_.cend(); ++it) { + proposals.emplace_back(it->second); + } + return proposals; +} + +std::vector> BTVProposals::getProposalVotes(const uint64_t &proposalId) const { + auto votesIt = this->proposalVotes_.find(proposalId); + if (votesIt == this->proposalVotes_.cend()) { + return {}; + } + std::vector> votes; + for (auto it = votesIt->second.cbegin(); it != votesIt->second.cend(); ++it) { + votes.emplace_back(it->first, it->second); + } + return votes; +} + +uint256_t BTVProposals::getProposalPrice() const { + return this->proposalPrice_.get(); +} + +uint256_t BTVProposals::getProposalEnergy(const uint64_t &proposalId) const { + auto proposalIt = this->activeProposals_.find(proposalId); + if (proposalIt == this->activeProposals_.cend()) { + throw DynamicException("BTVProposals::getProposalEnergy : proposal does not exist"); + } + return std::get<0>(proposalIt->second); +} + +uint64_t BTVProposals::getProposalCount() const { + return this->proposalCount_.get(); +} + +DBBatch BTVProposals::dump() const { + DBBatch dbBatch = Ownable::dump(); + dbBatch.push_back(StrConv::stringToBytes("proposalCount_"), UintConv::uint64ToBytes(this->proposalCount_.get()), this->getDBPrefix()); + + for (auto it = this->activeProposals_.cbegin(); it != this->activeProposals_.cend(); ++it) { + auto& [id, proposal] = *it; + auto& [energy, title, description] = proposal; + Bytes value; + Utils::appendBytes(value, UintConv::uint256ToBytes(energy)); + Utils::appendBytes(value, UintConv::uint64ToBytes(title.size())); + Utils::appendBytes(value, StrConv::stringToBytes(title)); + Utils::appendBytes(value, StrConv::stringToBytes(description)); + dbBatch.push_back(UintConv::uint64ToBytes(id), value, this->getNewPrefix("activeProposals_")); + } + + for (auto it = this->completedProposals_.cbegin(); it != this->completedProposals_.cend(); ++it) { + auto& [id, proposal] = *it; + auto& [energy, title, description] = proposal; + Bytes value; + Utils::appendBytes(value, UintConv::uint256ToBytes(energy)); + Utils::appendBytes(value, UintConv::uint64ToBytes(title.size())); + Utils::appendBytes(value, StrConv::stringToBytes(title)); + Utils::appendBytes(value, StrConv::stringToBytes(description)); + dbBatch.push_back(UintConv::uint64ToBytes(id), value, this->getNewPrefix("completedProposals_")); + } + + dbBatch.push_back(StrConv::stringToBytes("playerContract_"), this->playerContract_.get(), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("energyContract_"), this->energyContract_.get(), this->getDBPrefix()); + + for (auto it = this->proposalVotes_.cbegin(); it != this->proposalVotes_.cend(); ++it) { + auto& [proposalId, votes] = *it; + for (auto it2 = votes.cbegin(); it2 != votes.cend(); ++it2) { + auto& [tokenId, energy] = *it2; + Bytes key; + Utils::appendBytes(key, UintConv::uint64ToBytes(proposalId)); + Utils::appendBytes(key, UintConv::uint64ToBytes(tokenId)); + dbBatch.push_back(key, UintConv::uint256ToBytes(energy), this->getNewPrefix("proposalVotes_")); + } + } + + dbBatch.push_back(StrConv::stringToBytes("proposalPrice_"), UintConv::uint256ToBytes(this->proposalPrice_.get()), this->getDBPrefix()); + return dbBatch; +} diff --git a/src/contract/templates/btvproposals.h b/src/contract/templates/btvproposals.h new file mode 100644 index 00000000..89b3b9ab --- /dev/null +++ b/src/contract/templates/btvproposals.h @@ -0,0 +1,95 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef BTVPROPOSALS_H +#define BTVPROPOSALS_H + +#include + +#include "ownable.h" +#include "../../utils/db.h" +#include "../../utils/utils.h" +#include "../abi.h" +#include "../dynamiccontract.h" +#include "../variables/safestring.h" +#include "../variables/safeuint.h" +#include "../variables/safeunorderedmap.h" +#include "../variables/safeaddress.h" + +using BTVProposal = std::tuple< + uint256_t, // Energy Staked in Proposal + std::string, // Proposal Title + std::string // Proposal Description +>; + + +class BTVProposals : public virtual DynamicContract, public Ownable { + private: + SafeUint64_t proposalCount_; + SafeUnorderedMap activeProposals_; + SafeUnorderedMap completedProposals_; + SafeAddress playerContract_; + SafeAddress energyContract_; + SafeUnorderedMap> proposalVotes_; + SafeUint256_t proposalPrice_; + + void registerContractFunctions() override; + void onlyPlayer() const; + public: + using ConstructorArguments = std::tuple<>; + + BTVProposals(const Address& address, const DB& db); + BTVProposals( + const Address &address, const Address &creator, const uint64_t &chainId + ); + + void createProposal(const std::string& title, const std::string& description); // From + void voteOnProposal(const uint64_t& tokenId, const uint64_t& proposalId, const uint256_t& energy); + void removeVote(const uint64_t& tokenId, const uint64_t& proposalId, const uint256_t& energy); + + void completeProposal(const uint64_t& proposalId); + + void setProposalPrice(const uint256_t& price); + void setPlayerContract(const Address& playerContract); + void setEnergyContract(const Address& energyContract); + + std::vector getActiveProposals() const; + std::vector getCompletedProposals() const; + std::vector> getProposalVotes(const uint64_t& proposalId) const; + uint256_t getProposalPrice() const; + uint256_t getProposalEnergy(const uint64_t& proposalId) const; + uint64_t getProposalCount() const; + + /// Register contract class via ContractReflectionInterface. + static void registerContract() { + static std::once_flag once; + std::call_once(once, [](){ + DynamicContract::registerContractMethods( + std::vector{}, + std::make_tuple("createProposal", &BTVProposals::createProposal, FunctionTypes::NonPayable, std::vector{"title", "description"}), + std::make_tuple("voteOnProposal", &BTVProposals::voteOnProposal, FunctionTypes::NonPayable, std::vector{"tokenId", "proposalId", "energy"}), + std::make_tuple("removeVote", &BTVProposals::removeVote, FunctionTypes::NonPayable, std::vector{"tokenId", "proposalId", "energy"}), + std::make_tuple("completeProposal", &BTVProposals::completeProposal, FunctionTypes::NonPayable, std::vector{"proposalId"}), + std::make_tuple("setProposalPrice", &BTVProposals::setProposalPrice, FunctionTypes::NonPayable, std::vector{"price"}), + std::make_tuple("setPlayerContract", &BTVProposals::setPlayerContract, FunctionTypes::NonPayable, std::vector{"playerContract"}), + std::make_tuple("setEnergyContract", &BTVProposals::setEnergyContract, FunctionTypes::NonPayable, std::vector{"energyContract"}), + std::make_tuple("getActiveProposals", &BTVProposals::getActiveProposals, FunctionTypes::View, std::vector{}), + std::make_tuple("getCompletedProposals", &BTVProposals::getCompletedProposals, FunctionTypes::View, std::vector{}), + std::make_tuple("getProposalVotes", &BTVProposals::getProposalVotes, FunctionTypes::View, std::vector{"proposalId"}), + std::make_tuple("getProposalPrice", &BTVProposals::getProposalPrice, FunctionTypes::View, std::vector{}), + std::make_tuple("getProposalEnergy", &BTVProposals::getProposalEnergy, FunctionTypes::View, std::vector{"proposalId"}), + std::make_tuple("getProposalCount", &BTVProposals::getProposalCount, FunctionTypes::View, std::vector{}) + ); + }); + } + + DBBatch dump() const override; +}; + + +#endif // BTVPROPOSALS_H + diff --git a/src/contract/templates/buildthevoid.cpp b/src/contract/templates/buildthevoid.cpp new file mode 100644 index 00000000..ac32309f --- /dev/null +++ b/src/contract/templates/buildthevoid.cpp @@ -0,0 +1,509 @@ +#include "buildthevoid.h" +#include "btvplayer.h" +#include "btvenergy.h" + +BuildTheVoid::BuildTheVoid(const Address &address, const DB &db) : + DynamicContract(address, db), + Ownable(address, db), + playerContract_(this), + energyContract_(this), + activePlayers_(this), + inactivePlayers_(this), + deadPlayers_(this), + surfaceBlocks_(this), + energyBlockCounter_(this), + world_(this) { + + this->playerContract_ = Address(db.get(std::string("playerContract_"), this->getDBPrefix())); + this->energyContract_ = Address(db.get(std::string("energyContract_"), this->getDBPrefix())); + + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("activePlayers_"))) { + BTVUtils::PlayerInformation player; + View value(dbEntry.value); + player.position.x = IntConv::bytesToInt32(value.subspan(0, 4)); + player.position.y = IntConv::bytesToInt32(value.subspan(4, 4)); + player.position.z = IntConv::bytesToInt32(value.subspan(8, 4)); + player.energy = UintConv::bytesToUint256(value.subspan(12, 32)); + player.lastUpdate = UintConv::bytesToUint64(value.subspan(44, 8)); + this->activePlayers_[UintConv::bytesToUint64(dbEntry.key)] = player; + } + + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("inactivePlayers_"))) { + BTVUtils::PlayerInformation player; + View value(dbEntry.value); + player.position.x = IntConv::bytesToInt32(value.subspan(0, 4)); + player.position.y = IntConv::bytesToInt32(value.subspan(4, 4)); + player.position.z = IntConv::bytesToInt32(value.subspan(8, 4)); + player.energy = UintConv::bytesToUint256(value.subspan(12, 32)); + player.lastUpdate = UintConv::bytesToUint64(value.subspan(44, 8)); + this->inactivePlayers_[UintConv::bytesToUint64(dbEntry.key)] = player; + } + + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("deadPlayers_"))) { + BTVUtils::PlayerInformation player; + View value(dbEntry.value); + player.position.x = IntConv::bytesToInt32(value.subspan(0, 4)); + player.position.y = IntConv::bytesToInt32(value.subspan(4, 4)); + player.position.z = IntConv::bytesToInt32(value.subspan(8, 4)); + player.energy = UintConv::bytesToUint256(value.subspan(12, 32)); + player.lastUpdate = UintConv::bytesToUint64(value.subspan(44, 8)); + this->deadPlayers_[UintConv::bytesToUint64(dbEntry.key)] = player; + } + + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("surfaceBlocks_"))) { + View keyView(dbEntry.key); + BTVUtils::WorldBlockPos blockPos; + blockPos.x = IntConv::bytesToInt32(keyView.subspan(0, 4)); + blockPos.y = IntConv::bytesToInt32(keyView.subspan(4, 4)); + blockPos.z = IntConv::bytesToInt32(keyView.subspan(8, 4)); + this->surfaceBlocks_.push_back(blockPos); + } + + this->energyBlockCounter_ = UintConv::bytesToUint64(db.get(std::string("energyBlockCounter_"), this->getDBPrefix())); + + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("world_"))) { + View keyView(dbEntry.key); + BTVUtils::ChunkCoord2D chunkCoords; + chunkCoords.first = IntConv::bytesToInt32(keyView.subspan(0, 4)); + chunkCoords.second = IntConv::bytesToInt32(keyView.subspan(4, 4)); + this->world_.getChunks()[chunkCoords] = BTVUtils::Chunk::deserialize(dbEntry.value); + } + + this->playerContract_.commit(); + this->energyContract_.commit(); + this->activePlayers_.commit(); + this->inactivePlayers_.commit(); + this->deadPlayers_.commit(); + this->energyBlockCounter_.commit(); + this->surfaceBlocks_.commit(); + this->registerContractFunctions(); + this->playerContract_.enableRegister(); + this->energyContract_.enableRegister(); + this->activePlayers_.enableRegister(); + this->inactivePlayers_.enableRegister(); + this->deadPlayers_.enableRegister(); + this->energyBlockCounter_.enableRegister(); + this->surfaceBlocks_.enableRegister(); + this->world_.commitAndEnable(); +} + + +BuildTheVoid::BuildTheVoid(const Address &address, const Address &creator, const uint64_t &chainId) : + DynamicContract("BuildTheVoid", address, creator, chainId), + Ownable("BuildTheVoid", creator, address, creator, chainId), + playerContract_(this), + energyContract_(this), + activePlayers_(this), + inactivePlayers_(this), + deadPlayers_(this), + surfaceBlocks_(this), + energyBlockCounter_(this), + world_(this) { + +#ifdef BUILD_TESTNET + if (creator != Address(Hex::toBytes("0xc2f2ba5051975004171e6d4781eeda927e884024"))) { + throw DynamicException("Only the Chain Owner can create this contract"); + } +#endif + + // We need to fill the surfaceBlocks_ vector with the surface blocks + // it is a 10x10 area at y=5 + for (int x = 0; x < 10; x++) { + for (int z = 0; z < 10; z++) { + BTVUtils::WorldBlockPos blockPos; + blockPos.x = x; + blockPos.y = 5; + blockPos.z = z; + this->surfaceBlocks_.push_back(blockPos); + } + } + + this->playerContract_.commit(); + this->energyContract_.commit(); + this->activePlayers_.commit(); + this->inactivePlayers_.commit(); + this->deadPlayers_.commit(); + this->energyBlockCounter_.commit(); + this->surfaceBlocks_.commit(); + this->registerContractFunctions(); + this->playerContract_.enableRegister(); + this->energyContract_.enableRegister(); + this->activePlayers_.enableRegister(); + this->inactivePlayers_.enableRegister(); + this->deadPlayers_.enableRegister(); + this->energyBlockCounter_.enableRegister(); + this->surfaceBlocks_.enableRegister(); + this->world_.commitAndEnable(); +} + +void BuildTheVoid::registerContractFunctions() { + this->registerMemberFunctions( + std::make_tuple("setPlayerContract", &BuildTheVoid::setPlayerContract, FunctionTypes::NonPayable, this), + std::make_tuple("setEnergyContract", &BuildTheVoid::setEnergyContract, FunctionTypes::NonPayable, this), + std::make_tuple("forceUpdate", &BuildTheVoid::forceUpdate, FunctionTypes::NonPayable, this), + std::make_tuple("approve", &BuildTheVoid::approve, FunctionTypes::NonPayable, this), + std::make_tuple("loginPlayer", &BuildTheVoid::loginPlayer, FunctionTypes::NonPayable, this), + std::make_tuple("logoutPlayer", &BuildTheVoid::logoutPlayer, FunctionTypes::NonPayable, this), + std::make_tuple("changeBlock", &BuildTheVoid::changeBlock, FunctionTypes::NonPayable, this), + std::make_tuple("movePlayer", &BuildTheVoid::movePlayer, FunctionTypes::NonPayable, this), + std::make_tuple("claimEnergy", &BuildTheVoid::claimEnergy, FunctionTypes::NonPayable, this), + std::make_tuple("getChunk", &BuildTheVoid::getChunk, FunctionTypes::View, this), + std::make_tuple("getPlayerContract", &BuildTheVoid::getPlayerContract, FunctionTypes::View, this), + std::make_tuple("getEnergyContract", &BuildTheVoid::getEnergyContract, FunctionTypes::View, this), + std::make_tuple("getActivePlayers", &BuildTheVoid::getActivePlayers, FunctionTypes::View, this), + std::make_tuple("getInnactivePlayers", &BuildTheVoid::getInnactivePlayers, FunctionTypes::View, this), + std::make_tuple("getDeadPlayers", &BuildTheVoid::getDeadPlayers, FunctionTypes::View, this), + std::make_tuple("getPlayerStatus", &BuildTheVoid::getPlayerStatus, FunctionTypes::View, this) + ); + this->registerBlockObserver("selfcallUpdate", 1, &BuildTheVoid::selfcallUpdate, this); +} + +void BuildTheVoid::approve() { + this->onlyOwner(); + this->callContractFunction(this->energyContract_.get(), &ERC20::approve, this->playerContract_.get(), std::numeric_limits::max()); +} + +void BuildTheVoid::internalKillPlayer() { + // We loop the active players, and check if they were unnactive for at least 5 seconds + // If the player was innactive for 5 seconds, and there is no non-air underlaying block, we kill the player + // TODO: I believe this is better implemented by creating a vector of "possible players to kill" + // When a player moves (BuildTheVoid::movePlayer), we check if the underlying blocks are air + // If they are, they are added to the vector of "possible players to kill" and checked here. + return; +} + +void BuildTheVoid::internalLogoutPlayer() { + // TODO + // Almost the same as internalKillPlayer, but we don't kill the player + // we check if the last activity of the player was more than 30 seconds ago + // If it was, We need to move the player to the inactivePlayers_ map + // Not only that, but also call the BTVPlayer contract to add the energy to the player + // remember that this->getBlockTimestamp() returns the current block timestamp in microseconds + for (auto it = this->activePlayers_.cbegin(); it != this->activePlayers_.cend(); ++it) { + if (this->getBlockTimestamp() - it->second.lastUpdate > 30 * 1000000) { + this->inactivePlayers_[it->first] = it->second; + this->inactivePlayers_[it->first].energy = 0; + if (it->second.energy != 0) { + this->callContractFunction(this->playerContract_.get(), &BTVPlayer::addPlayerEnergy, it->first, it->second.energy); + } + this->activePlayers_.erase(it); + this->PlayerLogout(it->first); + } + } +} + +void BuildTheVoid::internalSpawnEnergyBlock() { + Utils::safePrint("Spawning energy block"); + // For every 100 blocks, we spawn an energy block + // The energy block is spawned on top of a surface block + // Surface blocks are described in the surfaceBlocks_ vector + uint64_t wantedEnergyBlocks = this->surfaceBlocks_.size() / 100; + Utils::safePrint("Wanted energy blocks: " + std::to_string(wantedEnergyBlocks)); + if (this->energyBlockCounter_.get() >= wantedEnergyBlocks) { + return; + } + Utils::safePrint("Energy block counter: " + std::to_string(this->energyBlockCounter_.get())); + for (uint64_t i = 0; i < wantedEnergyBlocks - this->energyBlockCounter_.get(); i++) { + uint64_t randomIndex = static_cast(this->getRandom() % this->surfaceBlocks_.size()); + Utils::safePrint("Trying to spawn a energy block at " + std::to_string(randomIndex)); + BTVUtils::WorldBlockPos blockPos = this->surfaceBlocks_[randomIndex]; + if (!this->world_.hasBlockOver(blockPos)) { + // Do not forget to add +1 to the Y position + blockPos.y += 1; + auto block = this->world_.getBlock(blockPos); + block->type = BTVUtils::BlockType::ENERGYCHEST; + block->modificationTimestamp = 0; + block->placer_ = std::nullopt; + ++this->energyBlockCounter_; + this->BlockChanged(std::numeric_limits::max(), blockPos.x, blockPos.y, blockPos.z, static_cast(BTVUtils::BlockType::ENERGYCHEST), this->getBlockTimestamp()); + } + } +} + +void BuildTheVoid::selfcallUpdate() { + this->internalKillPlayer(); + this->internalLogoutPlayer(); + this->internalSpawnEnergyBlock(); +} + +void BuildTheVoid::setEnergyContract(const Address& playerContract) { + this->onlyOwner(); + this->energyContract_ = playerContract; +} + +void BuildTheVoid::setPlayerContract(const Address& playerContract) { + this->onlyOwner(); + this->playerContract_ = playerContract; +} + +void BuildTheVoid::forceUpdate() { + this->onlyOwner(); + // this->selfcallUpdate(); +} + + +void BuildTheVoid::loginPlayer(const uint64_t& playerId, const uint256_t& energy) { + if (this->callContractViewFunction(this->playerContract_.get(), &BTVPlayer::ownerOf, static_cast(playerId)) != this->getCaller()) { + throw DynamicException("BuildTheVoid::loginPlayer: Not the owner of the player"); + } + if (this->deadPlayers_.contains(playerId)) { + throw DynamicException("BuildTheVoid::loginPlayer: Player is dead"); + } + if (energy != 0) { + this->callContractFunction(this->playerContract_.get(), &BTVPlayer::takePlayerEnergy, playerId, energy); + } + BTVUtils::PlayerInformation player; + auto it = this->inactivePlayers_.find(playerId); + if (it != this->inactivePlayers_.end()) { + // The player already existed in the inactivePlayers_ map + // Take off the player from the inactivePlayers_ map, add the energy and place it in the activePlayers_ map + player = it->second; + player.energy = energy; + player.lastUpdate = this->getBlockTimestamp(); + this->inactivePlayers_.erase(it); + this->activePlayers_[playerId] = player; + } else { + // The player is new, we need to create a new player + // Place the player above the 10x10 surface area + player.position.x = 4; + player.position.y = 6; + player.position.z = 4; + player.energy = energy; + player.lastUpdate = this->getBlockTimestamp(); + this->activePlayers_[playerId] = player; + } + + this->PlayerLogin(playerId, player.position.x, player.position.y, player.position.z); + return; +} + + +void BuildTheVoid::logoutPlayer(const uint64_t &playerId) { + if (this->callContractViewFunction(this->playerContract_.get(), &BTVPlayer::ownerOf, static_cast(playerId)) != this->getCaller()) { + throw DynamicException("BuildTheVoid::logoutPlayer: Not the owner of the player"); + } + if (this->deadPlayers_.contains(playerId)) { + throw DynamicException("BuildTheVoid::logoutPlayer: Player is dead"); + } + auto player = this->activePlayers_.find(playerId); + if (player == this->activePlayers_.end()) { + throw DynamicException("BuildTheVoid::logoutPlayer: Player is not active"); + } + if (player->second.energy != 0) { + this->callContractFunction(this->playerContract_.get(), &BTVPlayer::addPlayerEnergy, playerId, player->second.energy); + } + player->second.energy = 0; + player->second.lastUpdate = this->getBlockTimestamp(); + this->inactivePlayers_[playerId] = player->second; + this->activePlayers_.erase(player); + this->PlayerLogout(playerId); + return; +} + + +void BuildTheVoid::changeBlock(const uint64_t& playerId, const int32_t& x, const int32_t& y, const int32_t& z, const BTVUtils::BlockType& type) { + if (this->callContractViewFunction(this->playerContract_.get(), &BTVPlayer::ownerOf, static_cast(playerId)) != this->getCaller()) { + throw DynamicException("BuildTheVoid::logoutPlayer: Not the owner of the player"); + } + auto it = this->activePlayers_.find(playerId); + if (it == this->activePlayers_.cend()) { + throw DynamicException("BuildTheVoid::changeBlock: Player is not active"); + } + // Get the block itself + auto block = this->world_.getBlock(BTVUtils::WorldBlockPos{x, y, z}); + if (block->type == BTVUtils::BlockType::SURFACE || block->type == BTVUtils::BlockType::ENERGYCHEST) { + throw DynamicException("BuildTheVoid::changeBlock: Cannot change a surface or energy block"); + } + // Check if the block has ownership + if (block->placer_.has_value()) { + // Only allow the placer to change the block if the last modification was over 15 minutes ago + if (this->getBlockTimestamp() - block->modificationTimestamp < (15 * 60 * 1000000)) { + if (block->placer_.value() != playerId) { + throw DynamicException("BuildTheVoid::changeBlock: Block is owned by another player"); + } + } + } + // The player needs at least 1 energy (18 decimals do not forget) + if (it->second.energy < 1000000000000000000) { + throw DynamicException("BuildTheVoid::changeBlock: Player has no energy"); + } + // Check if the block is within placing range (7) from the player + if (BTVUtils::isBlockClose(BTVUtils::WorldBlockPos{x, y, z}, it->second.position, 7)) { + block->type = type; + block->placer_ = playerId; + block->modificationTimestamp = this->getBlockTimestamp(); + this->BlockChanged(playerId, x, y, z, static_cast(type), block->modificationTimestamp); + if (type == BTVUtils::BlockType::SURFACE) { + this->surfaceBlocks_.push_back(BTVUtils::WorldBlockPos{x, y, z}); + } + // Take 1 energy from the player + it->second.energy -= 1000000000000000000; + it->second.lastUpdate = this->getBlockTimestamp(); + } else { + throw DynamicException("BuildTheVoid::changeBlock: Block is too far away"); + } +} + +void BuildTheVoid::movePlayer(const uint64_t &playerId, const int32_t& x, const int32_t& y, const int32_t& z) { + if (this->callContractViewFunction(this->playerContract_.get(), &BTVPlayer::ownerOf, static_cast(playerId)) != this->getCaller()) { + throw DynamicException("BuildTheVoid::movePlayer: Not the owner of the player"); + } + auto it = this->activePlayers_.find(playerId); + if (it == this->activePlayers_.cend()) { + throw DynamicException("BuildTheVoid::movePlayer: Player is not active"); + } + // Check if the player is moving to a valid position (7 blocks away) + if (BTVUtils::isBlockClose(BTVUtils::WorldBlockPos{x, y, z}, it->second.position, 7)) { + it->second.position.x = x; + it->second.position.y = y; + it->second.position.z = z; + it->second.lastUpdate = this->getBlockTimestamp(); + this->PlayerMoved(playerId, x, y, z); + } else { + throw DynamicException("BuildTheVoid::movePlayer: Player is moving too far away"); + } +} + +void BuildTheVoid::claimEnergy(const uint64_t &playerId, const int32_t& x, const int32_t& y, const int32_t& z) { + if (this->callContractViewFunction(this->playerContract_.get(), &BTVPlayer::ownerOf, static_cast(playerId)) != this->getCaller()) { + throw DynamicException("BuildTheVoid::movePlayer: Not the owner of the player"); + } + auto it = this->activePlayers_.find(playerId); + if (it == this->activePlayers_.cend()) { + throw DynamicException("BuildTheVoid::claimEnergy: Player is not active"); + } + auto block = this->world_.getBlock(BTVUtils::WorldBlockPos{x, y, z}); + if (block->type != BTVUtils::BlockType::ENERGYCHEST) { + throw DynamicException("BuildTheVoid::claimEnergy: Block is not an energy block"); + } + // Check if the player is actually close to the energy block + if (BTVUtils::isBlockClose(BTVUtils::WorldBlockPos{x, y, z}, it->second.position, 7)) { + // If the block is a energy type and the user is close, we can safely claim the energy + // The energy value is something between 1 and 10 (including decimals), do not forget the 18 decimals of the token! + // We need to POW the energy value by 10^18 + auto randomEnergyValue = static_cast(this->getRandom() % uint256_t("10000000000000000000") + uint256_t("1000000000000000000")); + block->type = BTVUtils::BlockType::AIR; + block->placer_ = std::nullopt; + block->modificationTimestamp = this->getBlockTimestamp(); + it->second.energy += randomEnergyValue; + it->second.lastUpdate = this->getBlockTimestamp(); + std::cout << "energyBlockCounter_: " << this->energyBlockCounter_.get() << std::endl; + --this->energyBlockCounter_; + // Do not forget to mint the ERC20 tokens to ourselves! players playing the game have their energy stored in the contract + // and it is sent back when they logout + this->callContractFunction(this->energyContract_.get(), &BTVEnergy::mint, this->getContractAddress(), randomEnergyValue); + this->ClaimedEnergy(playerId, randomEnergyValue); + this->BlockChanged(playerId, x, y, z, static_cast(BTVUtils::BlockType::AIR), block->modificationTimestamp); + } else { + throw DynamicException("BuildTheVoid::claimEnergy: Player is too far away from the energy block"); + } +} + +Bytes BuildTheVoid::getChunk(const int32_t& cx, const int32_t& cy) const { + auto chunk = this->world_.getChunk({cx, cy}); + if (chunk != nullptr) { + return chunk->serialize(); + } + return {}; +} + +Address BuildTheVoid::getPlayerContract() const { + return this->playerContract_.get(); +} + +Address BuildTheVoid::getEnergyContract() const { + return this->energyContract_.get(); +} + +std::vector BuildTheVoid::getActivePlayers() const { + std::vector players; + for (auto it = this->activePlayers_.cbegin(); it != this->activePlayers_.cend(); ++it) { + players.push_back(std::make_tuple(it->first, std::make_tuple(it->second.position.x, it->second.position.y, it->second.position.z), it->second.energy, it->second.lastUpdate)); + } + return players; +} + +std::vector BuildTheVoid::getInnactivePlayers() const { + std::vector players; + for (auto it = this->inactivePlayers_.cbegin(); it != this->inactivePlayers_.cend(); ++it) { + players.push_back(std::make_tuple(it->first, std::make_tuple(it->second.position.x, it->second.position.y, it->second.position.z), it->second.energy, it->second.lastUpdate)); + } + return players; +} + +std::vector BuildTheVoid::getDeadPlayers() const { + std::vector players; + for (auto it = this->deadPlayers_.cbegin(); it != this->deadPlayers_.cend(); ++it) { + players.push_back(std::make_tuple(it->first, std::make_tuple(it->second.position.x, it->second.position.y, it->second.position.z), it->second.energy, it->second.lastUpdate)); + } + return players; +} + +BTVUtils::PlayerStatus BuildTheVoid::getPlayerStatus(const uint64_t &playerId) const { + if (this->activePlayers_.contains(playerId)) { + return BTVUtils::PlayerStatus::ACTIVE; + } + if (this->inactivePlayers_.contains(playerId)) { + return BTVUtils::PlayerStatus::INACTIVE; + } + if (this->deadPlayers_.contains(playerId)) { + return BTVUtils::PlayerStatus::DEAD; + } + return BTVUtils::PlayerStatus::NEVER_JOINED; +} + + +DBBatch BuildTheVoid::dump() const { + DBBatch dbBatch = Ownable::dump(); + dbBatch.push_back(StrConv::stringToBytes("energyContract_"), this->energyContract_.get(), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("playerContract_"), this->playerContract_.get(), this->getDBPrefix()); + + for (auto it = this->activePlayers_.cbegin(); it != this->activePlayers_.cend(); ++it) { + Bytes value; + Utils::appendBytes(value, IntConv::int32ToBytes(it->second.position.x)); + Utils::appendBytes(value, IntConv::int32ToBytes(it->second.position.y)); + Utils::appendBytes(value, IntConv::int32ToBytes(it->second.position.z)); + Utils::appendBytes(value, UintConv::uint256ToBytes(it->second.energy)); + Utils::appendBytes(value, UintConv::uint64ToBytes(it->second.lastUpdate)); + dbBatch.push_back(UintConv::uint64ToBytes(it->first), value, this->getNewPrefix("activePlayers_")); + } + + for (auto it = this->inactivePlayers_.cbegin(); it != this->inactivePlayers_.cend(); ++it) { + Bytes value; + Utils::appendBytes(value, IntConv::int32ToBytes(it->second.position.x)); + Utils::appendBytes(value, IntConv::int32ToBytes(it->second.position.y)); + Utils::appendBytes(value, IntConv::int32ToBytes(it->second.position.z)); + Utils::appendBytes(value, UintConv::uint256ToBytes(it->second.energy)); + Utils::appendBytes(value, UintConv::uint64ToBytes(it->second.lastUpdate)); + dbBatch.push_back(UintConv::uint64ToBytes(it->first), value, this->getNewPrefix("inactivePlayers_")); + } + + for (auto it = this->deadPlayers_.cbegin(); it != this->deadPlayers_.cend(); ++it) { + Bytes value; + Utils::appendBytes(value, IntConv::int32ToBytes(it->second.position.x)); + Utils::appendBytes(value, IntConv::int32ToBytes(it->second.position.y)); + Utils::appendBytes(value, IntConv::int32ToBytes(it->second.position.z)); + Utils::appendBytes(value, UintConv::uint256ToBytes(it->second.energy)); + Utils::appendBytes(value, UintConv::uint64ToBytes(it->second.lastUpdate)); + dbBatch.push_back(UintConv::uint64ToBytes(it->first), value, this->getNewPrefix("deadPlayers_")); + } + + for (auto it = this->surfaceBlocks_.cbegin(); it != this->surfaceBlocks_.cend(); ++it) { + Bytes key; + Utils::appendBytes(key, IntConv::int32ToBytes(it->x)); + Utils::appendBytes(key, IntConv::int32ToBytes(it->y)); + Utils::appendBytes(key, IntConv::int32ToBytes(it->z)); + dbBatch.push_back(key, Bytes(), this->getNewPrefix("surfaceBlocks_")); + } + + dbBatch.push_back(StrConv::stringToBytes("energyBlockCounter_"), UintConv::uint64ToBytes(this->energyBlockCounter_.get()), this->getDBPrefix()); + + const auto& chunks = this->world_.getChunks(); + for (auto it = chunks.cbegin(); it != chunks.cend(); ++it) { + Bytes key; + Utils::appendBytes(key, IntConv::int32ToBytes(it->first.first)); + Utils::appendBytes(key, IntConv::int32ToBytes(it->first.second)); + dbBatch.push_back(key, it->second.serialize(), this->getNewPrefix("world_")); + } + return dbBatch; +} diff --git a/src/contract/templates/buildthevoid.h b/src/contract/templates/buildthevoid.h new file mode 100644 index 00000000..dbbff00c --- /dev/null +++ b/src/contract/templates/buildthevoid.h @@ -0,0 +1,128 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef BUILDTHEVOID_H +#define BUILDTHEVOID_H + + +#include "btvcommon.h" +#include "ownable.h" +#include "../../utils/db.h" +#include "../../utils/utils.h" +#include "../dynamiccontract.h" +#include "../variables/safeaddress.h" +#include "../variables/safeunorderedmap.h" +#include "contract/variables/safevector.h" + +/// Template for an ERC20 contract. +class BuildTheVoid : virtual public DynamicContract, virtual public Ownable { + private: + SafeAddress playerContract_; + SafeAddress energyContract_; + SafeUnorderedMap activePlayers_; + SafeUnorderedMap inactivePlayers_; + SafeUnorderedMap deadPlayers_; + SafeVector surfaceBlocks_; // Used to generate energy blocks on top of. + SafeUint64_t energyBlockCounter_; + BTVUtils::World world_; + + void internalLogoutPlayer(); // Used by the self-calling methods + void internalKillPlayer(); // Used by the self-calling methods + void internalSpawnEnergyBlock(); + void selfcallUpdate(); // Used by the self-calling methods + + + /// Function for calling the register functions for contracts. + void registerContractFunctions() override; + + public: + using ConstructorArguments = std::tuple<>; + + BuildTheVoid(const Address& address, const DB& db); + + BuildTheVoid(const Address &address, const Address &creator, const uint64_t &chainId); + + // Event + void PlayerMoved(const EventParam& playerId, const EventParam& x, const EventParam& y, const EventParam& z) { + this->emitEvent("PlayerMoved", std::make_tuple(playerId, x, y, z)); + } + void PlayerLogin(const EventParam& playerId, const EventParam& x, const EventParam& y, const EventParam& z) { + this->emitEvent("PlayerLogin", std::make_tuple(playerId, x, y, z)); + } + void PlayerLogout(const EventParam& playerId) { + this->emitEvent("PlayerLogout", std::make_tuple(playerId)); + } + void BlockChanged(const EventParam& playerId, const EventParam& x, const EventParam& y, const EventParam& z, const EventParam& blockType, const EventParam& timestamp) { + this->emitEvent("BlockChanged", std::make_tuple(playerId, x, y, z, blockType, timestamp)); + } + void ClaimedEnergy(const EventParam& playerId, const EventParam& value) { + this->emitEvent("ClaimedEnergy", std::make_tuple(playerId, value)); + } + void PlayerDead(const EventParam& playerId) { + this->emitEvent("PlayerDead", std::make_tuple(playerId)); + } + + // Admin methods + void setPlayerContract(const Address& playerContract); + void setEnergyContract(const Address& energyContract); + void forceUpdate(); + void approve(); + + // Client methods + void loginPlayer(const uint64_t& playerId, const uint256_t& energy); + void logoutPlayer(const uint64_t& playerId); + void changeBlock(const uint64_t& playerId, const int32_t& x, const int32_t& y, const int32_t& z, const BTVUtils::BlockType& type); + void movePlayer(const uint64_t& playerId, const int32_t& x, const int32_t& y, const int32_t& z); + void claimEnergy(const uint64_t& playerId, const int32_t& x, const int32_t& y, const int32_t& z); + Bytes getChunk(const int32_t& cx, const int32_t& cy) const; + + Address getPlayerContract() const; + Address getEnergyContract() const; + std::vector getActivePlayers() const; + std::vector getInnactivePlayers() const; + std::vector getDeadPlayers() const; + BTVUtils::PlayerStatus getPlayerStatus(const uint64_t& playerId) const; + + /// Register contract class via ContractReflectionInterface. + static void registerContract() { + static std::once_flag once; + std::call_once (once, [](){ + DynamicContract::registerContractMethods( + std::vector{}, + std::make_tuple("setPlayerContract", &BuildTheVoid::setPlayerContract, FunctionTypes::NonPayable, std::vector{"playerContract"}), + std::make_tuple("setEnergyContract", &BuildTheVoid::setEnergyContract, FunctionTypes::NonPayable, std::vector{"energyContract"}), + std::make_tuple("forceUpdate", &BuildTheVoid::forceUpdate, FunctionTypes::NonPayable, std::vector{}), + std::make_tuple("approve", &BuildTheVoid::approve, FunctionTypes::NonPayable, std::vector{}), + std::make_tuple("loginPlayer", &BuildTheVoid::loginPlayer, FunctionTypes::NonPayable, std::vector{"playerId"}), + std::make_tuple("logoutPlayer", &BuildTheVoid::logoutPlayer, FunctionTypes::NonPayable, std::vector{"playerId"}), + std::make_tuple("changeBlock", &BuildTheVoid::changeBlock, FunctionTypes::NonPayable, std::vector{"playerId", "x", "y", "z", "type"}), + std::make_tuple("movePlayer", &BuildTheVoid::movePlayer, FunctionTypes::NonPayable, std::vector{"playerId", "x", "y", "z"}), + std::make_tuple("claimEnergy", &BuildTheVoid::claimEnergy, FunctionTypes::NonPayable, std::vector{"playerId", "x", "y", "z"}), + std::make_tuple("getChunk", &BuildTheVoid::getChunk, FunctionTypes::View, std::vector{"cx", "cy"}), + std::make_tuple("getPlayerContract", &BuildTheVoid::getPlayerContract, FunctionTypes::View, std::vector{}), + std::make_tuple("getEnergyContract", &BuildTheVoid::getEnergyContract, FunctionTypes::View, std::vector{}), + std::make_tuple("getActivePlayers", &BuildTheVoid::getActivePlayers, FunctionTypes::View, std::vector{}), + std::make_tuple("getInnactivePlayers", &BuildTheVoid::getInnactivePlayers, FunctionTypes::View, std::vector{}), + std::make_tuple("getDeadPlayers", &BuildTheVoid::getDeadPlayers, FunctionTypes::View, std::vector{}), + std::make_tuple("getPlayerStatus", &BuildTheVoid::getPlayerStatus, FunctionTypes::View, std::vector{"playerId"}), + std::make_tuple("selfcallUpdate", &BuildTheVoid::selfcallUpdate, FunctionTypes::NonPayable, std::vector{}) + ); + ContractReflectionInterface::registerContractEvents( + std::make_tuple("PlayerMoved", false, &BuildTheVoid::PlayerMoved, std::vector{"playerId", "x", "y", "z"}), + std::make_tuple("PlayerLogin", false, &BuildTheVoid::PlayerLogin, std::vector{"playerId", "x", "y", "z"}), + std::make_tuple("PlayerLogout", false, &BuildTheVoid::PlayerLogout, std::vector{"playerId"}), + std::make_tuple("BlockChanged", false, &BuildTheVoid::BlockChanged, std::vector{"playerId", "x", "y", "z", "blockType", "timestamp"}), + std::make_tuple("ClaimedEnergy", false, &BuildTheVoid::ClaimedEnergy, std::vector{"playerId", "value"}), + std::make_tuple("PlayerDead", false, &BuildTheVoid::PlayerDead, std::vector{"playerId"}) + ); + }); + } + + DBBatch dump() const override; +}; + +#endif // BUILDTHEVOID_H \ No newline at end of file diff --git a/src/contract/templates/dexv2/dexv2factory.cpp b/src/contract/templates/dexv2/dexv2factory.cpp index ac051512..25c172dc 100644 --- a/src/contract/templates/dexv2/dexv2factory.cpp +++ b/src/contract/templates/dexv2/dexv2factory.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,72 +8,72 @@ See the LICENSE.txt file in the project root for more information. #include "dexv2factory.h" #include "dexv2pair.h" -DEXV2Factory::DEXV2Factory( - ContractManagerInterface &interface, const Address &address, const std::unique_ptr &db -) : DynamicContract(interface, address, db), feeTo_(this), feeToSetter_(this), +#include "../../../utils/uintconv.h" +#include "../../../utils/strconv.h" + +DEXV2Factory::DEXV2Factory(const Address &address, const DB& db +) : DynamicContract(address, db), feeTo_(this), feeToSetter_(this), allPairs_(this), getPair_(this) { - // Load from DB constructor - this->feeTo_ = Address(db->get(std::string("feeTo_"), this->getDBPrefix())); - this->feeToSetter_ = Address(db->get(std::string("feeToSetter_"), this->getDBPrefix())); - std::vector allPairs = db->getBatch(this->getNewPrefix("allPairs_")); - for (const auto& dbEntry : allPairs) this->allPairs_.push_back(Address(dbEntry.value)); - std::vector getPairs = db->getBatch(this->getNewPrefix("getPair_")); - for (const auto& dbEntry : getPairs) { - BytesArrView valueView(dbEntry.value); + this->feeTo_ = Address(db.get(std::string("feeTo_"), this->getDBPrefix())); + this->feeToSetter_ = Address(db.get(std::string("feeToSetter_"), this->getDBPrefix())); + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("allPairs_"))) { + this->allPairs_.push_back(Address(dbEntry.value)); + } + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("getPair_"))) { + View valueView(dbEntry.value); this->getPair_[Address(dbEntry.key)][Address(valueView.subspan(0, 20))] = Address(valueView.subspan(20)); } - this->registerContractFunctions(); + this->feeTo_.commit(); this->feeToSetter_.commit(); this->allPairs_.commit(); this->getPair_.commit(); + + this->registerContractFunctions(); + + this->feeTo_.enableRegister(); + this->feeToSetter_.enableRegister(); + this->allPairs_.enableRegister(); + this->getPair_.enableRegister(); } DEXV2Factory::DEXV2Factory( const Address& feeToSetter, - ContractManagerInterface &interface, - const Address &address, const Address &creator, const uint64_t &chainId, - const std::unique_ptr &db -) : DynamicContract(interface, "DEXV2Factory", address, creator, chainId, db), + const Address &address, const Address &creator, const uint64_t &chainId +) : DynamicContract("DEXV2Factory", address, creator, chainId), feeTo_(this), feeToSetter_(this), allPairs_(this), getPair_(this) { - // Create new constructor this->feeToSetter_ = feeToSetter; + + this->feeTo_.commit(); this->feeToSetter_.commit(); + this->allPairs_.commit(); + this->getPair_.commit(); + this->registerContractFunctions(); -} -DEXV2Factory::~DEXV2Factory() { - DBBatch batchOperations; - batchOperations.push_back(Utils::stringToBytes("feeTo_"), this->feeTo_.get().view_const(), this->getDBPrefix()); - batchOperations.push_back(Utils::stringToBytes("feeToSetter_"), this->feeToSetter_.get().view_const(), this->getDBPrefix()); - uint32_t index = 0; - for (const auto& address : this->allPairs_.get()) batchOperations.push_back( - Utils::uint32ToBytes(index), address.view_const(), this->getNewPrefix("allPairs_") - ); - for (auto tokenA = this->getPair_.cbegin(); tokenA != this->getPair_.cend(); tokenA++) { - for (auto tokenB = tokenA->second.cbegin(); tokenB != tokenA->second.cend(); tokenB++) { - const auto& key = tokenA->first.get(); - Bytes value = tokenB->first.asBytes(); - Utils::appendBytes(value, tokenB->second.asBytes()); - batchOperations.push_back(key, value, this->getNewPrefix("getPair_")); - } - } - this->db_->putBatch(batchOperations); + this->feeTo_.enableRegister(); + this->feeToSetter_.enableRegister(); + this->allPairs_.enableRegister(); + this->getPair_.enableRegister(); } +DEXV2Factory::~DEXV2Factory() {}; + void DEXV2Factory::registerContractFunctions() { registerContract(); - this->registerMemberFunction("feeTo", &DEXV2Factory::feeTo, FunctionTypes::View, this); - this->registerMemberFunction("feeToSetter", &DEXV2Factory::feeToSetter, FunctionTypes::View, this); - this->registerMemberFunction("allPairs", &DEXV2Factory::allPairs, FunctionTypes::View, this); - this->registerMemberFunction("allPairsLength", &DEXV2Factory::allPairsLength, FunctionTypes::View, this); - this->registerMemberFunction("getPair", &DEXV2Factory::getPair, FunctionTypes::View, this); - this->registerMemberFunction("getPairByIndex", &DEXV2Factory::getPairByIndex, FunctionTypes::View, this); - this->registerMemberFunction("createPair", &DEXV2Factory::createPair, FunctionTypes::NonPayable, this); - this->registerMemberFunction("setFeeTo", &DEXV2Factory::setFeeTo, FunctionTypes::NonPayable, this); - this->registerMemberFunction("setFeeToSetter", &DEXV2Factory::setFeeToSetter, FunctionTypes::NonPayable, this); + this->registerMemberFunctions( + std::make_tuple("feeTo", &DEXV2Factory::feeTo, FunctionTypes::View, this), + std::make_tuple("feeToSetter", &DEXV2Factory::feeToSetter, FunctionTypes::View, this), + std::make_tuple("allPairs", &DEXV2Factory::allPairs, FunctionTypes::View, this), + std::make_tuple("allPairsLength", &DEXV2Factory::allPairsLength, FunctionTypes::View, this), + std::make_tuple("getPair", &DEXV2Factory::getPair, FunctionTypes::View, this), + std::make_tuple("getPairByIndex", &DEXV2Factory::getPairByIndex, FunctionTypes::View, this), + std::make_tuple("createPair", &DEXV2Factory::createPair, FunctionTypes::NonPayable, this), + std::make_tuple("setFeeTo", &DEXV2Factory::setFeeTo, FunctionTypes::NonPayable, this), + std::make_tuple("setFeeToSetter", &DEXV2Factory::setFeeToSetter, FunctionTypes::NonPayable, this) + ); } Address DEXV2Factory::feeTo() const { return this->feeTo_.get(); } @@ -85,10 +85,8 @@ std::vector
DEXV2Factory::allPairs() const { return this->allPairs_.get uint64_t DEXV2Factory::allPairsLength() const { return this->allPairs_.size(); } Address DEXV2Factory::getPair(const Address& tokenA, const Address& tokenB) const { - auto it = this->getPair_.find(tokenA); - if (it != this->getPair_.end()) { - auto itt = it->second.find(tokenB); - if (itt != it->second.end()) return itt->second; + if (auto it = this->getPair_.find(tokenA); it != this->getPair_.cend()) { + if (auto itt = it->second.find(tokenB); itt != it->second.cend()) return itt->second; } return Address(); } @@ -99,14 +97,12 @@ Address DEXV2Factory::getPairByIndex(const uint64_t& index) const { } Address DEXV2Factory::createPair(const Address& tokenA, const Address& tokenB) { - if (tokenA == tokenB) throw std::runtime_error("DEXV2Factory::createPair: IDENTICAL_ADDRESSES"); + if (tokenA == tokenB) throw DynamicException("DEXV2Factory::createPair: IDENTICAL_ADDRESSES"); auto& token0 = (tokenA < tokenB) ? tokenA : tokenB; auto& token1 = (tokenA < tokenB) ? tokenB : tokenA; - if (token0 == Address()) throw std::runtime_error("DEXV2Factory::createPair: ZERO_ADDRESS"); - if (this->getPair(token0, token1) != Address()) throw std::runtime_error("DEXV2Factory::createPair: PAIR_EXISTS"); - Utils::safePrint("DEXV2Factory: creating pair..."); - auto pair = this->callCreateContract(0, 0, 0); - Utils::safePrint("DEXV2Factory: pair created..."); + if (token0 == Address()) throw DynamicException("DEXV2Factory::createPair: ZERO_ADDRESS"); + if (this->getPair(token0, token1) != Address()) throw DynamicException("DEXV2Factory::createPair: PAIR_EXISTS"); + auto pair = this->callCreateContract(); this->callContractFunction(pair, &DEXV2Pair::initialize, token0, token1); getPair_[token0][token1] = pair; getPair_[token1][token0] = pair; @@ -118,3 +114,27 @@ void DEXV2Factory::setFeeTo(const Address& feeTo) { this->feeTo_ = feeTo; } void DEXV2Factory::setFeeToSetter(const Address& feeToSetter) { this->feeToSetter_ = feeToSetter; } +DBBatch DEXV2Factory::dump() const { + DBBatch dbBatch = BaseContract::dump(); + uint32_t i = 0; + + dbBatch.push_back(StrConv::stringToBytes("feeTo_"), this->feeTo_.get().view(), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("feeToSetter_"), this->feeToSetter_.get().view(), this->getDBPrefix()); + + for (const auto& address : this->allPairs_.get()) { + dbBatch.push_back(UintConv::uint32ToBytes(i), address.view(), this->getNewPrefix("allPairs_")); + i++; + } + + for (auto tokenA = this->getPair_.cbegin(); tokenA != this->getPair_.cend(); tokenA++) { + for (auto tokenB = tokenA->second.cbegin(); tokenB != tokenA->second.cend(); tokenB++) { + const auto& key = tokenA->first; + Bytes value = tokenB->first.asBytes(); + Utils::appendBytes(value, tokenB->second.asBytes()); + dbBatch.push_back(key, value, this->getNewPrefix("getPair_")); + } + } + + return dbBatch; +} + diff --git a/src/contract/templates/dexv2/dexv2factory.h b/src/contract/templates/dexv2/dexv2factory.h index 4f17dc82..f2527fab 100644 --- a/src/contract/templates/dexv2/dexv2factory.h +++ b/src/contract/templates/dexv2/dexv2factory.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,7 +8,6 @@ See the LICENSE.txt file in the project root for more information. #ifndef DEXFACTORY_H #define DEXFACTORY_H -#include "../../../utils/contractreflectioninterface.h" #include "../../../utils/db.h" #include "../../abi.h" #include "../../dynamiccontract.h" @@ -31,7 +30,7 @@ class DEXV2Factory : public DynamicContract { SafeVector
allPairs_; /// Solidity: mapping(address => mapping(address => address)) public getPair; - SafeUnorderedMap> getPair_; + SafeUnorderedMap> getPair_; /// Function for calling the register functions for contracts. void registerContractFunctions() override; @@ -45,29 +44,21 @@ class DEXV2Factory : public DynamicContract { /** * Constructor for loading contract from DB. - * @param interface Reference to the contract manager interface. * @param address The address where the contract will be deployed. * @param db Reference to the database object. */ - DEXV2Factory( - ContractManagerInterface& interface, - const Address& address, const std::unique_ptr& db - ); + DEXV2Factory(const Address& address, const DB& db); /** * Constructor to be used when creating a new contract. * @param feeToSetter The address of the feeToSetter. - * @param interface Reference to the contract manager interface. * @param address The address where the contract will be deployed. * @param creator The address of the creator of the contract. * @param chainId The chain where the contract wil be deployed. - * @param db Reference to the database object. */ DEXV2Factory( const Address& feeToSetter, - ContractManagerInterface &interface, - const Address &address, const Address &creator, const uint64_t &chainId, - const std::unique_ptr &db + const Address &address, const Address &creator, const uint64_t &chainId ); // Destructor. @@ -108,23 +99,24 @@ class DEXV2Factory : public DynamicContract { /// Register the contract functions to the ContractReflectionInterface. static void registerContract() { - ContractReflectionInterface::registerContractMethods< - DEXV2Factory, const Address&, ContractManagerInterface &, - const Address &, const Address &, const uint64_t &, - const std::unique_ptr & - >( - std::vector{"_feeToSetter"}, - std::make_tuple("feeTo", &DEXV2Factory::feeTo, FunctionTypes::View, std::vector{}), - std::make_tuple("feeToSetter", &DEXV2Factory::feeToSetter, FunctionTypes::View, std::vector{}), - std::make_tuple("allPairs", &DEXV2Factory::allPairs, FunctionTypes::View, std::vector{}), - std::make_tuple("allPairsLength", &DEXV2Factory::allPairsLength, FunctionTypes::View, std::vector{}), - std::make_tuple("getPair", &DEXV2Factory::getPair, FunctionTypes::View, std::vector{"token0", "token1"}), - std::make_tuple("getPairByIndex", &DEXV2Factory::getPairByIndex, FunctionTypes::View, std::vector{"index"}), - std::make_tuple("createPair", &DEXV2Factory::createPair, FunctionTypes::NonPayable, std::vector{"tokenA", "tokenB"}), - std::make_tuple("setFeeTo", &DEXV2Factory::setFeeTo, FunctionTypes::NonPayable, std::vector{"_feeTo"}), - std::make_tuple("setFeeToSetter", &DEXV2Factory::setFeeToSetter, FunctionTypes::NonPayable, std::vector{"_feeToSetter"}) - ); + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{"_feeToSetter"}, + std::make_tuple("feeTo", &DEXV2Factory::feeTo, FunctionTypes::View, std::vector{}), + std::make_tuple("feeToSetter", &DEXV2Factory::feeToSetter, FunctionTypes::View, std::vector{}), + std::make_tuple("allPairs", &DEXV2Factory::allPairs, FunctionTypes::View, std::vector{}), + std::make_tuple("allPairsLength", &DEXV2Factory::allPairsLength, FunctionTypes::View, std::vector{}), + std::make_tuple("getPair", &DEXV2Factory::getPair, FunctionTypes::View, std::vector{"token0", "token1"}), + std::make_tuple("getPairByIndex", &DEXV2Factory::getPairByIndex, FunctionTypes::View, std::vector{"index"}), + std::make_tuple("createPair", &DEXV2Factory::createPair, FunctionTypes::NonPayable, std::vector{"tokenA", "tokenB"}), + std::make_tuple("setFeeTo", &DEXV2Factory::setFeeTo, FunctionTypes::NonPayable, std::vector{"_feeTo"}), + std::make_tuple("setFeeToSetter", &DEXV2Factory::setFeeToSetter, FunctionTypes::NonPayable, std::vector{"_feeToSetter"}) + ); + }); } + /// Dump method + DBBatch dump() const override; }; #endif // DEXFACTORY_H diff --git a/src/contract/templates/dexv2/dexv2library.cpp b/src/contract/templates/dexv2/dexv2library.cpp index 03ee42be..131fa95b 100644 --- a/src/contract/templates/dexv2/dexv2library.cpp +++ b/src/contract/templates/dexv2/dexv2library.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -12,36 +12,41 @@ See the LICENSE.txt file in the project root for more information. namespace DEXV2Library { std::pair sortTokens(const Address& tokenA, const Address& tokenB) { - if (tokenA == tokenB) throw std::runtime_error("DEXV2Library: IDENTICAL_ADDRESSES"); + if (tokenA == tokenB) throw DynamicException("DEXV2Library: IDENTICAL_ADDRESSES"); auto ret = tokenA < tokenB ? std::make_pair(tokenA, tokenB) : std::make_pair(tokenB, tokenA); - if (ret.first == Address()) throw std::runtime_error("DEXV2Library: ZERO_ADDRESS"); + if (ret.first == Address()) throw DynamicException("DEXV2Library: ZERO_ADDRESS"); return ret; } Address pairFor( - const ContractManagerInterface& interface, const Address& factory, + const ContractHost* host, const Address& factory, const Address& tokenA, const Address& tokenB ) { - return interface.getContract(factory)->getPair(tokenA, tokenB); + return host->getContract(factory)->getPair(tokenA, tokenB); } std::pair getReserves( - const ContractManagerInterface& interface, const Address& factory, + const ContractHost* host, const Address& factory, const Address& tokenA, const Address& tokenB ) { - auto pair = pairFor(interface, factory, tokenA, tokenB); - return interface.getContract(pair)->getReservess(); + if (host == nullptr) throw DynamicException("DEXV2Library: INVALID_HOST"); + auto pair = pairFor(host, factory, tokenA, tokenB); + std::pair ret; + const auto& [reserveA, reserveB, timestamp] = host->getContract(pair)->getReserves(); + ret.first = reserveA; + ret.second = reserveB; + return ret; } uint256_t quote(const uint256_t& amountA, const uint256_t& reserveA, const uint256_t& reserveB) { - if (amountA == 0) throw std::runtime_error("DEXV2Library: INSUFFICIENT_AMOUNT"); - if (reserveA == 0 || reserveB == 0) throw std::runtime_error("DEXV2Library: INSUFFICIENT_LIQUIDITY"); + if (amountA == 0) throw DynamicException("DEXV2Library: INSUFFICIENT_AMOUNT"); + if (reserveA == 0 || reserveB == 0) throw DynamicException("DEXV2Library: INSUFFICIENT_LIQUIDITY"); return amountA * reserveB / reserveA; } uint256_t getAmountOut(const uint256_t& amountIn, const uint256_t& reserveIn, const uint256_t& reserveOut) { - if (amountIn == 0) throw std::runtime_error("DEXV2Library: INSUFFICIENT_INPUT_AMOUNT"); - if (reserveIn == 0 || reserveOut == 0) throw std::runtime_error("DEXV2Library: INSUFFICIENT_LIQUIDITY"); + if (amountIn == 0) throw DynamicException("DEXV2Library: INSUFFICIENT_INPUT_AMOUNT"); + if (reserveIn == 0 || reserveOut == 0) throw DynamicException("DEXV2Library: INSUFFICIENT_LIQUIDITY"); uint256_t amountInWithFee = amountIn * 997; uint256_t numerator = amountInWithFee * reserveOut; uint256_t denominator = reserveIn * 1000 + amountInWithFee; @@ -49,36 +54,36 @@ namespace DEXV2Library { } uint256_t getAmountIn(const uint256_t& amountOut, const uint256_t& reserveIn, const uint256_t& reserveOut) { - if (amountOut == 0) throw std::runtime_error("DEXV2Library: INSUFFICIENT_OUTPUT_AMOUNT"); - if (reserveIn == 0 || reserveOut == 0) throw std::runtime_error("DEXV2Library: INSUFFICIENT_LIQUIDITY"); + if (amountOut == 0) throw DynamicException("DEXV2Library: INSUFFICIENT_OUTPUT_AMOUNT"); + if (reserveIn == 0 || reserveOut == 0) throw DynamicException("DEXV2Library: INSUFFICIENT_LIQUIDITY"); uint256_t numerator = reserveIn * amountOut * 1000; uint256_t denominator = (reserveOut - amountOut) * 997; return (numerator / denominator) + 1; } std::vector getAmountsOut( - const ContractManagerInterface& interface, const Address& factory, + const ContractHost* host, const Address& factory, const uint256_t& amountIn, const std::vector
& path ) { - if (path.size() < 2) throw std::runtime_error("DEXV2Library: INVALID_PATH"); + if (path.size() < 2) throw DynamicException("DEXV2Library: INVALID_PATH"); std::vector amounts(path.size()); amounts[0] = amountIn; for (size_t i = 0; i < path.size() - 1; i++) { - auto [reservesA, reservesB] = getReserves(interface, factory, path[i], path[i + 1]); + auto [reservesA, reservesB] = getReserves(host, factory, path[i], path[i + 1]); amounts[i + 1] = getAmountOut(amounts[i], reservesA, reservesB); } return amounts; } std::vector getAmountsIn( - const ContractManagerInterface& interface, const Address& factory, + const ContractHost* host, const Address& factory, const uint256_t& amountOut, const std::vector
& path ) { - if (path.size() < 2) throw std::runtime_error("DEXV2Library: INVALID_PATH"); + if (path.size() < 2) throw DynamicException("DEXV2Library: INVALID_PATH"); std::vector amounts(path.size()); amounts[amounts.size() - 1] = amountOut; for (size_t i = path.size() - 1; i > 0; i--) { - auto [reservesA, reservesB] = getReserves(interface, factory, path[i - 1], path[i]); + auto [reservesA, reservesB] = getReserves(host, factory, path[i - 1], path[i]); amounts[i - 1] = getAmountIn(amounts[i], reservesA, reservesB); } return amounts; diff --git a/src/contract/templates/dexv2/dexv2library.h b/src/contract/templates/dexv2/dexv2library.h index ed91b3e2..b93f762a 100644 --- a/src/contract/templates/dexv2/dexv2library.h +++ b/src/contract/templates/dexv2/dexv2library.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -11,7 +11,7 @@ See the LICENSE.txt file in the project root for more information. #include "../../../utils/utils.h" /// Forward Declaration. -class ContractManagerInterface; +class ContractHost; /// Namespace for common functions used in the DEXV2 contract. namespace DEXV2Library { @@ -28,28 +28,28 @@ namespace DEXV2Library { /** * Returns the pair address for the given tokens. * Differently from solidity, we don't calculate the address, we ask the factory. - * The way addresses are derived within OrbiterSDK are completely different. - * @param interface The contract manager interface. + * Because we don't use CREATE2 Derivation method. + * @param host The contract host. * @param factory The factory address. * @param tokenA The address of tokenA. * @param tokenB The address of tokenB. * @return The pair address. */ Address pairFor( - const ContractManagerInterface& interface, const Address& factory, + const ContractHost* host, const Address& factory, const Address& tokenA, const Address& tokenB ); /** * Fetches and sorts the reserves for a pair. - * @param interface The contract manager interface. + * @param host The contract host. * @param factory The factory address. * @param tokenA The address of tokenA. * @param tokenB The address of tokenB. * @return The pair of reserves. */ std::pair getReserves( - const ContractManagerInterface& interface, const Address& factory, + const ContractHost* host, const Address& factory, const Address& tokenA, const Address& tokenB ); @@ -86,28 +86,28 @@ namespace DEXV2Library { /** * Performs a chained getAmountOut calculation on any number of pairs. * Solidity counterpart: function getAmountsOut(uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) - * @param interface The contract manager interface. + * @param host The contract host. * @param factory The factory address. * @param amountIn The amount of assetIn. * @param path The path of the pairs. * @return The amount each iteration will return. */ std::vector getAmountsOut( - const ContractManagerInterface& interface, const Address& factory, + const ContractHost* host, const Address& factory, const uint256_t& amountIn, const std::vector
& path ); /** * Performs a chained getAmountIn calculation on any number of pairs. * Solidity counterpart: function getAmountsIn(uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) - * @param interface The contract manager interface. + * @param host The contract host. * @param factory The factory address. * @param amountOut The amount of assetOut. * @param path The path of the pairs. * @return The amount each iteration will return. */ std::vector getAmountsIn( - const ContractManagerInterface& interface, const Address& factory, + const ContractHost* host, const Address& factory, const uint256_t& amountOut, const std::vector
& path ); } diff --git a/src/contract/templates/dexv2/dexv2pair.cpp b/src/contract/templates/dexv2/dexv2pair.cpp index 1d724125..bf6e2643 100644 --- a/src/contract/templates/dexv2/dexv2pair.cpp +++ b/src/contract/templates/dexv2/dexv2pair.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,21 +8,24 @@ See the LICENSE.txt file in the project root for more information. #include "dexv2pair.h" #include "dexv2factory.h" -DEXV2Pair::DEXV2Pair( - ContractManagerInterface &interface, const Address& address, const std::unique_ptr &db -) : ERC20(interface, address, db), factory_(this), token0_(this), token1_(this), +#include "../../../utils/uintconv.h" +#include "../../../utils/strconv.h" + +DEXV2Pair::DEXV2Pair(const Address& address, const DB& db +) : DynamicContract(address, db), ERC20(address, db), factory_(this), token0_(this), token1_(this), reserve0_(this), reserve1_(this), blockTimestampLast_(this), price0CumulativeLast_(this), price1CumulativeLast_(this), kLast_(this) { - this->factory_ = Address(this->db_->get(std::string("factory_"), this->getDBPrefix())); - this->token0_ = Address(this->db_->get(std::string("token0_"), this->getDBPrefix())); - this->token1_ = Address(this->db_->get(std::string("token1_"), this->getDBPrefix())); - this->reserve0_ = Utils::bytesToUint112(this->db_->get(std::string("reserve0_"), this->getDBPrefix())); - this->reserve1_ = Utils::bytesToUint112(this->db_->get(std::string("reserve1_"), this->getDBPrefix())); - this->blockTimestampLast_ = Utils::bytesToUint32(this->db_->get(std::string("blockTimestampLast_"), this->getDBPrefix())); - this->price0CumulativeLast_ = Utils::bytesToUint256(this->db_->get(std::string("price0CumulativeLast_"), this->getDBPrefix())); - this->price1CumulativeLast_ = Utils::bytesToUint256(this->db_->get(std::string("price1CumulativeLast_"), this->getDBPrefix())); - this->kLast_ = Utils::bytesToUint256(this->db_->get(std::string("kLast_"), this->getDBPrefix())); + this->factory_ = Address(db.get(std::string("factory_"), this->getDBPrefix())); + this->token0_ = Address(db.get(std::string("token0_"), this->getDBPrefix())); + this->token1_ = Address(db.get(std::string("token1_"), this->getDBPrefix())); + this->reserve0_ = UintConv::bytesToUint112(db.get(std::string("reserve0_"), this->getDBPrefix())); + this->reserve1_ = UintConv::bytesToUint112(db.get(std::string("reserve1_"), this->getDBPrefix())); + this->blockTimestampLast_ = UintConv::bytesToUint32(db.get(std::string("blockTimestampLast_"), this->getDBPrefix())); + this->price0CumulativeLast_ = UintConv::bytesToUint256(db.get(std::string("price0CumulativeLast_"), this->getDBPrefix())); + this->price1CumulativeLast_ = UintConv::bytesToUint256(db.get(std::string("price1CumulativeLast_"), this->getDBPrefix())); + this->kLast_ = UintConv::bytesToUint256(db.get(std::string("kLast_"), this->getDBPrefix())); + this->factory_.commit(); this->token0_.commit(); this->token1_.commit(); @@ -32,51 +35,78 @@ DEXV2Pair::DEXV2Pair( this->price0CumulativeLast_.commit(); this->price1CumulativeLast_.commit(); this->kLast_.commit(); + this->registerContractFunctions(); + + this->factory_.enableRegister(); + this->token0_.enableRegister(); + this->token1_.enableRegister(); + this->reserve0_.enableRegister(); + this->reserve1_.enableRegister(); + this->blockTimestampLast_ .enableRegister(); + this->price0CumulativeLast_.enableRegister(); + this->price1CumulativeLast_.enableRegister(); + this->kLast_.enableRegister(); } DEXV2Pair::DEXV2Pair( - ContractManagerInterface& interface, - const Address& address, const Address& creator, const uint64_t& chainId, - const std::unique_ptr& db -) : ERC20("DEXV2Pair", "DEX V2", "DEX-V2", 18, 0, interface, address, creator, chainId, db), + const Address& address, const Address& creator, const uint64_t& chainId +) : DynamicContract("DEXV2Pair", address, creator, chainId), + ERC20("DEXV2Pair", "DEX V2", "DEX-V2", 18, 0, address, creator, chainId), factory_(this), token0_(this), token1_(this), reserve0_(this), reserve1_(this), blockTimestampLast_(this), price0CumulativeLast_(this), price1CumulativeLast_(this), kLast_(this) { + // Explicitly initialize numbers to 0 to avoid junk values on DB load this->factory_ = creator; + this->reserve0_ = 0; + this->reserve1_ = 0; + this->blockTimestampLast_ = 0; + this->price0CumulativeLast_ = 0; + this->price1CumulativeLast_ = 0; + this->kLast_ = 0; + this->factory_.commit(); + this->token0_.commit(); + this->token1_.commit(); + this->reserve0_.commit(); + this->reserve1_.commit(); + this->blockTimestampLast_ .commit(); + this->price0CumulativeLast_.commit(); + this->price1CumulativeLast_.commit(); + this->kLast_.commit(); + this->registerContractFunctions(); -} -DEXV2Pair::~DEXV2Pair() { - DBBatch batchOperations; - batchOperations.push_back(Utils::stringToBytes("factory_"), this->factory_.get().view_const(), this->getDBPrefix()); - batchOperations.push_back(Utils::stringToBytes("token0_"), this->token0_.get().view_const(), this->getDBPrefix()); - batchOperations.push_back(Utils::stringToBytes("token1_"), this->token1_.get().view_const(), this->getDBPrefix()); - batchOperations.push_back(Utils::stringToBytes("reserve0_"), Utils::uint112ToBytes(this->reserve0_.get()), this->getDBPrefix()); - batchOperations.push_back(Utils::stringToBytes("reserve1_"), Utils::uint112ToBytes(this->reserve1_.get()), this->getDBPrefix()); - batchOperations.push_back(Utils::stringToBytes("blockTimestampLast_"), Utils::uint32ToBytes(this->blockTimestampLast_.get()), this->getDBPrefix()); - batchOperations.push_back(Utils::stringToBytes("price0CumulativeLast_"), Utils::uint256ToBytes(this->price0CumulativeLast_.get()), this->getDBPrefix()); - batchOperations.push_back(Utils::stringToBytes("price1CumulativeLast_"), Utils::uint256ToBytes(this->price1CumulativeLast_.get()), this->getDBPrefix()); - batchOperations.push_back(Utils::stringToBytes("kLast_"), Utils::uint256ToBytes(this->kLast_.get()), this->getDBPrefix()); - this->db_->putBatch(batchOperations); + this->factory_.enableRegister(); + this->token0_.enableRegister(); + this->token1_.enableRegister(); + this->reserve0_.enableRegister(); + this->reserve1_.enableRegister(); + this->blockTimestampLast_ .enableRegister(); + this->price0CumulativeLast_.enableRegister(); + this->price1CumulativeLast_.enableRegister(); + this->kLast_.enableRegister(); } +DEXV2Pair::~DEXV2Pair() {}; + void DEXV2Pair::registerContractFunctions() { registerContract(); - this->registerMemberFunction("initialize", &DEXV2Pair::initialize, FunctionTypes::NonPayable, this); - this->registerMemberFunction("getReserves", &DEXV2Pair::getReserves, FunctionTypes::View, this); - this->registerMemberFunction("factory", &DEXV2Pair::factory, FunctionTypes::View, this); - this->registerMemberFunction("token0", &DEXV2Pair::token0, FunctionTypes::View, this); - this->registerMemberFunction("token1", &DEXV2Pair::token1, FunctionTypes::View, this); - this->registerMemberFunction("price0CumulativeLast", &DEXV2Pair::price0CumulativeLast, FunctionTypes::View, this); - this->registerMemberFunction("price1CumulativeLast", &DEXV2Pair::price1CumulativeLast, FunctionTypes::View, this); - this->registerMemberFunction("kLast", &DEXV2Pair::kLast, FunctionTypes::View, this); - this->registerMemberFunction("mint", &DEXV2Pair::mint, FunctionTypes::NonPayable, this); - this->registerMemberFunction("burn", &DEXV2Pair::burn, FunctionTypes::NonPayable, this); - this->registerMemberFunction("swap", &DEXV2Pair::swap, FunctionTypes::NonPayable, this); - this->registerMemberFunction("skim", &DEXV2Pair::skim, FunctionTypes::NonPayable, this); - this->registerMemberFunction("sync", &DEXV2Pair::sync, FunctionTypes::NonPayable, this); + this->registerMemberFunctions( + std::make_tuple("initialize", &DEXV2Pair::initialize, FunctionTypes::NonPayable, this), + std::make_tuple("getReserves", &DEXV2Pair::getReserves, FunctionTypes::View, this), + std::make_tuple("factory", &DEXV2Pair::factory, FunctionTypes::View, this), + std::make_tuple("token0", &DEXV2Pair::token0, FunctionTypes::View, this), + std::make_tuple("token1", &DEXV2Pair::token1, FunctionTypes::View, this), + std::make_tuple("price0CumulativeLast", &DEXV2Pair::price0CumulativeLast, FunctionTypes::View, this), + std::make_tuple("price1CumulativeLast", &DEXV2Pair::price1CumulativeLast, FunctionTypes::View, this), + std::make_tuple("kLast", &DEXV2Pair::kLast, FunctionTypes::View, this), + std::make_tuple("mint", &DEXV2Pair::mint, FunctionTypes::NonPayable, this), + std::make_tuple("burn", &DEXV2Pair::burn, FunctionTypes::NonPayable, this), + std::make_tuple("swap", &DEXV2Pair::swap, FunctionTypes::NonPayable, this), + std::make_tuple("skim", &DEXV2Pair::skim, FunctionTypes::NonPayable, this), + std::make_tuple("sync", &DEXV2Pair::sync, FunctionTypes::NonPayable, this) + ); } void DEXV2Pair::_safeTransfer(const Address& token, const Address& to, const uint256_t& value) { @@ -86,8 +116,10 @@ void DEXV2Pair::_safeTransfer(const Address& token, const Address& to, const uin void DEXV2Pair::_update(const uint256_t& balance0, const uint256_t& balance1, const uint256_t& reserve0, const uint256_t& reserve1) { // Timestamp is in microseconds, we want in seconds auto blockTimestamp = uint32_t(ContractGlobals::getBlockTimestamp() / 1000000); - uint32_t timeElapsed = blockTimestamp - this->blockTimestampLast_.get(); - if (timeElapsed > 0 && reserve0 != 0 && reserve1 != 0) { + if ( + uint32_t timeElapsed = blockTimestamp - this->blockTimestampLast_.get(); + timeElapsed > 0 && reserve0 != 0 && reserve1 != 0 + ) { this->price0CumulativeLast_ += uint256_t(UQ112x112::uqdiv(UQ112x112::encode(uint112_t(reserve1)), uint112_t(reserve0))) * timeElapsed; this->price1CumulativeLast_ += uint256_t(UQ112x112::uqdiv(UQ112x112::encode(uint112_t(reserve0)), uint112_t(reserve1))) * timeElapsed; } @@ -100,16 +132,14 @@ bool DEXV2Pair::_mintFee(uint112_t reserve0, uint112_t reserve1) { Address feeTo = this->callContractViewFunction(this->factory_.get(), &DEXV2Factory::feeTo); bool feeOn = feeTo ? true : false; uint256_t _kLast = this->kLast_.get(); - if (feeOn) { - if (_kLast != 0) { - uint256_t rootK = boost::multiprecision::sqrt(uint256_t(reserve0) * uint256_t(reserve1)); - uint256_t rootKLast = boost::multiprecision::sqrt(_kLast); - if (rootK > rootKLast) { - uint256_t numerator = this->totalSupply_.get() * (rootK - rootKLast); - uint256_t denominator = rootK * 5 + rootKLast; - uint256_t liquidity = numerator / denominator; - if (liquidity > 0) this->mintValue_(feeTo, liquidity); - } + if (feeOn && _kLast != 0) { + uint256_t rootK = boost::multiprecision::sqrt(uint256_t(reserve0) * uint256_t(reserve1)); + uint256_t rootKLast = boost::multiprecision::sqrt(_kLast); + if (rootK > rootKLast) { + uint256_t numerator = this->totalSupply_.get() * (rootK - rootKLast); + uint256_t denominator = rootK * 5 + rootKLast; + uint256_t liquidity = numerator / denominator; + if (liquidity > 0) this->mintValue_(feeTo, liquidity); } } else if (_kLast != 0) { this->kLast_ = 0; @@ -118,15 +148,11 @@ bool DEXV2Pair::_mintFee(uint112_t reserve0, uint112_t reserve1) { } void DEXV2Pair::initialize(const Address& token0, const Address& token1) { - if (this->factory_ != this->getCaller()) throw std::runtime_error("DEXV2Pair: FORBIDDEN"); + if (this->factory_ != this->getCaller()) throw DynamicException("DEXV2Pair: FORBIDDEN"); this->token0_ = token0; this->token1_ = token1; } -std::pair DEXV2Pair::getReservess() const { - return std::make_pair(this->reserve0_.get(), this->reserve1_.get()); -} - std::tuple DEXV2Pair::getReserves() const { return std::make_tuple(this->reserve0_.get(), this->reserve1_.get(), this->blockTimestampLast_.get()); } @@ -152,8 +178,7 @@ uint256_t DEXV2Pair::mint(const Address& to) { uint256_t amount1 = balance1 - this->reserve1_.get(); bool feeOn = this->_mintFee(this->reserve0_.get(), this->reserve1_.get()); - uint256_t totalSupply = this->totalSupply_.get(); - if (totalSupply == 0) { + if (uint256_t totalSupply = this->totalSupply_.get(); totalSupply == 0) { // Permanently lock the first MINIMUM_LIQUIDITY tokens liquidity = boost::multiprecision::sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY; this->mintValue_(Address(Hex::toBytes("0x0000000000000000000000000000000000000000")), MINIMUM_LIQUIDITY); @@ -161,7 +186,7 @@ uint256_t DEXV2Pair::mint(const Address& to) { liquidity = std::min(amount0 * totalSupply / this->reserve0_.get(), amount1 * totalSupply / this->reserve1_.get()); } - if (liquidity == 0) throw std::runtime_error("DEXV2Pair: INSUFFICIENT_LIQUIDITY_MINTED"); + if (liquidity == 0) throw DynamicException("DEXV2Pair: INSUFFICIENT_LIQUIDITY_MINTED"); this->mintValue_(to, liquidity); this->_update(balance0, balance1, this->reserve0_.get(), this->reserve1_.get()); if (feeOn) this->kLast_ = uint256_t(this->reserve0_.get()) * uint256_t(this->reserve1_.get()); @@ -182,7 +207,7 @@ std::tuple DEXV2Pair::burn(const Address& to) { uint256_t totalSupply = this->totalSupply_.get(); uint256_t amount0 = liquidity * balance0 / totalSupply; uint256_t amount1 = liquidity * balance1 / totalSupply; - if (amount0 == 0 || amount1 == 0) throw std::runtime_error("DEXV2Pair: INSUFFICIENT_LIQUIDITY_BURNED"); + if (amount0 == 0 || amount1 == 0) throw DynamicException("DEXV2Pair: INSUFFICIENT_LIQUIDITY_BURNED"); this->burnValue_(this->getContractAddress(), liquidity); this->_safeTransfer(this->token0_.get(), to, amount0); this->_safeTransfer(this->token1_.get(), to, amount1); @@ -195,19 +220,24 @@ std::tuple DEXV2Pair::burn(const Address& to) { void DEXV2Pair::swap(const uint256_t& amount0Out, const uint256_t& amount1Out, const Address& to) { ReentrancyGuard reentrancyGuard(this->reentrancyLock_); - if (amount0Out == 0 && amount1Out == 0) throw std::runtime_error("DEXV2Pair: INSUFFICIENT_OUTPUT_AMOUNT"); - if (reserve0_ <= uint112_t(amount0Out) && reserve1_ <= uint112_t(amount1Out)) throw std::runtime_error("DEXV2Pair: INSUFFICIENT_LIQUIDITY"); - if (token0_ == to || token1_ == to) throw std::runtime_error("DEXV2Pair: INVALID_TO"); + if (amount0Out == 0 && amount1Out == 0) throw DynamicException("DEXV2Pair: INSUFFICIENT_OUTPUT_AMOUNT"); + if (reserve0_ <= uint112_t(amount0Out) && reserve1_ <= uint112_t(amount1Out)) throw DynamicException("DEXV2Pair: INSUFFICIENT_LIQUIDITY"); + if (token0_ == to || token1_ == to) throw DynamicException("DEXV2Pair: INVALID_TO"); if (amount0Out > 0) this->_safeTransfer(this->token0_.get(), to, amount0Out); if (amount1Out > 0) this->_safeTransfer(this->token1_.get(), to, amount1Out); uint256_t balance0 = this->callContractViewFunction(this->token0_.get(), &ERC20::balanceOf, this->getContractAddress()); uint256_t balance1 = this->callContractViewFunction(this->token1_.get(), &ERC20::balanceOf, this->getContractAddress()); uint256_t amount0In = balance0 > this->reserve0_.get() - uint112_t(amount0Out) ? balance0 - this->reserve0_.get() + uint112_t(amount0Out) : 0; uint256_t amount1In = balance1 > this->reserve1_.get() - uint112_t(amount1Out) ? balance1 - this->reserve1_.get() + uint112_t(amount1Out) : 0; - if (amount0In == 0 && amount1In == 0) throw std::runtime_error("DEXV2Pair: INSUFFICIENT_INPUT_AMOUNT"); + if (amount0In == 0 && amount1In == 0) throw DynamicException("DEXV2Pair: INSUFFICIENT_INPUT_AMOUNT"); uint256_t balance0Adjusted = balance0 * 1000 - amount0In * 3; uint256_t balance1Adjusted = balance1 * 1000 - amount1In * 3; - if (balance0Adjusted * balance1Adjusted < uint256_t(this->reserve0_.get()) * this->reserve1_.get() * 1000 * 1000) throw std::runtime_error("DEXV2Pair: K"); + if ( + uint256_t balTotal = balance0Adjusted * balance1Adjusted; + balTotal < uint256_t(this->reserve0_.get()) * this->reserve1_.get() * 1000 * 1000 + ) { + throw DynamicException("DEXV2Pair: K"); + } this->_update(balance0, balance1, this->reserve0_.get(), this->reserve1_.get()); } @@ -230,3 +260,23 @@ void DEXV2Pair::sync() { ); } + +DBBatch DEXV2Pair::dump() const { + // We have to dump the tokens as well + DBBatch dbBatch = BaseContract::dump(); + DBBatch erc20Batch = ERC20::dump(); + for (const auto& dbItem : erc20Batch.getPuts()) dbBatch.push_back(dbItem); + for (const auto& dbItem : erc20Batch.getDels()) dbBatch.delete_key(dbItem); + + dbBatch.push_back(StrConv::stringToBytes("factory_"), this->factory_.get().view(), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("token0_"), this->token0_.get().view(), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("token1_"), this->token1_.get().view(), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("reserve0_"), UintConv::uint112ToBytes(this->reserve0_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("reserve1_"), UintConv::uint112ToBytes(this->reserve1_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("blockTimestampLast_"), UintConv::uint32ToBytes(this->blockTimestampLast_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("price0CumulativeLast_"), UintConv::uint256ToBytes(this->price0CumulativeLast_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("price1CumulativeLast_"), UintConv::uint256ToBytes(this->price1CumulativeLast_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("kLast_"), UintConv::uint256ToBytes(this->kLast_.get()), this->getDBPrefix()); + return dbBatch; +} + diff --git a/src/contract/templates/dexv2/dexv2pair.h b/src/contract/templates/dexv2/dexv2pair.h index 80f2fef3..c15e28ca 100644 --- a/src/contract/templates/dexv2/dexv2pair.h +++ b/src/contract/templates/dexv2/dexv2pair.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -10,7 +10,6 @@ See the LICENSE.txt file in the project root for more information. #include -#include "../../../utils/contractreflectioninterface.h" #include "../../../utils/db.h" #include "../../../utils/utils.h" #include "../../abi.h" @@ -19,7 +18,7 @@ See the LICENSE.txt file in the project root for more information. #include "../../variables/safeaddress.h" #include "../../variables/safestring.h" #include "../../variables/safeunorderedmap.h" -#include "../erc20.h" +#include "../standards/erc20.h" #include "uq112x112.h" /// Template for an DEXV2Pair contract. @@ -91,27 +90,21 @@ class DEXV2Pair : public ERC20 { /** * Constructor for loading contract from DB. - * @param interface Reference to the contract manager interface. * @param address The address where the contract will be deployed. * @param db Reference to the database object. */ DEXV2Pair( - ContractManagerInterface& interface, - const Address& address, const std::unique_ptr& db + const Address& address, const DB& db ); /** * Constructor to be used when creating a new contract. - * @param interface Reference to the contract manager interface. * @param address The address where the contract will be deployed. * @param creator The address of the creator of the contract. * @param chainId The chain where the contract wil be deployed. - * @param db Reference to the database object. */ DEXV2Pair( - ContractManagerInterface &interface, - const Address &address, const Address &creator, const uint64_t &chainId, - const std::unique_ptr &db + const Address &address, const Address &creator, const uint64_t &chainId ); /// Destructor. @@ -126,12 +119,6 @@ class DEXV2Pair : public ERC20 { */ void initialize(const Address& token0, const Address& token1); - /** - * Get the reserves of the ERC2OPair - * Direct std::pair function call so it can be utilized by others contracts in the C++ side. - */ - std::pair getReservess() const; - /** * Get the reserves of the ERC20Pair. * Solidity counterpart: function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) @@ -213,27 +200,28 @@ class DEXV2Pair : public ERC20 { /// Register contract class via ContractReflectionInterface. static void registerContract() { - ContractReflectionInterface::registerContractMethods< - DEXV2Pair, ContractManagerInterface &, - const Address &, const Address &, const uint64_t &, - const std::unique_ptr & - >( - std::vector{}, - std::make_tuple("initialize", &DEXV2Pair::initialize, FunctionTypes::NonPayable, std::vector{"token0_", "token1_"}), - std::make_tuple("getReserves", &DEXV2Pair::getReserves, FunctionTypes::View, std::vector{}), - std::make_tuple("factory", &DEXV2Pair::factory, FunctionTypes::View, std::vector{}), - std::make_tuple("token0", &DEXV2Pair::token0, FunctionTypes::View, std::vector{}), - std::make_tuple("token1", &DEXV2Pair::token1, FunctionTypes::View, std::vector{}), - std::make_tuple("price0CumulativeLast", &DEXV2Pair::price0CumulativeLast, FunctionTypes::View, std::vector{}), - std::make_tuple("price1CumulativeLast", &DEXV2Pair::price1CumulativeLast, FunctionTypes::View, std::vector{}), - std::make_tuple("kLast", &DEXV2Pair::kLast, FunctionTypes::View, std::vector{}), - std::make_tuple("mint", &DEXV2Pair::mint, FunctionTypes::NonPayable, std::vector{"to"}), - std::make_tuple("burn", &DEXV2Pair::burn, FunctionTypes::NonPayable, std::vector{"to"}), - std::make_tuple("swap", &DEXV2Pair::swap, FunctionTypes::NonPayable, std::vector{"amount0Out", "amount1Out", "to"}), - std::make_tuple("skim", &DEXV2Pair::skim, FunctionTypes::NonPayable, std::vector{"to"}), - std::make_tuple("sync", &DEXV2Pair::sync, FunctionTypes::NonPayable, std::vector{}) - ); + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{}, + std::make_tuple("initialize", &DEXV2Pair::initialize, FunctionTypes::NonPayable, std::vector{"token0_", "token1_"}), + std::make_tuple("getReserves", &DEXV2Pair::getReserves, FunctionTypes::View, std::vector{}), + std::make_tuple("factory", &DEXV2Pair::factory, FunctionTypes::View, std::vector{}), + std::make_tuple("token0", &DEXV2Pair::token0, FunctionTypes::View, std::vector{}), + std::make_tuple("token1", &DEXV2Pair::token1, FunctionTypes::View, std::vector{}), + std::make_tuple("price0CumulativeLast", &DEXV2Pair::price0CumulativeLast, FunctionTypes::View, std::vector{}), + std::make_tuple("price1CumulativeLast", &DEXV2Pair::price1CumulativeLast, FunctionTypes::View, std::vector{}), + std::make_tuple("kLast", &DEXV2Pair::kLast, FunctionTypes::View, std::vector{}), + std::make_tuple("mint", &DEXV2Pair::mint, FunctionTypes::NonPayable, std::vector{"to"}), + std::make_tuple("burn", &DEXV2Pair::burn, FunctionTypes::NonPayable, std::vector{"to"}), + std::make_tuple("swap", &DEXV2Pair::swap, FunctionTypes::NonPayable, std::vector{"amount0Out", "amount1Out", "to"}), + std::make_tuple("skim", &DEXV2Pair::skim, FunctionTypes::NonPayable, std::vector{"to"}), + std::make_tuple("sync", &DEXV2Pair::sync, FunctionTypes::NonPayable, std::vector{}) + ); + }); } + /// Dump method + DBBatch dump() const override; }; #endif // DEXV2PAIR_H diff --git a/src/contract/templates/dexv2/dexv2router02.cpp b/src/contract/templates/dexv2/dexv2router02.cpp index 4038df9c..3431f869 100644 --- a/src/contract/templates/dexv2/dexv2router02.cpp +++ b/src/contract/templates/dexv2/dexv2router02.cpp @@ -1,67 +1,80 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. */ +#include + #include "dexv2router02.h" #include "dexv2factory.h" #include "dexv2pair.h" + #include "../nativewrapper.h" -#include -DEXV2Router02::DEXV2Router02( - ContractManagerInterface &interface, const Address &address, const std::unique_ptr &db -) : DynamicContract(interface, address, db), factory_(this), wrappedNative_(this) +#include "../../../utils/strconv.h" + +DEXV2Router02::DEXV2Router02(const Address &address, const DB& db +) : DynamicContract(address, db), factory_(this), wrappedNative_(this) { - this->factory_ = Address(this->db_->get(Utils::stringToBytes("factory_"), this->getDBPrefix())); - this->wrappedNative_ = Address(this->db_->get(Utils::stringToBytes("wrappedNative_"), this->getDBPrefix())); + this->factory_ = Address(db.get(StrConv::stringToBytes("factory_"), this->getDBPrefix())); + this->wrappedNative_ = Address(db.get(StrConv::stringToBytes("wrappedNative_"), this->getDBPrefix())); + this->factory_.commit(); this->wrappedNative_.commit(); + this->registerContractFunctions(); + + this->factory_.enableRegister(); + this->wrappedNative_.enableRegister(); } DEXV2Router02::DEXV2Router02( const Address& factory, const Address& nativeWrapper, - ContractManagerInterface &interface, - const Address &address, const Address &creator, const uint64_t &chainId, - const std::unique_ptr &db -) : DynamicContract(interface, "DEXV2Router02", address, creator, chainId, db), + const Address &address, const Address &creator, const uint64_t &chainId +) : DynamicContract("DEXV2Router02", address, creator, chainId), factory_(this), wrappedNative_(this) { this->factory_ = factory; this->wrappedNative_ = nativeWrapper; + this->factory_.commit(); this->wrappedNative_.commit(); + this->registerContractFunctions(); + + this->factory_.enableRegister(); + this->wrappedNative_.enableRegister(); } -DEXV2Router02::~DEXV2Router02() { - DBBatch batchOperations; - batchOperations.push_back( - Utils::stringToBytes("factory_"), this->factory_.get().view_const(), this->getDBPrefix() - ); - batchOperations.push_back( - Utils::stringToBytes("wrappedNative_"), this->wrappedNative_.get().view_const(), this->getDBPrefix() - ); - this->db_->putBatch(batchOperations); +DEXV2Router02::~DEXV2Router02() {}; + +void DEXV2Router02::receive(const evmc_message& msg) { + // can only receive from wrapped token + if (View
(msg.sender) != this->wrappedNative_.get()) { + throw DynamicException("DEXV2Router02::receive: Only wrapped native token can call receive!"); + } + return; } + void DEXV2Router02::registerContractFunctions() { registerContract(); - this->registerMemberFunction("factory", &DEXV2Router02::factory, FunctionTypes::View, this); - this->registerMemberFunction("wrappedNative", &DEXV2Router02::wrappedNative, FunctionTypes::View, this); - this->registerMemberFunction("addLiquidity", &DEXV2Router02::addLiquidity, FunctionTypes::NonPayable, this); - this->registerMemberFunction("addLiquidityNative", &DEXV2Router02::addLiquidityNative, FunctionTypes::Payable, this); - this->registerMemberFunction("removeLiquidity", &DEXV2Router02::removeLiquidity, FunctionTypes::NonPayable, this); - this->registerMemberFunction("removeLiquidityNative", &DEXV2Router02::removeLiquidityNative, FunctionTypes::Payable, this); - this->registerMemberFunction("swapExactTokensForTokens", &DEXV2Router02::swapExactTokensForTokens, FunctionTypes::NonPayable, this); - this->registerMemberFunction("swapTokensForExactTokens", &DEXV2Router02::swapTokensForExactTokens, FunctionTypes::NonPayable, this); - this->registerMemberFunction("swapExactNativeForTokens", &DEXV2Router02::swapExactNativeForTokens, FunctionTypes::Payable, this); - this->registerMemberFunction("swapTokensForExactNative", &DEXV2Router02::swapTokensForExactNative, FunctionTypes::Payable, this); - this->registerMemberFunction("swapExactTokensForNative", &DEXV2Router02::swapExactTokensForNative, FunctionTypes::Payable, this); - this->registerMemberFunction("swapNativeForExactTokens", &DEXV2Router02::swapNativeForExactTokens, FunctionTypes::Payable, this); + this->registerMemberFunctions( + std::make_tuple("factory", &DEXV2Router02::factory, FunctionTypes::View, this), + std::make_tuple("wrappedNative", &DEXV2Router02::wrappedNative, FunctionTypes::View, this), + std::make_tuple("addLiquidity", &DEXV2Router02::addLiquidity, FunctionTypes::NonPayable, this), + std::make_tuple("addLiquidityNative", &DEXV2Router02::addLiquidityNative, FunctionTypes::Payable, this), + std::make_tuple("removeLiquidity", &DEXV2Router02::removeLiquidity, FunctionTypes::NonPayable, this), + std::make_tuple("removeLiquidityNative", &DEXV2Router02::removeLiquidityNative, FunctionTypes::Payable, this), + std::make_tuple("swapExactTokensForTokens", &DEXV2Router02::swapExactTokensForTokens, FunctionTypes::NonPayable, this), + std::make_tuple("swapTokensForExactTokens", &DEXV2Router02::swapTokensForExactTokens, FunctionTypes::NonPayable, this), + std::make_tuple("swapExactNativeForTokens", &DEXV2Router02::swapExactNativeForTokens, FunctionTypes::Payable, this), + std::make_tuple("swapTokensForExactNative", &DEXV2Router02::swapTokensForExactNative, FunctionTypes::Payable, this), + std::make_tuple("swapExactTokensForNative", &DEXV2Router02::swapExactTokensForNative, FunctionTypes::Payable, this), + std::make_tuple("swapNativeForExactTokens", &DEXV2Router02::swapNativeForExactTokens, FunctionTypes::Payable, this) + ); } std::pair DEXV2Router02::_addLiquidity( @@ -85,33 +98,28 @@ std::pair DEXV2Router02::_addLiquidity( } else { Utils::safePrint("_addLiquidity: contract exists!"); } - auto reserves = this->callContractViewFunction(pairAddress.get(), &DEXV2Pair::getReservess); - const auto& [reserveA, reserveB] = reserves; + auto reserves = this->callContractViewFunction(pairAddress, &DEXV2Pair::getReserves); + const auto& [reserveA, reserveB, timestamp] = reserves; if (reserveA == 0 && reserveB == 0) { amountA = amountADesired; amountB = amountBDesired; } else { uint256_t amountBoptimal = DEXV2Library::quote(amountADesired, reserveA, reserveB); if (amountBoptimal <= amountBDesired) { - if (amountBoptimal < amountBMin) throw std::runtime_error( + if (amountBoptimal < amountBMin) throw DynamicException( "DEXV2Router02::_addLiquidity: INSUFFICIENT_B_AMOUNT" ); amountA = amountADesired; amountB = amountBoptimal; } else { uint256_t amountAoptimal = DEXV2Library::quote(amountBDesired, reserveB, reserveA); - if (amountAoptimal <= amountADesired) { - if (amountAoptimal < amountAMin) throw std::runtime_error( - "DEXV2Router02::_addLiquidity: INSUFFICIENT_A_AMOUNT" - ); - amountA = amountAoptimal; - amountB = amountBDesired; - } else { - throw std::runtime_error("DEXV2Router02::_addLiquidity: INSUFFICIENT_A_AMOUNT"); + if (amountAoptimal > amountADesired || amountAoptimal < amountAMin) { + throw DynamicException("DEXV2Router02::_addLiquidity: INSUFFICIENT_A_AMOUNT"); } + amountA = amountAoptimal; + amountB = amountBDesired; } } - return {amountA, amountB}; } @@ -124,7 +132,7 @@ void DEXV2Router02::_swap( auto pairAddress = this->callContractViewFunction( this->factory_.get(), &DEXV2Factory::getPair, input, output ); - if (!pairAddress) throw std::runtime_error("DEXV2Router02::_swap: PAIR_NOT_FOUND"); + if (!pairAddress) throw DynamicException("DEXV2Router02::_swap: PAIR_NOT_FOUND"); auto token0 = DEXV2Library::sortTokens(input, output).first; uint256_t amountOut = amounts[i + 1]; uint256_t amount0Out; @@ -140,14 +148,14 @@ void DEXV2Router02::_swap( this->factory_.get(), &DEXV2Factory::getPair, output, path[i + 2] ) : _to; this->callContractFunction( - pairAddress.get(), &DEXV2Pair::swap, amount0Out, amount1Out, to + pairAddress, &DEXV2Pair::swap, amount0Out, amount1Out, to ); } } bool DEXV2Router02::ensure(const uint256_t& deadline) const { if (deadline < ContractGlobals::getBlockTimestamp()) { - throw std::runtime_error("DEXV2Router02::ensure: EXPIRED"); + throw DynamicException("DEXV2Router02::ensure: EXPIRED"); } return true; } @@ -170,7 +178,7 @@ std::tuple DEXV2Router02::addLiquidity( auto [amountA, amountB] = this->_addLiquidity( tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin ); - auto pair = DEXV2Library::pairFor(this->interface_, this->factory_.get(), tokenA, tokenB); + auto pair = DEXV2Library::pairFor(this->host_, this->factory_.get(), tokenA, tokenB); this->callContractFunction(tokenA, &ERC20::transferFrom, this->getCaller(), pair, amountA); this->callContractFunction(tokenB, &ERC20::transferFrom, this->getCaller(), pair, amountB); auto liquidity = this->callContractFunction(pair, &DEXV2Pair::mint, to); @@ -188,9 +196,9 @@ std::tuple DEXV2Router02::addLiquidityNative( this->ensure(deadline); auto [amountToken, amountNative] = this->_addLiquidity( token, this->wrappedNative_.get(), amountTokenDesired, - amountNativeMin, amountTokenMin, amountNativeMin + this->getValue(), amountTokenMin, amountNativeMin ); - auto pair = DEXV2Library::pairFor(this->interface_, this->factory_.get(), token, this->wrappedNative_.get()); + auto pair = DEXV2Library::pairFor(this->host_, this->factory_.get(), token, this->wrappedNative_.get()); this->callContractFunction(token, &ERC20::transferFrom, this->getCaller(), pair, amountToken); this->callContractFunction(amountNative, this->wrappedNative_.get(), &NativeWrapper::deposit); this->callContractFunction(this->wrappedNative_.get(), &ERC20::transfer, pair, amountNative); @@ -212,13 +220,13 @@ std::tuple DEXV2Router02::removeLiquidity( const uint256_t& deadline ) { this->ensure(deadline); - auto pair = DEXV2Library::pairFor(this->interface_, this->factory_.get(), tokenA, tokenB); + auto pair = DEXV2Library::pairFor(this->host_, this->factory_.get(), tokenA, tokenB); this->callContractFunction(pair, &ERC20::transferFrom, this->getCaller(), pair, liquidity); const auto& [amount0, amount1] = this->callContractFunction(pair, &DEXV2Pair::burn, to); auto amountA = tokenA == DEXV2Library::sortTokens(tokenA, tokenB).first ? amount0 : amount1; auto amountB = tokenA == DEXV2Library::sortTokens(tokenA, tokenB).first ? amount1 : amount0; - if (amountA < amountAMin) throw std::runtime_error("DEXV2Router02::removeLiquidity: INSUFFICIENT_A_AMOUNT"); - if (amountB < amountBMin) throw std::runtime_error("DEXV2Router02::removeLiquidity: INSUFFICIENT_B_AMOUNT"); + if (amountA < amountAMin) throw DynamicException("DEXV2Router02::removeLiquidity: INSUFFICIENT_A_AMOUNT"); + if (amountB < amountBMin) throw DynamicException("DEXV2Router02::removeLiquidity: INSUFFICIENT_B_AMOUNT"); return std::make_tuple(amountA, amountB); } @@ -249,12 +257,11 @@ std::vector DEXV2Router02::swapExactTokensForTokens( const uint256_t& deadline ) { this->ensure(deadline); - auto amounts = DEXV2Library::getAmountsOut(this->interface_, this->factory_.get(), amountIn, path); - auto amountOut = amounts.back(); - if (amountOut < amountOutMin) throw std::runtime_error( + auto amounts = DEXV2Library::getAmountsOut(this->host_, this->factory_.get(), amountIn, path); + if (const auto& amountOut = amounts.back(); amountOut < amountOutMin) throw DynamicException( "DEXV2Router02::swapExactTokensForTokens: INSUFFICIENT_OUTPUT_AMOUNT" ); - auto pair = DEXV2Library::pairFor(this->interface_, this->factory_.get(), path[0], path[1]); + auto pair = DEXV2Library::pairFor(this->host_, this->factory_.get(), path[0], path[1]); this->callContractFunction(path.front(), &ERC20::transferFrom, this->getCaller(), pair, amounts[0]); this->_swap(amounts, path, to); return amounts; @@ -268,12 +275,12 @@ std::vector DEXV2Router02::swapTokensForExactTokens( const uint256_t& deadline ) { this->ensure(deadline); - auto amounts = DEXV2Library::getAmountsIn(this->interface_, this->factory_.get(), amountOut, path); + auto amounts = DEXV2Library::getAmountsIn(this->host_, this->factory_.get(), amountOut, path); auto amountIn = amounts.front(); - if (amountIn > amountInMax) throw std::runtime_error( + if (amountIn > amountInMax) throw DynamicException( "DEXV2Router02::swapTokensForExactTokens: EXCESSIVE_INPUT_AMOUNT" ); - auto pair = DEXV2Library::pairFor(this->interface_, this->factory_.get(), path[0], path[1]); + auto pair = DEXV2Library::pairFor(this->host_, this->factory_.get(), path[0], path[1]); this->callContractFunction(path.front(), &ERC20::transferFrom, this->getCaller(), pair, amountIn); this->_swap(amounts, path, to); return amounts; @@ -286,16 +293,15 @@ std::vector DEXV2Router02::swapExactNativeForTokens( const uint256_t& deadline ) { this->ensure(deadline); - if (path[0] != this->wrappedNative_.get()) throw std::runtime_error( + if (path[0] != this->wrappedNative_.get()) throw DynamicException( "DEXV2Router02::swapExactNativeForTokens: INVALID_PATH" ); - auto amounts = DEXV2Library::getAmountsOut(this->interface_, this->factory_.get(), this->getValue(), path); - auto amountOut = amounts.back(); - if (amountOut < amountOutMin) throw std::runtime_error( + auto amounts = DEXV2Library::getAmountsOut(this->host_, this->factory_.get(), this->getValue(), path); + if (const auto& amountOut = amounts.back(); amountOut < amountOutMin) throw DynamicException( "DEXV2Router02::swapExactNativeForTokens: INSUFFICIENT_OUTPUT_AMOUNT" ); this->callContractFunction(amounts[0], this->wrappedNative_.get(), &NativeWrapper::deposit); - auto pair = DEXV2Library::pairFor(this->interface_, this->factory_.get(), path[0], path[1]); + auto pair = DEXV2Library::pairFor(this->host_, this->factory_.get(), path[0], path[1]); this->callContractFunction(this->wrappedNative_.get(), &ERC20::transfer, pair, amounts[0]); this->_swap(amounts, path, to); return amounts; @@ -309,15 +315,15 @@ std::vector DEXV2Router02::swapTokensForExactNative( const uint256_t& deadline ) { this->ensure(deadline); - if (path.back() != this->wrappedNative_.get()) throw std::runtime_error( + if (path.back() != this->wrappedNative_.get()) throw DynamicException( "DEXV2Router02::swapTokensForExactNative: INVALID_PATH" ); - auto amounts = DEXV2Library::getAmountsIn(this->interface_, this->factory_.get(), amountOut, path); + auto amounts = DEXV2Library::getAmountsIn(this->host_, this->factory_.get(), amountOut, path); auto amountIn = amounts.front(); - if (amountIn > amountInMax) throw std::runtime_error( + if (amountIn > amountInMax) throw DynamicException( "DEXV2Router02::swapTokensForExactNative: EXCESSIVE_INPUT_AMOUNT" ); - auto pair = DEXV2Library::pairFor(this->interface_, this->factory_.get(), path[0], path[1]); + auto pair = DEXV2Library::pairFor(this->host_, this->factory_.get(), path[0], path[1]); this->callContractFunction(path.front(), &ERC20::transferFrom, this->getCaller(), pair, amountIn); this->_swap(amounts, path, this->getContractAddress()); this->callContractFunction(this->wrappedNative_.get(), &NativeWrapper::withdraw, amountOut); @@ -333,15 +339,15 @@ std::vector DEXV2Router02::swapExactTokensForNative( const uint256_t& deadline ) { this->ensure(deadline); - if (path.back() != this->wrappedNative_.get()) throw std::runtime_error( + if (path.back() != this->wrappedNative_.get()) throw DynamicException( "DEXV2Router02::swapExactTokensForNative: INVALID_PATH" ); - auto amounts = DEXV2Library::getAmountsOut(this->interface_, this->factory_.get(), amountIn, path); - auto amountOut = amounts.back(); - if (amountOut < amountOutMin) throw std::runtime_error( + auto amounts = DEXV2Library::getAmountsOut(this->host_, this->factory_.get(), amountIn, path); + const auto& amountOut = amounts.back(); + if (amountOut < amountOutMin) throw DynamicException( "DEXV2Router02::swapExactTokensForNative: INSUFFICIENT_OUTPUT_AMOUNT" ); - auto pair = DEXV2Library::pairFor(this->interface_, this->factory_.get(), path[0], path[1]); + auto pair = DEXV2Library::pairFor(this->host_, this->factory_.get(), path[0], path[1]); this->callContractFunction(path.front(), &ERC20::transferFrom, this->getCaller(), pair, amounts[0]); this->_swap(amounts, path, this->getContractAddress()); this->callContractFunction(this->wrappedNative_.get(), &NativeWrapper::withdraw, amountOut); @@ -357,18 +363,26 @@ std::vector DEXV2Router02::swapNativeForExactTokens( const uint256_t& deadline ) { this->ensure(deadline); - if (path[0] != this->wrappedNative_.get()) throw std::runtime_error( + if (path[0] != this->wrappedNative_.get()) throw DynamicException( "DEXV2Router02::swapNativeForExactTokens: INVALID_PATH" ); - auto amounts = DEXV2Library::getAmountsIn(this->interface_, this->factory_.get(), amountOut, path); - auto amountIn = amounts.front(); - if (amountIn > amountInMax) throw std::runtime_error( + auto amounts = DEXV2Library::getAmountsIn(this->host_, this->factory_.get(), amountOut, path); + if (const auto amountIn = amounts.front(); amountIn > amountInMax) throw DynamicException( "DEXV2Router02::swapNativeForExactTokens: EXCESSIVE_INPUT_AMOUNT" ); this->callContractFunction(amounts[0], this->wrappedNative_.get(), &NativeWrapper::deposit); - auto pair = DEXV2Library::pairFor(this->interface_, this->factory_.get(), path[0], path[1]); + auto pair = DEXV2Library::pairFor(this->host_, this->factory_.get(), path[0], path[1]); this->callContractFunction(this->wrappedNative_.get(), &ERC20::transfer, pair, amounts[0]); this->_swap(amounts, path, to); return amounts; } +DBBatch DEXV2Router02::dump() const +{ + DBBatch dbBatch = BaseContract::dump(); + + dbBatch.push_back(StrConv::stringToBytes("factory_"), this->factory_.get().view(), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("wrappedNative_"), this->wrappedNative_.get().view(), this->getDBPrefix()); + + return dbBatch; +} diff --git a/src/contract/templates/dexv2/dexv2router02.h b/src/contract/templates/dexv2/dexv2router02.h index ae3e7d72..3beeb30d 100644 --- a/src/contract/templates/dexv2/dexv2router02.h +++ b/src/contract/templates/dexv2/dexv2router02.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -10,7 +10,6 @@ See the LICENSE.txt file in the project root for more information. #include -#include "../../../utils/contractreflectioninterface.h" #include "../../../utils/db.h" #include "../../dynamiccontract.h" #include "../../variables/safeaddress.h" @@ -24,6 +23,8 @@ See the LICENSE.txt file in the project root for more information. * See: https://uniswap.org/docs/v2/smart-contracts/router02/ */ class DEXV2Router02 : public DynamicContract { + protected: + virtual void receive(const evmc_message& msg) override; private: /// Solidity: address private immutable _factory SafeAddress factory_; @@ -70,7 +71,7 @@ class DEXV2Router02 : public DynamicContract { * ensure() doesn't have to execute the anything after the function call, so we can make it a bool function. * @param deadline The timestamp to check against, in seconds. * @return `true` if deadline has not expired yet. - * @throw std::runtime_error if deadline has expired. + * @throw DynamicException if deadline has expired. */ bool ensure(const uint256_t& deadline) const; @@ -80,30 +81,22 @@ class DEXV2Router02 : public DynamicContract { /** * Constructor for loading contract from DB. - * @param interface Reference to the contract manager interface. * @param address The address where the contract will be deployed. * @param db Reference to the database object. */ - DEXV2Router02( - ContractManagerInterface& interface, - const Address& address, const std::unique_ptr& db - ); + DEXV2Router02(const Address& address, const DB& db); /** * Constructor to be used when creating a new contract. * @param factory The address of the factory contract. * @param wrappedNative The address of the wrapped native token. - * @param interface Reference to the contract manager interface. * @param address The address where the contract will be deployed. * @param creator The address of the creator of the contract. * @param chainId The chain where the contract wil be deployed. - * @param db Reference to the database object. */ DEXV2Router02( const Address& factory, const Address& wrappedNative, - ContractManagerInterface &interface, - const Address &address, const Address &creator, const uint64_t &chainId, - const std::unique_ptr &db + const Address &address, const Address &creator, const uint64_t &chainId ); // Destructor. @@ -315,46 +308,48 @@ class DEXV2Router02 : public DynamicContract { /// Register the contract functions to the ContractReflectionInterface. static void registerContract() { - ContractReflectionInterface::registerContractMethods< - DEXV2Router02, const Address &, const Address &, ContractManagerInterface &, - const Address &, const Address &, const uint64_t &, - const std::unique_ptr & - >( - std::vector{"factory", "wrappedNative"}, - std::make_tuple("factory", &DEXV2Router02::factory, FunctionTypes::View, std::vector{}), - std::make_tuple("wrappedNative", &DEXV2Router02::wrappedNative, FunctionTypes::View, std::vector{}), - std::make_tuple("addLiquidity", &DEXV2Router02::addLiquidity, FunctionTypes::NonPayable, - std::vector{"tokenA", "tokenB", "amountADesired", "amountBDesired", "amountAMin", "amountBMin", "to", "deadline"} - ), - std::make_tuple("addLiquidityNative", &DEXV2Router02::addLiquidityNative, FunctionTypes::Payable, - std::vector{"token", "amountTokenDesired", "amountTokenMin", "amountNativeMin", "to", "deadline"} - ), - std::make_tuple("removeLiquidity", &DEXV2Router02::removeLiquidity, FunctionTypes::NonPayable, - std::vector{"tokenA", "tokenB", "liquidity", "amountAMin", "amountBMin", "to", "deadline"} - ), - std::make_tuple("removeLiquidityNative", &DEXV2Router02::removeLiquidityNative, FunctionTypes::Payable, - std::vector{"token", "liquidity", "amountTokenMin", "amountNativeMin", "to", "deadline"} - ), - std::make_tuple("swapExactTokensForTokens", &DEXV2Router02::swapExactTokensForTokens, FunctionTypes::NonPayable, - std::vector{"amountIn", "amountOutMin", "path", "to", "deadline"} - ), - std::make_tuple("swapTokensForExactTokens", &DEXV2Router02::swapTokensForExactTokens, FunctionTypes::NonPayable, - std::vector{"amountOut", "amountInMax", "path", "to", "deadline"} - ), - std::make_tuple("swapExactNativeForTokens", &DEXV2Router02::swapExactNativeForTokens, FunctionTypes::Payable, - std::vector{"amountOutMin", "path", "to", "deadline"} - ), - std::make_tuple("swapTokensForExactNative", &DEXV2Router02::swapTokensForExactNative, FunctionTypes::Payable, - std::vector{"amountIn", "amountOutMin", "path", "to", "deadline"} - ), - std::make_tuple("swapExactTokensForNative", &DEXV2Router02::swapExactTokensForNative, FunctionTypes::Payable, - std::vector{"amountIn", "amountOutMin", "path", "to", "deadline"} - ), - std::make_tuple("swapNativeForExactTokens", &DEXV2Router02::swapNativeForExactTokens, FunctionTypes::Payable, - std::vector{"amountOut", "amountInMax", "path", "to", "deadline"} - ) - ); + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{"factory", "wrappedNative"}, + std::make_tuple("factory", &DEXV2Router02::factory, FunctionTypes::View, std::vector{}), + std::make_tuple("wrappedNative", &DEXV2Router02::wrappedNative, FunctionTypes::View, std::vector{}), + std::make_tuple("addLiquidity", &DEXV2Router02::addLiquidity, FunctionTypes::NonPayable, + std::vector{"tokenA", "tokenB", "amountADesired", "amountBDesired", "amountAMin", "amountBMin", "to", "deadline"} + ), + std::make_tuple("addLiquidityNative", &DEXV2Router02::addLiquidityNative, FunctionTypes::Payable, + std::vector{"token", "amountTokenDesired", "amountTokenMin", "amountNativeMin", "to", "deadline"} + ), + std::make_tuple("removeLiquidity", &DEXV2Router02::removeLiquidity, FunctionTypes::NonPayable, + std::vector{"tokenA", "tokenB", "liquidity", "amountAMin", "amountBMin", "to", "deadline"} + ), + std::make_tuple("removeLiquidityNative", &DEXV2Router02::removeLiquidityNative, FunctionTypes::Payable, + std::vector{"token", "liquidity", "amountTokenMin", "amountNativeMin", "to", "deadline"} + ), + std::make_tuple("swapExactTokensForTokens", &DEXV2Router02::swapExactTokensForTokens, FunctionTypes::NonPayable, + std::vector{"amountIn", "amountOutMin", "path", "to", "deadline"} + ), + std::make_tuple("swapTokensForExactTokens", &DEXV2Router02::swapTokensForExactTokens, FunctionTypes::NonPayable, + std::vector{"amountOut", "amountInMax", "path", "to", "deadline"} + ), + std::make_tuple("swapExactNativeForTokens", &DEXV2Router02::swapExactNativeForTokens, FunctionTypes::Payable, + std::vector{"amountOutMin", "path", "to", "deadline"} + ), + std::make_tuple("swapTokensForExactNative", &DEXV2Router02::swapTokensForExactNative, FunctionTypes::Payable, + std::vector{"amountIn", "amountOutMin", "path", "to", "deadline"} + ), + std::make_tuple("swapExactTokensForNative", &DEXV2Router02::swapExactTokensForNative, FunctionTypes::Payable, + std::vector{"amountIn", "amountOutMin", "path", "to", "deadline"} + ), + std::make_tuple("swapNativeForExactTokens", &DEXV2Router02::swapNativeForExactTokens, FunctionTypes::Payable, + std::vector{"amountOut", "amountInMax", "path", "to", "deadline"} + ) + ); + }); } + + /// Dump method + DBBatch dump() const override; }; #endif // DEXV2ROUTER_H diff --git a/src/contract/templates/dexv2/uq112x112.h b/src/contract/templates/dexv2/uq112x112.h index 5a8ca1cc..45b13190 100644 --- a/src/contract/templates/dexv2/uq112x112.h +++ b/src/contract/templates/dexv2/uq112x112.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. diff --git a/src/contract/templates/erc20.cpp b/src/contract/templates/erc20.cpp deleted file mode 100644 index 9028973d..00000000 --- a/src/contract/templates/erc20.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* -Copyright (c) [2023-2024] [Sparq Network] - -This software is distributed under the MIT License. -See the LICENSE.txt file in the project root for more information. -*/ - -#include "erc20.h" - -// Default Constructor when loading contract from DB. -ERC20::ERC20(ContractManagerInterface &interface, const Address& address, const std::unique_ptr &db) : - DynamicContract(interface, address, db), name_(this), symbol_(this), decimals_(this), totalSupply_(this), balances_(this), allowed_(this) { - - this->name_ = Utils::bytesToString(db->get(std::string("name_"), this->getDBPrefix())); - this->symbol_ = Utils::bytesToString(db->get(std::string("symbol_"), this->getDBPrefix())); - this->decimals_ = Utils::bytesToUint8(db->get(std::string("decimals_"), this->getDBPrefix())); - this->totalSupply_ = Utils::bytesToUint256(db->get(std::string("totalSupply_"), this->getDBPrefix())); - auto balances = db->getBatch(this->getNewPrefix("balances_")); - for (const auto& dbEntry : balances) { - this->balances_[Address(dbEntry.key)] = Utils::fromBigEndian(dbEntry.value); - } - - auto allowances = db->getBatch(this->getNewPrefix("allowed_")); - for (const auto& dbEntry : allowances) { - BytesArrView valueView(dbEntry.value); - this->allowed_[Address(dbEntry.key)][Address(valueView.subspan(0, 20))] = Utils::fromBigEndian(valueView.subspan(20)); - } - this->registerContractFunctions(); - - this->name_.commit(); - this->symbol_.commit(); - this->decimals_.commit(); - this->totalSupply_.commit(); - this->balances_.commit(); - this->allowed_.commit(); -} - -ERC20::ERC20( - const std::string& erc20name_, const std::string& erc20symbol_, - const uint8_t& erc20decimals_, const uint256_t& mintValue, - ContractManagerInterface& interface, - const Address& address, const Address& creator, const uint64_t& chainId, - const std::unique_ptr& db -) : DynamicContract(interface, "ERC20", address, creator, chainId, db), - name_(this), symbol_(this), decimals_(this), totalSupply_(this), balances_(this), allowed_(this) -{ - name_ = erc20name_; - symbol_ = erc20symbol_; - decimals_ = erc20decimals_; - mintValue_(creator, mintValue); - this->registerContractFunctions(); - name_.commit(); - symbol_.commit(); - decimals_.commit(); -} - -ERC20::ERC20( - const std::string &derivedTypeName, const std::string& erc20name_, const std::string& erc20symbol_, - const uint8_t& erc20decimals_, const uint256_t& mintValue, - ContractManagerInterface& interface, - const Address& address, const Address& creator, const uint64_t& chainId, - const std::unique_ptr& db -) : DynamicContract(interface, derivedTypeName, address, creator, chainId, db), - name_(this), symbol_(this), decimals_(this), totalSupply_(this), balances_(this), allowed_(this) -{ - name_ = erc20name_; - symbol_ = erc20symbol_; - decimals_ = erc20decimals_; - mintValue_(creator, mintValue); - this->registerContractFunctions(); -} - - -ERC20::~ERC20() { - DBBatch batchOperations; - this->db_->put(std::string("name_"), name_.get(), this->getDBPrefix()); - this->db_->put(std::string("symbol_"), symbol_.get(), this->getDBPrefix()); - this->db_->put(std::string("decimals_"), Utils::uint8ToBytes(decimals_.get()), this->getDBPrefix()); - this->db_->put(std::string("totalSupply_"), Utils::uint256ToBytes(totalSupply_.get()), this->getDBPrefix()); - - for (auto it = balances_.cbegin(); it != balances_.cend(); ++it) { - const auto& key = it->first.get(); - Bytes value = Utils::uintToBytes(it->second); - batchOperations.push_back(key, value, this->getNewPrefix("balances_")); - } - - for (auto it = allowed_.cbegin(); it != allowed_.cend(); ++it) { - for (auto it2 = it->second.cbegin(); it2 != it->second.cend(); ++it2) { - const auto& key = it->first.get(); - Bytes value = it2->first.asBytes(); - Utils::appendBytes(value, Utils::uintToBytes(it2->second)); - batchOperations.push_back(key, value, this->getNewPrefix("allowed_")); - } - } - this->db_->putBatch(batchOperations); -} - -void ERC20::registerContractFunctions() { - registerContract(); - this->registerMemberFunction("name", &ERC20::name, FunctionTypes::View, this); - this->registerMemberFunction("symbol", &ERC20::symbol, FunctionTypes::View, this); - this->registerMemberFunction("decimals", &ERC20::decimals, FunctionTypes::View, this); - this->registerMemberFunction("totalSupply", &ERC20::totalSupply, FunctionTypes::View, this); - this->registerMemberFunction("balanceOf", &ERC20::balanceOf, FunctionTypes::View, this); - this->registerMemberFunction("allowance", &ERC20::allowance, FunctionTypes::View, this); - this->registerMemberFunction("transfer", &ERC20::transfer, FunctionTypes::NonPayable, this); - this->registerMemberFunction("approve", &ERC20::approve, FunctionTypes::NonPayable, this); - this->registerMemberFunction("transferFrom", &ERC20::transferFrom, FunctionTypes::NonPayable, this); -} - -void ERC20::mintValue_(const Address& address, const uint256_t& value) { - balances_[address] += value; - totalSupply_ += value; -} - -void ERC20::burnValue_(const Address& address, const uint256_t& value) { - balances_[address] -= value; - totalSupply_ -= value; -} - -std::string ERC20::name() const { return this->name_.get(); } - -std::string ERC20::symbol() const { return this->symbol_.get(); } - -uint8_t ERC20::decimals() const { return this->decimals_.get(); } - -uint256_t ERC20::totalSupply() const { return this->totalSupply_.get(); } - -uint256_t ERC20::balanceOf(const Address& owner) const { - const auto& it = std::as_const(this->balances_).find(owner); - return (it == this->balances_.end()) - ? 0 : it->second; -} - -void ERC20::transfer(const Address &to, const uint256_t &value) { - this->balances_[this->getCaller()] -= value; - this->balances_[to] += value; -} - -void ERC20::approve(const Address &spender, const uint256_t &value) { - this->allowed_[this->getCaller()][spender] = value; -} - -uint256_t ERC20::allowance(const Address& owner, const Address& spender) const { - const auto& it = std::as_const(this->allowed_).find(owner); - if (it == this->allowed_.end()) { - return 0; - } else { - const auto& it2 = it->second.find(spender); - if (it2 == it->second.end()) { - return 0; - } else { - return it2->second; - } - } -} - -void ERC20::transferFrom( - const Address &from, const Address &to, const uint256_t &value -) { - this->allowed_[from][this->getCaller()] -= value; - this->balances_[from] -= value; - this->balances_[to] += value; -} - diff --git a/src/contract/templates/erc20wrapper.cpp b/src/contract/templates/erc20wrapper.cpp index 135e324a..cf4eb4f1 100644 --- a/src/contract/templates/erc20wrapper.cpp +++ b/src/contract/templates/erc20wrapper.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -7,74 +7,61 @@ See the LICENSE.txt file in the project root for more information. #include "erc20wrapper.h" -ERC20Wrapper::ERC20Wrapper( - ContractManagerInterface& interface, - const Address& contractAddress, const std::unique_ptr& db -) : DynamicContract(interface, contractAddress, db), tokensAndBalances_(this) { - registerContractFunctions(); - auto tokensAndBalances = this->db_->getBatch(this->getNewPrefix("tokensAndBalances_")); - for (const auto& dbEntry : tokensAndBalances) { - BytesArrView valueView(dbEntry.value); +ERC20Wrapper::ERC20Wrapper(const Address& contractAddress, const DB& db +) : DynamicContract(contractAddress, db), tokensAndBalances_(this) +{ + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("tokensAndBalances_"))) { + View valueView(dbEntry.value); this->tokensAndBalances_[Address(dbEntry.key)][Address(valueView.subspan(0, 20))] = Utils::fromBigEndian(valueView.subspan(20)); } - tokensAndBalances_.commit(); + + this->tokensAndBalances_.commit(); + + registerContractFunctions(); + + this->tokensAndBalances_.enableRegister(); } -ERC20Wrapper::ERC20Wrapper( - ContractManagerInterface& interface, const Address& address, - const Address& creator, const uint64_t& chainId, const std::unique_ptr& db -) : DynamicContract(interface, "ERC20Wrapper", address, creator, chainId, db), - tokensAndBalances_(this) +ERC20Wrapper::ERC20Wrapper(const Address& address, const Address& creator, const uint64_t& chainId +) : DynamicContract("ERC20Wrapper", address, creator, chainId), tokensAndBalances_(this) { + this->tokensAndBalances_.commit(); + registerContractFunctions(); - tokensAndBalances_.commit(); -} -ERC20Wrapper::~ERC20Wrapper() { - DBBatch tokensAndBalancesBatch; - for (auto it = tokensAndBalances_.cbegin(); it != tokensAndBalances_.cend(); ++it) { - for (auto it2 = it->second.cbegin(); it2 != it->second.cend(); ++it2) { - const auto& key = it->first.get(); - Bytes value = it2->first.asBytes(); - Utils::appendBytes(value, Utils::uintToBytes(it2->second)); - tokensAndBalancesBatch.push_back(key, value, this->getNewPrefix("tokensAndBalances_")); - } - } - this->db_->putBatch(tokensAndBalancesBatch); + this->tokensAndBalances_.enableRegister(); } +ERC20Wrapper::~ERC20Wrapper() {} + uint256_t ERC20Wrapper::getContractBalance(const Address& token) const { return this->callContractViewFunction(token, &ERC20::balanceOf, this->getContractAddress()); } uint256_t ERC20Wrapper::getUserBalance(const Address& token, const Address& user) const { auto it = this->tokensAndBalances_.find(token); - if (it == this->tokensAndBalances_.end()) { - return 0; - } + if (it == this->tokensAndBalances_.cend()) return 0; auto itUser = it->second.find(user); - if (itUser == it->second.end()) { - return 0; - } + if (itUser == it->second.cend()) return 0; return itUser->second; } void ERC20Wrapper::withdraw(const Address& token, const uint256_t& value) { auto it = this->tokensAndBalances_.find(token); - if (it == this->tokensAndBalances_.end()) throw std::runtime_error("Token not found"); + if (it == this->tokensAndBalances_.end()) throw DynamicException("Token not found"); auto itUser = it->second.find(this->getCaller()); - if (itUser == it->second.end()) throw std::runtime_error("User not found"); - if (itUser->second <= value) throw std::runtime_error("ERC20Wrapper: Not enough balance"); + if (itUser == it->second.end()) throw DynamicException("User not found"); + if (itUser->second <= value) throw DynamicException("ERC20Wrapper: Not enough balance"); itUser->second -= value; this->callContractFunction(token, &ERC20::transfer, this->getCaller(), value); } void ERC20Wrapper::transferTo(const Address& token, const Address& to, const uint256_t& value) { auto it = this->tokensAndBalances_.find(token); - if (it == this->tokensAndBalances_.end()) throw std::runtime_error("Token not found"); + if (it == this->tokensAndBalances_.end()) throw DynamicException("Token not found"); auto itUser = it->second.find(this->getCaller()); - if (itUser == it->second.end()) throw std::runtime_error("User not found"); - if (itUser->second <= value) throw std::runtime_error("ERC20Wrapper: Not enough balance"); + if (itUser == it->second.end()) throw DynamicException("User not found"); + if (itUser->second <= value) throw DynamicException("ERC20Wrapper: Not enough balance"); itUser->second -= value; this->callContractFunction(token, &ERC20::transfer, to, value); } @@ -86,10 +73,24 @@ void ERC20Wrapper::deposit(const Address& token, const uint256_t& value) { void ERC20Wrapper::registerContractFunctions() { registerContract(); - this->registerMemberFunction("getContractBalance", &ERC20Wrapper::getContractBalance, FunctionTypes::View, this); - this->registerMemberFunction("getUserBalance", &ERC20Wrapper::getUserBalance, FunctionTypes::View, this); - this->registerMemberFunction("withdraw", &ERC20Wrapper::withdraw, FunctionTypes::NonPayable, this); - this->registerMemberFunction("transferTo", &ERC20Wrapper::transferTo, FunctionTypes::NonPayable, this); - this->registerMemberFunction("deposit", &ERC20Wrapper::deposit, FunctionTypes::NonPayable, this); + this->registerMemberFunctions( + std::make_tuple("getContractBalance", &ERC20Wrapper::getContractBalance, FunctionTypes::View, this), + std::make_tuple("getUserBalance", &ERC20Wrapper::getUserBalance, FunctionTypes::View, this), + std::make_tuple("withdraw", &ERC20Wrapper::withdraw, FunctionTypes::NonPayable, this), + std::make_tuple("transferTo", &ERC20Wrapper::transferTo, FunctionTypes::NonPayable, this), + std::make_tuple("deposit", &ERC20Wrapper::deposit, FunctionTypes::NonPayable, this) + ); } +DBBatch ERC20Wrapper::dump() const { + DBBatch dbBatch = BaseContract::dump(); + for (auto i = tokensAndBalances_.cbegin(); i != tokensAndBalances_.cend(); ++i) { + for (auto j = i->second.cbegin(); j != i->second.cend(); ++j) { + const auto& key = i->first; + Bytes value = j->first.asBytes(); + Utils::appendBytes(value, Utils::uintToBytes(j->second)); + dbBatch.push_back(key, value, this->getNewPrefix("tokensAndBalances_")); + } + } + return dbBatch; +} diff --git a/src/contract/templates/erc20wrapper.h b/src/contract/templates/erc20wrapper.h index fb557a97..89346041 100644 --- a/src/contract/templates/erc20wrapper.h +++ b/src/contract/templates/erc20wrapper.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -12,11 +12,12 @@ See the LICENSE.txt file in the project root for more information. #include #include "../../utils/db.h" +#include "../../utils/utils.h" // using uint256_t #include "../abi.h" #include "../contractmanager.h" #include "../dynamiccontract.h" #include "../variables/safeunorderedmap.h" -#include "erc20.h" +#include "standards/erc20.h" /// Template for an ERC20Wrapper contract. class ERC20Wrapper : public DynamicContract { @@ -25,57 +26,47 @@ class ERC20Wrapper : public DynamicContract { * Map for tokens and balances. Solidity counterpart: * mapping(address => mapping(address => uint256)) internal tokensAndBalances_; */ - SafeUnorderedMap> tokensAndBalances_; + SafeUnorderedMap> tokensAndBalances_; /// Function for calling the register functions for contracts. void registerContractFunctions() override; public: - /** - * ConstructorArguments is a tuple of the contract constructor arguments in the order they appear in the constructor. - */ + * ConstructorArguments is a tuple of the contract constructor arguments in the order they appear in the constructor. + */ using ConstructorArguments = std::tuple<>; /** * Constructor for loading contract from DB. - * @param interface Reference to the contract manager interface. * @param contractAddress The address where the contract will be deployed. * @param db Reference pointer to the database object. */ - ERC20Wrapper( - ContractManagerInterface& interface, - const Address& contractAddress, const std::unique_ptr& db - ); + ERC20Wrapper(const Address& contractAddress, const DB& db); /** * Constructor for building a new contract from scratch. - * @param interface Reference to the contract manager interface. * @param address The address where the contract will be deployed. * @param creator The address of the creator of the contract. * @param chainId The chain id of the contract. - * @param db Reference pointer to the database object. */ ERC20Wrapper( - ContractManagerInterface& interface, - const Address& address, const Address& creator, - const uint64_t& chainId, const std::unique_ptr& db + const Address& address, const Address& creator, const uint64_t& chainId ); /// Register contract class via ContractReflectionInterface. static void registerContract() { - ContractReflectionInterface::registerContractMethods< - ERC20Wrapper, ContractManagerInterface&, - const Address&, const Address&, const uint64_t&, - const std::unique_ptr& - >( - std::vector{}, - std::make_tuple("getContractBalance", &ERC20Wrapper::getContractBalance, FunctionTypes::View, std::vector{"token"}), - std::make_tuple("getUserBalance", &ERC20Wrapper::getUserBalance, FunctionTypes::View, std::vector{"token", "user"}), - std::make_tuple("withdraw", &ERC20Wrapper::withdraw, FunctionTypes::NonPayable, std::vector{"token", "value"}), - std::make_tuple("transferTo", &ERC20Wrapper::transferTo, FunctionTypes::NonPayable, std::vector{"token", "to", "value"}), - std::make_tuple("deposit", &ERC20Wrapper::deposit, FunctionTypes::NonPayable, std::vector{"token", "value"}) - ); + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{}, + std::make_tuple("getContractBalance", &ERC20Wrapper::getContractBalance, FunctionTypes::View, std::vector{"token"}), + std::make_tuple("getUserBalance", &ERC20Wrapper::getUserBalance, FunctionTypes::View, std::vector{"token", "user"}), + std::make_tuple("withdraw", &ERC20Wrapper::withdraw, FunctionTypes::NonPayable, std::vector{"token", "value"}), + std::make_tuple("transferTo", &ERC20Wrapper::transferTo, FunctionTypes::NonPayable, std::vector{"token", "to", "value"}), + std::make_tuple("deposit", &ERC20Wrapper::deposit, FunctionTypes::NonPayable, std::vector{"token", "value"}) + ); + }); } /// Destructor. @@ -101,7 +92,7 @@ class ERC20Wrapper : public DynamicContract { * function withdraw (address _token, uint256 _value) public returns (bool) * @param token The address of the token. * @param value The amount of tokens to withdraw. - * @throw std::runtime_error if the contract does not have enough tokens, + * @throw DynamicException if the contract does not have enough tokens, * or if the token was not found. */ void withdraw(const Address& token, const uint256_t& value); @@ -112,7 +103,7 @@ class ERC20Wrapper : public DynamicContract { * @param token The address of the token. * @param to The address of the user to send tokens to. * @param value The amount of tokens to transfer. - * @throw std::runtime_error if the contract does not have enough tokens, + * @throw DynamicException if the contract does not have enough tokens, * or if either the token or the user were not found. */ void transferTo(const Address& token, const Address& to, const uint256_t& value); @@ -124,6 +115,9 @@ class ERC20Wrapper : public DynamicContract { * @param value The amount of tokens to deposit. */ void deposit(const Address& token, const uint256_t& value); + + /// Dump method + DBBatch dump() const override; }; #endif // ERC20WRAPPER_H diff --git a/src/contract/templates/erc721.cpp b/src/contract/templates/erc721.cpp deleted file mode 100644 index 8a2fbd85..00000000 --- a/src/contract/templates/erc721.cpp +++ /dev/null @@ -1,281 +0,0 @@ -/* -Copyright (c) [2023-2024] [Sparq Network] - -This software is distributed under the MIT License. -See the LICENSE.txt file in the project root for more information. -*/ - -#include "erc721.h" - -ERC721::ERC721( - ContractManagerInterface& interface, const Address& address, const std::unique_ptr& db -) : DynamicContract(interface, address, db), name_(this), symbol_(this), - owners_(this), balances_(this), tokenApprovals_(this), operatorAddressApprovals_(this) -{ - this->name_ = Utils::bytesToString(db->get(std::string("name_"), this->getDBPrefix())); - this->symbol_ = Utils::bytesToString(db->get(std::string("symbol_"), this->getDBPrefix())); - - auto owners = db->getBatch(this->getNewPrefix("owners_")); - for (const auto& dbEntry : owners) { - BytesArrView valueView(dbEntry.value); - this->owners_[Utils::fromBigEndian(dbEntry.key)] = Address(valueView.subspan(0, 20)); - } - - auto balances = db->getBatch(this->getNewPrefix("balances_")); - for (const auto& dbEntry : balances) { - this->balances_[Address(dbEntry.key)] = Utils::fromBigEndian(dbEntry.value); - } - - auto approvals = db->getBatch(this->getNewPrefix("tokenApprovals_")); - for (const auto& dbEntry : approvals) { - this->tokenApprovals_[Utils::fromBigEndian(dbEntry.key)] = Address(dbEntry.value); - } - - auto operatorAddressApprovals = db->getBatch(this->getNewPrefix("operatorAddressApprovals_")); - for (const auto& dbEntry : operatorAddressApprovals) { - BytesArrView valueView(dbEntry.value); - this->operatorAddressApprovals_[Address(dbEntry.key)][Address(valueView.subspan(0, 20))] = valueView[20]; - } - - this->registerContractFunctions(); -} - -ERC721::ERC721( - const std::string &erc721name, const std::string &erc721symbol_, - ContractManagerInterface &interface, - const Address &address, const Address &creator, const uint64_t &chainId, - const std::unique_ptr &db -) : DynamicContract(interface, "ERC721", address, creator, chainId, db), name_(this, erc721name), - symbol_(this, erc721symbol_), owners_(this), balances_(this), tokenApprovals_(this), operatorAddressApprovals_(this) { - this->name_.commit(); - this->symbol_.commit(); - this->registerContractFunctions(); -} - -ERC721::ERC721( - const std::string &derivedTypeName, - const std::string &erc721name, const std::string &erc721symbol_, - ContractManagerInterface &interface, - const Address &address, const Address &creator, const uint64_t &chainId, - const std::unique_ptr &db -) : DynamicContract(interface, derivedTypeName, address, creator, chainId, db), name_(this, erc721name), - symbol_(this, erc721symbol_), owners_(this), balances_(this), tokenApprovals_(this), operatorAddressApprovals_(this) { - this->name_.commit(); - this->symbol_.commit(); - this->registerContractFunctions(); -} - -ERC721::~ERC721() { - DBBatch batchedOperations; - - this->db_->put(std::string("name_"), name_.get(), this->getDBPrefix()); - this->db_->put(std::string("symbol_"), symbol_.get(), this->getDBPrefix()); - - for (auto it = owners_.cbegin(), end = owners_.cend(); it != end; ++it) { - // key: uint -> value: Address - batchedOperations.push_back(Utils::uintToBytes(it->first), it->second.get(), this->getNewPrefix("owners_")); - } - - for (auto it = balances_.cbegin(), end = balances_.cend(); it != end; ++it) { - // key: Address -> value: uint - batchedOperations.push_back(it->first.get(), Utils::uintToBytes(it->second), this->getNewPrefix("balances_")); - } - - for (auto it = tokenApprovals_.cbegin(), end = tokenApprovals_.cend(); it != end; ++it) { - // key: uint -> value: Address - batchedOperations.push_back(Utils::uintToBytes(it->first), it->second.get(), this->getNewPrefix("tokenApprovals_")); - } - - for (auto it = operatorAddressApprovals_.cbegin(); it != operatorAddressApprovals_.cend(); ++it) { - for (auto it2 = it->second.cbegin(); it2 != it->second.cend(); ++it2) { - // key: address -> value: address + bool (1 byte) - const auto& key = it->first.get(); - Bytes value = it2->first.asBytes(); - value.insert(value.end(), char(it2->second)); - batchedOperations.push_back(key, value, this->getNewPrefix("operatorAddressApprovals_")); - } - } -} - -void ERC721::registerContractFunctions() { - this->registerContract(); - this->registerMemberFunction("name", &ERC721::name, FunctionTypes::View, this); - this->registerMemberFunction("symbol", &ERC721::symbol, FunctionTypes::View, this); - this->registerMemberFunction("balanceOf", &ERC721::balanceOf, FunctionTypes::View, this); - this->registerMemberFunction("ownerOf", &ERC721::ownerOf, FunctionTypes::View, this); - this->registerMemberFunction("approve", &ERC721::approve, FunctionTypes::NonPayable, this); - this->registerMemberFunction("getApproved", &ERC721::getApproved, FunctionTypes::View, this); - this->registerMemberFunction("setApprovalForAll", &ERC721::setApprovalForAll, FunctionTypes::NonPayable, this); - this->registerMemberFunction("isApprovedForAll", &ERC721::isApprovedForAll, FunctionTypes::View, this); - this->registerMemberFunction("transferFrom", &ERC721::transferFrom, FunctionTypes::NonPayable, this); -} - -Address ERC721::ownerOf_(const uint256_t& tokenId) const { - auto it = this->owners_.find(tokenId); - if (it == this->owners_.end()) { - return Address(); - } - return it->second; -} - -Address ERC721::getApproved_(const uint256_t& tokenId) const { - auto it = this->tokenApprovals_.find(tokenId); - if (it == this->tokenApprovals_.end()) { - return Address(); - } - return it->second; -} - -Address ERC721::update_(const Address& to, const uint256_t& tokenId, const Address& auth) { - Address from = this->ownerOf_(tokenId); - if (auth) { - this->checkAuthorized_(from, auth, tokenId); - } - if (from) { - this->tokenApprovals_[tokenId] = Address(); - this->balances_[from]--; - } - if (to) { - this->balances_[to]++; - } - this->owners_[tokenId] = to; - return from; -} - -void ERC721::checkAuthorized_(const Address& owner, const Address& spender, const uint256_t& tokenId) const { - if (!this->isAuthorized_(owner, spender, tokenId)) { - if (owner) { - throw std::runtime_error("ERC721::checkAuthorized_: Not authorized"); - } - throw std::runtime_error("ERC721::checkAuthorized_: inexistent token"); - } -} - -bool ERC721::isAuthorized_(const Address& owner, const Address& spender, const uint256_t& tokenId) const { - if (spender == owner) { return true; } - if (spender == Address()) { return false; } - if (this->isApprovedForAll(owner, spender)) { return true; } - if (this->getApproved_(tokenId) == spender) { return true; } - return false; -} - -void ERC721::mint_(const Address& to, const uint256_t& tokenId) { - if (to == Address()) { - throw std::runtime_error("ERC721::mint_: mint to the zero address"); - } - Address prevOwner = this->update_(to, tokenId, Address()); -} - -void ERC721::burn_(const uint256_t& tokenId) { - Address prevOwner = this->update_(Address(), tokenId, Address()); - if (prevOwner == Address()) { - throw std::runtime_error("ERC721::burn_: inexistent token"); - } -} - -void ERC721::transfer_(const Address& from, const Address& to, const uint256_t& tokenId) { - if (to == Address()) { - throw std::runtime_error("ERC721::transfer_: transfer to the zero address"); - } - - Address prevOwner = this->update_(to, tokenId, Address()); - if (prevOwner == Address()) { - throw std::runtime_error("ERC721::transfer_: inexistent token"); - } else if (prevOwner != from) { - throw std::runtime_error("ERC721::transfer_: incorrect owner"); - } -} - -Address ERC721::approve_(const Address& to, const uint256_t& tokenId, const Address& auth) { - Address owner = this->ownerOf(tokenId); - - if (auth != Address() && owner != auth && !this->isApprovedForAll(owner, auth)) { - throw std::runtime_error("ERC721::approve_: Not authorized"); - } - - this->tokenApprovals_[tokenId] = to; - - return owner; -} - -std::string ERC721::name() const { - return this->name_.get(); -} - -std::string ERC721::symbol() const { - return this->symbol_.get(); -} - -uint256_t ERC721::balanceOf(const Address& owner) const { - if (owner == Address()) { - throw std::runtime_error("ERC721::balanceOf: zero address"); - } - auto it = this->balances_.find(owner); - if (it == this->balances_.end()) { - return 0; - } - return it->second; -} - -Address ERC721::ownerOf(const uint256_t& tokenId) const { - Address owner = this->ownerOf_(tokenId); - if (owner == Address()) { - throw std::runtime_error("ERC721::ownerOf: inexistent token"); - } - return owner; -} - -std::string ERC721::tokenURI(const uint256_t& tokenId) const { - this->requireMinted_(tokenId); - return this->baseURI_() + tokenId.str(); -} - -void ERC721::approve(const Address& to, const uint256_t& tokenId) { - approve_(to, tokenId, this->getCaller()); -} - -Address ERC721::getApproved(const uint256_t &tokenId) const { - this->requireMinted_(tokenId); - return this->getApproved_(tokenId); -} - -void ERC721::setApprovalForAll(const Address& operatorAddress, const bool& approved) { - this->setApprovalForAll_(this->getCaller(), operatorAddress, approved); -} - -void ERC721::setApprovalForAll_(const Address& owner, const Address& operatorAddress, bool approved) { - if (operatorAddress == Address()) { - throw std::runtime_error("ERC721::setApprovalForAll_: zero address"); - } - this->operatorAddressApprovals_[owner][operatorAddress] = approved; -} - -void ERC721::requireMinted_(const uint256_t& tokenId) const { - if (this->ownerOf_(tokenId) == Address()) { - throw std::runtime_error("ERC721::requireMinted_: inexistent token"); - } -} - -bool ERC721::isApprovedForAll(const Address& owner, const Address& operatorAddress) const { - auto it = this->operatorAddressApprovals_.find(owner); - if (it == this->operatorAddressApprovals_.end()) { - return false; - } - auto it2 = it->second.find(operatorAddress); - if (it2 == it->second.end()) { - return false; - } - return it2->second; -} - -void ERC721::transferFrom(const Address& from, const Address& to, const uint256_t& tokenId) { - if (to == Address()) { - throw std::runtime_error("ERC721::transferFrom: transfer to the zero address"); - } - Address prevOwner = this->update_(to, tokenId, this->getCaller()); - if (prevOwner == Address()) { - throw std::runtime_error("ERC721::transferFrom: inexistent token"); - } else if (prevOwner != from) { - throw std::runtime_error("ERC721::transferFrom: incorrect owner"); - } -} diff --git a/src/contract/templates/erc721test.cpp b/src/contract/templates/erc721test.cpp new file mode 100644 index 00000000..4cac49b8 --- /dev/null +++ b/src/contract/templates/erc721test.cpp @@ -0,0 +1,81 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "erc721test.h" + +#include "../../utils/uintconv.h" +#include "../../utils/strconv.h" + +ERC721Test::ERC721Test(const Address &address, const DB& db) +: DynamicContract(address, db), + ERC721(address, db), + tokenIdCounter_(this), maxTokens_(this), totalSupply_(this) +{ + this->tokenIdCounter_ = UintConv::bytesToUint64(db.get(std::string("tokenIdCounter_"), this->getDBPrefix())); + this->maxTokens_ = UintConv::bytesToUint64(db.get(std::string("maxTokens_"), this->getDBPrefix())); + this->totalSupply_ = UintConv::bytesToUint64(db.get(std::string("totalSupply_"), this->getDBPrefix())); + + this->tokenIdCounter_.commit(); + this->maxTokens_.commit(); + this->totalSupply_.commit(); + + ERC721Test::registerContractFunctions(); + + this->tokenIdCounter_.enableRegister(); + this->maxTokens_.enableRegister(); + this->totalSupply_.enableRegister(); +} + +ERC721Test::ERC721Test( + const std::string &erc721name, const std::string &erc721symbol, const uint64_t& maxTokens, + const Address &address, const Address &creator, const uint64_t &chainId +) : DynamicContract("ERC721Test", address, creator, chainId), + ERC721(erc721name, erc721symbol, address, creator, chainId), + tokenIdCounter_(this, 0), maxTokens_(this, maxTokens), totalSupply_(this, 0) +{ + this->tokenIdCounter_.commit(); + this->maxTokens_.commit(); + this->totalSupply_.commit(); + + ERC721Test::registerContractFunctions(); + + this->tokenIdCounter_.enableRegister(); + this->maxTokens_.enableRegister(); + this->totalSupply_.enableRegister(); +} + +DBBatch ERC721Test::dump() const { + DBBatch batch = ERC721::dump(); + batch.push_back(StrConv::stringToBytes("tokenIdCounter_"), UintConv::uint64ToBytes(this->tokenIdCounter_.get()), this->getDBPrefix()); + batch.push_back(StrConv::stringToBytes("maxTokens_"), UintConv::uint64ToBytes(this->maxTokens_.get()), this->getDBPrefix()); + batch.push_back(StrConv::stringToBytes("totalSupply_"), UintConv::uint64ToBytes(this->totalSupply_.get()), this->getDBPrefix()); + return batch; +} + +void ERC721Test::registerContractFunctions() { + ERC721Test::registerContract(); + this->registerMemberFunctions( + std::make_tuple("mint", &ERC721Test::mint, FunctionTypes::NonPayable, this), + std::make_tuple("burn", &ERC721Test::burn, FunctionTypes::NonPayable, this), + std::make_tuple("tokenIdCounter", &ERC721Test::tokenIdCounter, FunctionTypes::View, this), + std::make_tuple("maxTokens", &ERC721Test::maxTokens, FunctionTypes::View, this), + std::make_tuple("totalSupply", &ERC721Test::totalSupply, FunctionTypes::View, this) + ); +} + +void ERC721Test::mint(const Address& to) { + if (this->tokenIdCounter_ >= this->maxTokens_) throw DynamicException("Max tokens reached"); + this->mint_(to, tokenIdCounter_.get()); + ++this->tokenIdCounter_; + ++this->totalSupply_; +} + +void ERC721Test::burn(const uint256_t& tokenId) { + this->burn_(tokenId); + --totalSupply_; +} + diff --git a/src/contract/templates/erc721test.h b/src/contract/templates/erc721test.h new file mode 100644 index 00000000..248a43ee --- /dev/null +++ b/src/contract/templates/erc721test.h @@ -0,0 +1,106 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef ERC721_TEST +#define ERC721_TEST + +#include "standards/erc721.h" // ERC721Test derives from base ERC721 + +/** + * ERC721Test testing class. + * This is a class to test the capabilities of the ERC721 template contract. + * The ERC721 contract is based on the OpenZeppelin ERC721 implementation. + * As the ERC721 (OpenZeppelin) contract does not have a public function for minting and burning the tokens, + * this wrapper class is used to make that functions available. + * The mint function will use a internal counter to generate the token ID. + * Anyone can mint a token and there is a limit of X tokens defined in the constructor. + * The burn function will use the token id to burn the token, + * the sender of the burn transaction MUST be the owner of the token OR + * an approved operator for the token (All these cases are included in the tests) + * (the ERC721::_update function is used to check ownership and allowance). + */ +class ERC721Test : public ERC721 { + private: + SafeUint64_t tokenIdCounter_; ///< TokenId Counter for the public mint() functions. + SafeUint64_t maxTokens_; ///< How many tokens can be minted (used by mint()). + SafeUint64_t totalSupply_; ///< How many tokens exist. + void registerContractFunctions() override; ///< Register contract functions. + + public: + /** + * ConstructorArguments is a tuple of the contract constructor arguments in + * the order they appear in the constructor. + */ + using ConstructorArguments = std::tuple; + + /** + * Constructor for loading contract from DB. + * @param address The address where the contract will be deployed. + * @param db Reference to the database object. + */ + ERC721Test(const Address &address, const DB& db); + + /** + * Constructor to be used when creating a new contract. + * @param erc721name The name of the ERC721 token. + * @param erc721symbol The symbol of the ERC721 token. + * @param maxTokens The maximum amount of tokens that can be minted + * @param address The address where the contract will be deployed. + * @param creator The address of the creator of the contract. + * @param chainId The chain where the contract wil be deployed. + */ + ERC721Test( + const std::string &erc721name, const std::string &erc721symbol, const uint64_t& maxTokens, + const Address &address, const Address &creator, const uint64_t &chainId + ); + + /// Destructor. + ~ERC721Test() override = default; + + /** + * Mint a single token to the to address. + * @param to Address to send the token to. + * The mint function will use the internal tokenIdCounter to generate the token id + */ + void mint(const Address& to); + + /** + * Burn a single token given that token id + * @param tokenId The token id to burn + * The called of this function should be the owner of the token or an approved operator + */ + void burn(const uint256_t& tokenId); + + /// Getter for the tokenIdCounter_ + uint64_t tokenIdCounter() const { return tokenIdCounter_.get(); } + + /// Getter for the maxTokens_ + uint64_t maxTokens() const { return maxTokens_.get(); } + + /// Getter for the totalSupply_ + uint64_t totalSupply() const { return totalSupply_.get(); } + + /// Register contract class via ContractReflectionInterface. + static void registerContract() { + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{"erc721_name", "erc721_symbol", "maxTokens"}, + std::make_tuple("mint", &ERC721Test::mint, FunctionTypes::NonPayable, std::vector{"to"}), + std::make_tuple("burn", &ERC721Test::burn, FunctionTypes::NonPayable, std::vector{"tokenId"}), + std::make_tuple("tokenIdCounter", &ERC721Test::tokenIdCounter, FunctionTypes::View, std::vector{}), + std::make_tuple("maxTokens", &ERC721Test::maxTokens, FunctionTypes::View, std::vector{}), + std::make_tuple("totalSupply", &ERC721Test::totalSupply, FunctionTypes::View, std::vector{}) + ); + }); + } + + /// Dump method + DBBatch dump() const override; +}; + +#endif // ERC721_TEST diff --git a/src/contract/templates/mintableerc20.cpp b/src/contract/templates/mintableerc20.cpp new file mode 100644 index 00000000..e1f918dd --- /dev/null +++ b/src/contract/templates/mintableerc20.cpp @@ -0,0 +1,56 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "mintableerc20.h" + +ERC20Mintable::ERC20Mintable(const Address& address, const DB& db +) : DynamicContract(address, db), ERC20(address, db), Ownable(address, db) { + this->registerContractFunctions(); +} + +ERC20Mintable::ERC20Mintable( + const std::string &erc20_name, const std::string &erc20_symbol, + const uint8_t &erc20_decimals, + const Address &address, const Address &creator, + const uint64_t &chainId +) : DynamicContract("ERC20Mintable", address, creator, chainId), + ERC20("ERC20Mintable", erc20_name, erc20_symbol, erc20_decimals, + 0, address, creator, chainId), Ownable("ERC20Mintable", creator, address, creator, chainId) { + this->registerContractFunctions(); +} + +ERC20Mintable::~ERC20Mintable() = default; + +DBBatch ERC20Mintable::dump() const { + // We need to dump all the data from the parent class as well + DBBatch batch = ERC20::dump(); + auto ownableBatch = Ownable::dump(); + for (const auto& dbItem : ownableBatch.getPuts()) batch.push_back(dbItem); + for (const auto& dbItem : ownableBatch.getDels()) batch.delete_key(dbItem); + return batch; +} + +void ERC20Mintable::registerContractFunctions() { + registerContract(); + this->registerMemberFunctions( + std::make_tuple("mint", &ERC20Mintable::mint, FunctionTypes::NonPayable, this), + std::make_tuple("burn", &ERC20Mintable::burn, FunctionTypes::NonPayable, this) + ); +} + +void ERC20Mintable::mint(const Address &to, const uint256_t &amount) { + this->onlyOwner(); + ERC20::mint_(to, amount); +} + +void ERC20Mintable::burn(const uint256_t &amount) { + if (amount > this->balanceOf(this->getCaller())) { + throw DynamicException("Not enough balance to burn"); + } + ERC20::burnValue_(this->getCaller(), amount); +} + diff --git a/src/contract/templates/mintableerc20.h b/src/contract/templates/mintableerc20.h new file mode 100644 index 00000000..ea807de6 --- /dev/null +++ b/src/contract/templates/mintableerc20.h @@ -0,0 +1,79 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef MINTABLEERC20_H +#define MINTABLEERC20_H + +#include + +#include "../../utils/db.h" +#include "../../utils/utils.h" +#include "../../utils/contractreflectioninterface.h" +#include "../dynamiccontract.h" +#include "../variables/safestring.h" +#include "../variables/safeunorderedmap.h" +#include "ownable.h" +#include "standards/erc20.h" + +/// Template for a Mintable ERC20 contract. +class ERC20Mintable : virtual public ERC20, virtual public Ownable { + private: + /// Function for calling the register functions for contracts + void registerContractFunctions() override; + + public: + /// ConstructorArguments is a tuple of the contract constructor arguments in the order they appear in the constructor. + using ConstructorArguments = std::tuple< + const std::string &, const std::string &, const uint8_t & + >; + + /** + * Constructor for loading contract from DB. + * @param address The address where the contract will be deployed. + * @param db Reference to the database object. + */ + ERC20Mintable(const Address& address, const DB& db); + + /** + * Constructor to be used when creating a new contract. + * @param erc20_name The name of the token. + * @param erc20_symbol The symbol of the token. + * @param erc20_decimals The decimals of the token. + * @param address The address where the contract will be deployed. + * @param creator The address of the creator of the contract. + * @param chainId The chain id of the contract. + */ + ERC20Mintable( + const std::string &erc20_name, const std::string &erc20_symbol, + const uint8_t &erc20_decimals, + const Address &address, const Address &creator, + const uint64_t &chainId + ); + + /// Destructor. + ~ERC20Mintable() override; + + void mint(const Address &to, const uint256_t &amount); + void burn(const uint256_t& value); // Only the owner of the token can burn tokens + + /// Register contract using ContractReflectionInterface. + static void registerContract() { + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{"erc20_name", "erc20_symbol", "erc20_decimals"}, + std::make_tuple("mint", &ERC20Mintable::mint, FunctionTypes::Payable, std::vector{}), + std::make_tuple("burn", &ERC20Mintable::burn, FunctionTypes::Payable, std::vector{}) + ); + }); + } + + /// Dump method + DBBatch dump() const override; +}; + +#endif // MINTABLEERC20_H diff --git a/src/contract/templates/nativewrapper.cpp b/src/contract/templates/nativewrapper.cpp index 0134ab45..450d8b8f 100644 --- a/src/contract/templates/nativewrapper.cpp +++ b/src/contract/templates/nativewrapper.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -7,10 +7,8 @@ See the LICENSE.txt file in the project root for more information. #include "nativewrapper.h" -// Default Constructor when loading contract from DB. -NativeWrapper::NativeWrapper( - ContractManagerInterface &interface, const Address& address, const std::unique_ptr &db -) : ERC20(interface, address, db) +NativeWrapper::NativeWrapper(const Address& address, const DB& db +) : DynamicContract(address, db), ERC20(address, db) { this->registerContractFunctions(); } @@ -18,25 +16,36 @@ NativeWrapper::NativeWrapper( NativeWrapper::NativeWrapper( const std::string &erc20_name, const std::string &erc20_symbol, const uint8_t &erc20_decimals, - ContractManagerInterface &interface, const Address &address, const Address &creator, - const uint64_t &chainId,const std::unique_ptr &db -) : ERC20("NativeWrapper", erc20_name, erc20_symbol, erc20_decimals, - 0, interface, address, creator, chainId, db + const uint64_t &chainId +) : DynamicContract("NativeWrapper", address, creator, chainId), + ERC20("NativeWrapper", erc20_name, erc20_symbol, erc20_decimals, + 0, address, creator, chainId ) { this->registerContractFunctions(); } NativeWrapper::~NativeWrapper() = default; +DBBatch NativeWrapper::dump() const { + // We need to dump all the data from the parent class as well + DBBatch batch = ERC20::dump(); + DBBatch baseDump = BaseContract::dump(); + for (const auto& dbItem : baseDump.getPuts()) batch.push_back(dbItem); + for (const auto& dbItem : baseDump.getDels()) batch.delete_key(dbItem); + return batch; +} + void NativeWrapper::registerContractFunctions() { registerContract(); - this->registerMemberFunction("deposit", &NativeWrapper::deposit, FunctionTypes::Payable, this); - this->registerMemberFunction("withdraw", &NativeWrapper::withdraw, FunctionTypes::Payable, this); + this->registerMemberFunctions( + std::make_tuple("deposit", &NativeWrapper::deposit, FunctionTypes::Payable, this), + std::make_tuple("withdraw", &NativeWrapper::withdraw, FunctionTypes::Payable, this) + ); } void NativeWrapper::deposit() { - this->mintValue_(this->getCaller(), this->getValue()); + this->mint_(this->getCaller(), this->getValue()); } void NativeWrapper::withdraw(const uint256_t &value) { diff --git a/src/contract/templates/nativewrapper.h b/src/contract/templates/nativewrapper.h index d561e1b3..ceaaf6c4 100644 --- a/src/contract/templates/nativewrapper.h +++ b/src/contract/templates/nativewrapper.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -18,7 +18,7 @@ See the LICENSE.txt file in the project root for more information. #include "../variables/safestring.h" #include "../variables/safeuint.h" #include "../variables/safeunorderedmap.h" -#include "erc20.h" +#include "standards/erc20.h" /// Template for a NativeWrapper contract. class NativeWrapper : public ERC20 { @@ -34,32 +34,25 @@ class NativeWrapper : public ERC20 { /** * Constructor for loading contract from DB. - * @param interface Reference to the contract manager interface. * @param address The address where the contract will be deployed. * @param db Reference to the database object. */ - NativeWrapper( - ContractManagerInterface& interface, - const Address& address, const std::unique_ptr& db - ); + NativeWrapper(const Address& address, const DB& db); /** * Constructor to be used when creating a new contract. * @param erc20_name The name of the token. * @param erc20_symbol The symbol of the token. * @param erc20_decimals The decimals of the token. - * @param interface Reference to the contract manager interface. * @param address The address where the contract will be deployed. * @param creator The address of the creator of the contract. * @param chainId The chain id of the contract. - * @param db Reference to the database object. */ NativeWrapper( const std::string &erc20_name, const std::string &erc20_symbol, const uint8_t &erc20_decimals, - ContractManagerInterface &interface, const Address &address, const Address &creator, - const uint64_t &chainId, const std::unique_ptr &db + const uint64_t &chainId ); /// Destructor. @@ -80,16 +73,18 @@ class NativeWrapper : public ERC20 { /// Register contract using ContractReflectionInterface. static void registerContract() { - ContractReflectionInterface::registerContractMethods< - NativeWrapper, std::string &, std::string &, uint8_t &, - ContractManagerInterface &, const Address &, - const Address &, const uint64_t &, const std::unique_ptr & - >( - std::vector{"erc20_name", "erc20_symbol", "erc20_decimals"}, - std::make_tuple("deposit", &NativeWrapper::deposit, FunctionTypes::Payable, std::vector{}), - std::make_tuple("withdraw", &NativeWrapper::withdraw, FunctionTypes::Payable, std::vector{"value"}) - ); + static std::once_flag once; + std::call_once(once, [](){ + DynamicContract::registerContractMethods( + std::vector{"erc20_name", "erc20_symbol", "erc20_decimals"}, + std::make_tuple("deposit", &NativeWrapper::deposit, FunctionTypes::Payable, std::vector{}), + std::make_tuple("withdraw", &NativeWrapper::withdraw, FunctionTypes::Payable, std::vector{"value"}) + ); + }); } + + /// Dump method + DBBatch dump() const override; }; #endif // NATIVEWRAPPER_H diff --git a/src/contract/templates/orderbook/orderbook.cpp b/src/contract/templates/orderbook/orderbook.cpp new file mode 100644 index 00000000..c65d60c5 --- /dev/null +++ b/src/contract/templates/orderbook/orderbook.cpp @@ -0,0 +1,597 @@ +/* + Copyright (c) [2023] [Sparq Network] + + This software is distributed under the MIT License. + See the LICENSE.txt file in the project root for more information. +*/ + +#include "orderbook.h" + +OrderBook::OrderBook(const Address& addA, const std::string& tickerA, const uint8_t decA, + const Address& addB, const std::string& tickerB, const uint8_t decB, + const Address& address, const Address& creator, + const uint64_t& chainId) + : DynamicContract("OrderBook", address, creator, chainId), + nextOrderID_(this), + addressAssetA_(this), + tickerAssetA_(this), + addressAssetB_(this), + tickerAssetB_(this), + spread_(this), + tickSize_(this), + lotSize_(this), + lastPrice_(this), + bids_(this), + asks_(this), + stops_(this) +{ + // set + this->nextOrderID_ = 0; + this->addressAssetA_ = addA; + this->addressAssetB_ = addB; + this->tickerAssetA_ = tickerA; + this->tickerAssetB_ = tickerB; + this->spread_ = 0; + this->lastPrice_ = 0; + // constant + this->tickSize_ = Utils::exp10(decB - 4); + this->lotSize_ = Utils::exp10(decA - 4); + // commit + this->nextOrderID_.commit(); + this->addressAssetA_.commit(); + this->addressAssetB_.commit(); + this->tickerAssetA_.commit(); + this->tickerAssetB_.commit(); + this->spread_.commit(); + this->lotSize_.commit(); + this->tickSize_.commit(); + this->lastPrice_.commit(); + this->bids_.commit(); + this->asks_.commit(); + this->stops_.commit(); + // register functions + this->registerContractFunctions(); + // enable register + this->nextOrderID_.enableRegister(); + this->addressAssetA_.enableRegister(); + this->addressAssetB_.enableRegister(); + this->tickerAssetA_.enableRegister(); + this->tickerAssetB_.enableRegister(); + this->spread_.enableRegister(); + this->lotSize_.enableRegister(); + this->tickSize_.enableRegister(); + this->lastPrice_.enableRegister(); + this->bids_.enableRegister(); + this->asks_.enableRegister(); + this->stops_.enableRegister(); +} + +OrderBook::OrderBook(const Address& address, + const DB &db) + : DynamicContract(address, db), + nextOrderID_(this), + addressAssetA_(this), + tickerAssetA_(this), + addressAssetB_(this), + tickerAssetB_(this), + spread_(this) +{ + // set + this->nextOrderID_ = UintConv::bytesToUint256(db.get(std::string("nextOrderID_"), this->getDBPrefix())); + this->addressAssetA_ = Address(db.get(std::string("addressAssetA_"), this->getDBPrefix())); + this->addressAssetB_ = Address(db.get(std::string("addressAssetB_"), this->getDBPrefix())); + this->tickerAssetA_ = StrConv::bytesToString(db.get(std::string("tickerAssetA_"), this->getDBPrefix())); + this->tickerAssetB_ = StrConv::bytesToString(db.get(std::string("tickerAssetB_"), this->getDBPrefix())); + this->spread_ = UintConv::bytesToUint256(db.get(std::string("spread_"), this->getDBPrefix())); + this->tickSize_ = UintConv::bytesToUint256(db.get(std::string("tickSize_"), this->getDBPrefix())); + this->lotSize_ = UintConv::bytesToUint256(db.get(std::string("lotSize_"), this->getDBPrefix())); + this->lastPrice_ = UintConv::bytesToUint256(db.get(std::string("lastPrice_"), this->getDBPrefix())); + // commit + this->nextOrderID_.commit(); + this->addressAssetA_.commit(); + this->addressAssetB_.commit(); + this->tickerAssetA_.commit(); + this->tickerAssetB_.commit(); + this->spread_.commit(); + this->lotSize_.commit(); + this->tickSize_.commit(); + this->lastPrice_.commit(); + this->bids_.commit(); + this->asks_.commit(); + this->stops_.commit(); + // register functions + this->registerContractFunctions(); + // enable register + this->nextOrderID_.enableRegister(); + this->addressAssetA_.enableRegister(); + this->addressAssetB_.enableRegister(); + this->tickerAssetA_.enableRegister(); + this->tickerAssetB_.enableRegister(); + this->spread_.enableRegister(); + this->lotSize_.enableRegister(); + this->tickSize_.enableRegister(); + this->lastPrice_.enableRegister(); + this->bids_.enableRegister(); + this->asks_.enableRegister(); + this->stops_.enableRegister(); +} + +inline void OrderBook::insertAskOrder(Order&& askOrder) +{ + this->asks_.insert(askOrder); +} + +inline void OrderBook::insertBidOrder(Order&& bidOrder) +{ + this->bids_.insert(std::move(bidOrder)); +} + +inline void OrderBook::eraseAskOrder(const Order& askOrder) +{ + this->asks_.erase(askOrder); +} + +inline void OrderBook::eraseBidOrder(const Order& bidOrder) +{ + this->bids_.erase(bidOrder); +} + +void OrderBook::executeOrder(const Address& askOwner, + const Address& bidOwner, + const uint256_t& tokensToBePaid, + const uint256_t& tokenAmount) +{ + this->callContractFunction(this->addressAssetB_.get(), &ERC20::transfer, askOwner, tokensToBePaid); + this->callContractFunction(this->addressAssetA_.get(), &ERC20::transfer, bidOwner, this->tokensLot(tokenAmount)); +} + +Order* OrderBook::findMatchAskOrder(const Order& bidOrder) +{ + // auto& bidTokenAmount = std::get<3>(bidOrder); + // do we have any asks orders? + if (this->asks_.empty()) { + return nullptr; + } + // get the first ask order + const Order* askOrder = &(*(this->asks_.begin())); + auto& askTokenPrice = std::get<4>(*askOrder); + auto& bidTokenPrice = std::get<4>(bidOrder); + auto& bidOrderType = std::get<5>(bidOrder); + + switch (bidOrderType) { + // doesn't matter the price return the first ask order found + case OrderType::MARKET: { + return const_cast(askOrder); + } + // we want to buy for the lowest price + case OrderType::LIMIT: { + return ((bidTokenPrice <= askTokenPrice) ? const_cast(askOrder) : nullptr); + } + // default do nothing + default: + break; + } + // not found + return nullptr; +} + +Order* OrderBook::findMatchBidOrder(const Order& askOrder) +{ + //auto& askTokenAmount = std::get<3>(askOrder); + // do we have bid orders? + if (this->bids_.empty()) { + return nullptr; + } + // get the first bid order (the higher value) + const Order* bidOrder = &(*(this->bids_.begin())); + // we want to sell for the higher bid price + // but never not lower to the ask price limit + auto& bidTokenPrice = std::get<4>(*bidOrder); + auto& askTokenPrice = std::get<4>(askOrder); + auto& askOrderType = std::get<5>(askOrder); + switch (askOrderType) { + // doesn't matter the price return the first bid order found + case OrderType::MARKET: { + return const_cast(bidOrder); + } + // we want to sell at the highest price + case OrderType::LIMIT: { + return ((bidTokenPrice >= askTokenPrice) ? const_cast(bidOrder) : nullptr); + } + default: + break; + } + // not found + return nullptr; +} + +void OrderBook::evaluateMarketBidOrder(Order&& bidOrder) +{ + Order *matchAskOrder; + // get bid order attributes values + const auto& bidOwner = std::get<2>(bidOrder); + auto& bidTokenAmount = std::get<3>(bidOrder); + uint256_t bidTokensToBePaid = this->tokensTick(bidTokenAmount); + // find the ask order + while(((matchAskOrder = findMatchAskOrder(bidOrder)) != nullptr) and \ + (bidTokensToBePaid > 0) and \ + (bidTokenAmount > 0)) { + const auto& askOwner = std::get<2>(*matchAskOrder); + auto& askTokenAmount = std::get<3>(*matchAskOrder); + auto& askTokenPrice = std::get<4>(*matchAskOrder); + // compute the tokens amount and how much must be transfer (temporary variables) + uint256_t tokenAmount = std::min(askTokenAmount, ((bidTokensToBePaid * 10000) / askTokenPrice)); + uint256_t tokensToBePaid = this->tokensToBePaid(tokenAmount, askTokenPrice); + // transfer the tokens to the contract + this->transferToContract(this->addressAssetB_.get(), tokensToBePaid); + // executes the order, transfer the tokens from ask owner to bid owner + this->executeOrder(askOwner, bidOwner, tokensToBePaid, tokenAmount); + // update bid tokens to be paid information + bidTokensToBePaid -= tokensToBePaid; + // update amount information + bidTokenAmount -= tokenAmount; + askTokenAmount -= tokenAmount; + // update the current price + this->updateLastPrice(askTokenPrice); + // remove order if it was filled + if (askTokenAmount == 0) { + this->eraseAskOrder(*matchAskOrder); + } + } + // update spread and mid price + this->updateSpreadAndMidPrice(); +} + +void OrderBook::evaluateBidOrder(Order&& bidOrder) +{ + Order *matchAskOrder; + // get bid order attributes values + const auto& bidOwner = std::get<2>(bidOrder); + auto& bidTokenAmount = std::get<3>(bidOrder); + // auto& bidTokenPrice = std::get<4>(bidOrder); + while(((matchAskOrder = findMatchAskOrder(bidOrder)) != nullptr) and + (bidTokenAmount > 0)) { + // get ask order attributes values + const auto& askOwner = std::get<2>(*matchAskOrder); + auto& askTokenAmount = std::get<3>(*matchAskOrder); + auto& askTokenPrice = std::get<4>(*matchAskOrder); + // compute the aTokens and bTokens to be transfered + uint256_t tokenAmount = std::min(askTokenAmount, bidTokenAmount); + uint256_t tokensToBePaid = this->tokensToBePaid(tokenAmount, askTokenPrice); + // executes the order, transfer the tokens from ask owner to bid owner + this->executeOrder(askOwner, bidOwner, tokensToBePaid, tokenAmount); + // update amount information + bidTokenAmount -= tokenAmount; + askTokenAmount -= tokenAmount; + // update the current price + this->updateLastPrice(askTokenPrice); + // erase the ask asset if filled + if (askTokenAmount == 0) { + this->eraseAskOrder(*matchAskOrder); + } + } + // handle the bid order that was not filled (remainder) + if (bidTokenAmount > 0) { + this->insertBidOrder(std::move(bidOrder)); + } + // update spread and mid price + this->updateSpreadAndMidPrice(); +} + +void OrderBook::evaluateAskOrder(Order&& askOrder) +{ + Order *matchBidOrder; + const auto& askOwner = std::get<2>(askOrder); + auto& askTokenAmount = std::get<3>(askOrder); + // auto& askTokenPrice = std::get<4>(askOrder); + auto& askOrderType = std::get<5>(askOrder); + while (((matchBidOrder = findMatchBidOrder(askOrder)) != nullptr) and \ + (askTokenAmount > 0)) { + // get bid order attributes values + const auto& bidOwner = std::get<2>(*matchBidOrder); + auto& bidTokenAmount = std::get<3>(*matchBidOrder); + auto& bidTokenPrice = std::get<4>(*matchBidOrder); + // compute the asset and token amount + uint256_t tokenAmount = std::min(askTokenAmount, bidTokenAmount); + uint256_t tokensToBePaid = this->tokensToBePaid(tokenAmount, bidTokenPrice); + // executes the order, transfer the tokens from ask owner to bid owner + this->executeOrder(askOwner, bidOwner, tokensToBePaid, tokenAmount); + // update order asset amounts + askTokenAmount -= tokenAmount; + bidTokenAmount -= tokenAmount; + // update the current price + this->updateLastPrice(bidTokenPrice); + // erase the bid order if was filled + if (bidTokenAmount == 0) { + this->eraseBidOrder(*matchBidOrder); + } + } + // handle the ask order that was not filled (remainder) + if (askTokenAmount > 0 and askOrderType != OrderType::MARKET) { + this->insertAskOrder(std::move(askOrder)); + } + // update spread and mid price + this->updateSpreadAndMidPrice(); +} + +void OrderBook::transferToContract(const Address& assetAddress, + const uint256_t& tokenAmount) +{ + this->callContractFunction(assetAddress, + &ERC20::transferFrom, + this->getCaller(), + this->getContractAddress(), + tokenAmount); +} + +Order OrderBook::makeOrder(const uint256_t& tokenAmount, + const uint256_t& tokenPrice, + const OrderType orderType) const +{ + return Order(this->nextOrderID_.get(), + this->getCurrentTimestamp(), + this->getCaller(), + tokenAmount, + tokenPrice, + orderType); +} + +void OrderBook::addBidLimitOrder(const uint256_t& tokenAmount, + // we want to buy for the lowest price + const uint256_t& tokenPrice) +{ + // set the amount of B tokens available + uint256_t tokensBTotalBalance = \ + this->callContractViewFunction(this->addressAssetB_.get(), + &ERC20::balanceOf, + this->getCaller()); + + // convert to the number of tokens + uint256_t tokensBToBePaid = this->tokensToBePaid(tokenAmount, tokenPrice); + + // verify the tokens balance + if (tokensBToBePaid > tokensBTotalBalance) { + throw std::runtime_error("OrderBook::addBidLimitOrder: INSUFFICIENT_BALANCE"); + } + // transfer token to be paid to order book contract + // evaluate the bid limit order and increment the next order id + this->transferToContract(this->addressAssetB_.get(), tokensBToBePaid); + this->evaluateBidOrder(std::move(this->makeOrder(tokenAmount, + tokenPrice, + OrderType::LIMIT))); + ++this->nextOrderID_; +} + +void OrderBook::delBidLimitOrder(const uint256_t& id) +{ + this->bids_.erase_if([&id, this](const Order& bidOrder) { + auto const& bidId = std::get<0>(bidOrder); + if (bidId != id) { + return false; + } + auto const& bidOwner = std::get<2>(bidOrder); + auto const& bidTokenAmount = std::get<3>(bidOrder); + auto const& bidTokenPrice = std::get<4>(bidOrder); + if (bidOwner != this->getCaller()) { + throw std::runtime_error("OrderBook::delBidLimitOrder: INVALID_OWNER"); + } + uint256_t tokenAmount = this->tokensToBePaid(bidTokenAmount, bidTokenPrice); + this->callContractFunction(this->addressAssetB_.get(), + &ERC20::transfer, + bidOwner, + tokenAmount); + return true; + }); +} + +// you can sell for the limit value that you want, but +// the value must be a multiplier of the lot size and +// and the tokens amount needs to be less then the total of A tokens +// available +void OrderBook::addAskLimitOrder(const uint256_t& tokenAmount, + // remember this is the low limit, we + // want to sell for the biggest available in + // the order book + const uint256_t& tokenPrice) +{ + uint256_t tokensTotalBalance = \ + this->callContractViewFunction(this->addressAssetA_.get(), + &ERC20::balanceOf, + this->getCaller()); + + // convert tokens amount to tokens lot + uint256_t tokensLot = this->tokensLot(tokenAmount); + // verify tokens available + if (tokensLot > tokensTotalBalance) { + throw std::runtime_error("OrderBook::addAskLimitOrder:" \ + "Insufficient number of tokens"); + } + // verify if asset price is of lot sizable + if (not(isLotSizable(tokensLot))) { + throw std::runtime_error("OrderBook::addAskLimitOrder:" \ + "The asset amount must be a multiple of the lot size"); + } + // transfer lot amount to order book contract + // evaluate the the nearly created ask limit order and increment next order id + this->transferToContract(this->addressAssetA_.get(), this->tokensLot(tokenAmount)); + // this should be transfer to another thread of execution? + this->evaluateAskOrder(std::move(this->makeOrder(tokenAmount, + tokenPrice, + OrderType::LIMIT))); + ++this->nextOrderID_; +} + +void OrderBook::delAskLimitOrder(const uint256_t& id) +{ + this->asks_.erase_if([&id, this](const Order& askOrder) { + auto const& askId = std::get<0>(askOrder); + if (askId != id) { + return false; + } + auto const& askOwner = std::get<2>(askOrder); + auto const& askTokenAmount = std::get<3>(askOrder); + if (askOwner != this->getCaller()) { + throw std::runtime_error("OrderBook::delAskLimitOrder: INVALID_OWNER"); + } + this->callContractFunction(this->addressAssetA_.get(), + &ERC20::transfer, + askOwner, + this->tokensLot(askTokenAmount)); + return true; + }); +} + +void OrderBook::addAskMarketOrder(const uint256_t& tokenAmount, + const uint256_t& tokenPrice) +{ + // set tokens balance + uint256_t tokenBalance = \ + this->callContractViewFunction(this->addressAssetA_.get(), + &ERC20::balanceOf, + this->getCaller()); + // convert lot amount + uint256_t tokenLotAmount = this->tokensLot(tokenAmount); + // verify if token lot amount is bigger than user token balance + if (tokenLotAmount > tokenBalance) { + throw std::runtime_error("OrderBook::addAskMarketOrder: INSUFFICIENT_BALANCE"); + } + this->transferToContract(this->addressAssetA_.get(), tokenLotAmount); + this->evaluateAskOrder(std::move(this->makeOrder(tokenAmount, 0, OrderType::MARKET))); + ++this->nextOrderID_; +} + +void OrderBook::addBidMarketOrder(const uint256_t& tokenAmount, + const uint256_t& tokenPrice) +{ + // set asset balance + uint256_t tokenBalance = \ + this->callContractViewFunction(this->addressAssetB_.get(), + &ERC20::balanceOf, + this->getCaller()); + // convert tick amount + uint256_t tokensTick = this->tokensTick(tokenAmount); + // verify if tick amount is bigger than user balance + if (tokensTick > tokenBalance) { + throw std::runtime_error("OrderBook::addBidMarketOrder: INSUFFICIENT_BALANCE"); + } + this->evaluateMarketBidOrder(std::move(this->makeOrder(tokenAmount, 0, OrderType::MARKET))); + ++this->nextOrderID_; +} + +inline void OrderBook::updateLastPrice(const uint256_t &price) +{ + this->lastPrice_ = price; +} + +void OrderBook::updateSpreadAndMidPrice() +{ + if (this->bids_.empty() or + this->asks_.empty()) + return; + uint256_t bidPrice = std::get<4>(*this->bids_.cbegin()); + uint256_t askPrice = std::get<4>(*this->asks_.cbegin()); + this->spread_ = (std::max(bidPrice, askPrice) -\ + std::min(bidPrice, askPrice)); +} + +uint64_t OrderBook::getCurrentTimestamp() const +{ + return this->getBlockTimestamp(); +} + +Order OrderBook::getFirstBid() const +{ + return *(this->bids_.cbegin()); +} + +Order OrderBook::getFirstAsk() const +{ + return *(this->asks_.cbegin()); +} + +std::vector OrderBook::getBids() const +{ + std::vector ret; + for (auto it = this->bids_.cbegin(); it != this->bids_.cend(); ++it) { + ret.push_back(*it); + } + return ret; +} + +std::vector OrderBook::getAsks() const +{ + std::vector ret; + for (auto it = this->asks_.cbegin(); it != this->asks_.cend(); ++it) { + ret.push_back(*it); + } + return ret; +} + +std::tuple, + std::vector, + std::vector> +OrderBook::getUserOrders(const Address& user) const +{ + std::tuple, std::vector, std::vector> ret; + auto& bids = std::get<0>(ret); + auto& asks = std::get<1>(ret); + auto& stops = std::get<2>(ret); + for (auto it = this->asks_.cbegin(); it != this->asks_.cend(); ++it) { + const auto& askAddress = std::get<2>(*it); + if (askAddress == user) asks.push_back(*it); + } + for (auto it = this->bids_.cbegin(); it != this->bids_.cend(); ++it) { + const auto& bidAddress = std::get<2>(*it); + if (bidAddress == user) bids.push_back(*it); + } + for (auto it = this->stops_.cbegin(); it != this->stops_.cend(); ++it) { + const auto& stopAddress = std::get<2>(*it); + if (stopAddress == user) stops.push_back(*it); + } + return ret; +} + +void OrderBook::registerContractFunctions() +{ + registerContract(); + this->registerMemberFunctions( + std::make_tuple("getNextOrderID", &OrderBook::getNextOrderID, FunctionTypes::View, this), + std::make_tuple("getAddressAssetA", &OrderBook::getAddressAssetA, FunctionTypes::View, this), + std::make_tuple("getAddressAssetB", &OrderBook::getAddressAssetB, FunctionTypes::View, this), + std::make_tuple("getTickerAssetA", &OrderBook::getTickerAssetA, FunctionTypes::View, this), + std::make_tuple("getTickerAssetB", &OrderBook::getTickerAssetB, FunctionTypes::View, this), + std::make_tuple("getSpread", &OrderBook::getSpread, FunctionTypes::View, this), + std::make_tuple("getTickSize", &OrderBook::getTickSize, FunctionTypes::View, this), + std::make_tuple("getLotSize", &OrderBook::getLotSize, FunctionTypes::View, this), + std::make_tuple("getLastPrice", &OrderBook::getLastPrice, FunctionTypes::View, this), + std::make_tuple("getPrecision", &OrderBook::getPrecision, FunctionTypes::View, this), + std::make_tuple("getAsks", &OrderBook::getAsks, FunctionTypes::View, this), + std::make_tuple("getBids", &OrderBook::getBids, FunctionTypes::View, this), + std::make_tuple("getFirstAsk", &OrderBook::getFirstAsk, FunctionTypes::View, this), + std::make_tuple("getFirstBid", &OrderBook::getFirstBid, FunctionTypes::View, this), + std::make_tuple("getUserOrders", &OrderBook::getUserOrders, FunctionTypes::View, this), + std::make_tuple("addAskLimitOrder", &OrderBook::addAskLimitOrder, FunctionTypes::NonPayable, this), + std::make_tuple("addBidLimitOrder", &OrderBook::addBidLimitOrder, FunctionTypes::NonPayable, this), + std::make_tuple("delAskLimitOrder", &OrderBook::delAskLimitOrder, FunctionTypes::NonPayable, this), + std::make_tuple("delBidLimitOrder", &OrderBook::delBidLimitOrder, FunctionTypes::NonPayable, this), + std::make_tuple("addAskMarketOrder", &OrderBook::addAskMarketOrder, FunctionTypes::NonPayable, this), + std::make_tuple("addBidMarketOrder", &OrderBook::addBidMarketOrder, FunctionTypes::NonPayable, this) + ); +} + +DBBatch OrderBook::dump() const +{ + DBBatch b = BaseContract::dump(); + + b.push_back(StrConv::stringToBytes("nextOrderID_"), UintConv::uint256ToBytes(this->nextOrderID_.get()), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("addressAssetA_"), this->addressAssetA_.get().view(), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("addressAssetB_"), this->addressAssetB_.get().view(), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("tickerAssetA_"), StrConv::stringToBytes(this->tickerAssetA_.get()), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("tickerAssetB_"), StrConv::stringToBytes(this->tickerAssetB_.get()), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("spread_"), UintConv::uint256ToBytes(this->spread_.get()), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("tickSize_"), UintConv::uint256ToBytes(this->tickSize_.get()), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("lotSize_"), UintConv::uint256ToBytes(this->lotSize_.get()), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("lastPrice_"), UintConv::uint256ToBytes(this->lastPrice_.get()), this->getDBPrefix()); + + return b; +} diff --git a/src/contract/templates/orderbook/orderbook.h b/src/contract/templates/orderbook/orderbook.h new file mode 100644 index 00000000..772d6ff5 --- /dev/null +++ b/src/contract/templates/orderbook/orderbook.h @@ -0,0 +1,505 @@ +/* + Copyright (c) [2023] [Sparq Network] + + This software is distributed under the MIT License. + See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef ORDERBOOK_H +#define ORDERBOOK_H + +#include +#include +#include +#include + +#include "../standards/erc20.h" + +#include "../../dynamiccontract.h" +#include "../../variables/safeuint.h" +#include "../../variables/safestring.h" +#include "../../variables/safeaddress.h" +#include "../../variables/safemultiset.h" + +#include "../../../utils/db.h" +#include "../../../utils/utils.h" +#include "../../../utils/strings.h" + +/// Enum for identifying order types (market or limit, and the respective stops). +enum OrderType { MARKET, LIMIT, STOPMARKET, STOPLIMIT }; + +/// Enum for identifying order side (bid or ask). +enum OrderSide { BID, ASK }; + +/// Order Fields +enum OrderField { + ID = 0, + TIMESTAMP, + OWNER, + AMOUNT, + PRICE, + TYPE +}; + +/** + * Tuple for a given stop order in the book. + * 0 - const uint256_t - id - Sequential unique ID of the order. + * 1 - const uint - timestamp - The epoch timestamp of the order's creation. + * 2 - const Address - owner - The address that made the order. + * 3 - uint256_t - tokenAmount - The amount of the asset the order has to offer (tokenA for bids, tokenB for asks). + * 4 - const uint256_t - tokenPrice - The unit price of the asset the order has to offer in WEI of tokenB. + * 5 - const uint256_t - stopLimit - The stop limit price of the order (only for stop limit orders), in WEI. + * 6 - const OrderSide - side - Whether the order originally is a bid or ask. + * 7 - const OrderType - type - Whether the order originally is a market or limit. + */ +using StopOrder = std::tuple; + +/** + * Lesser comparison operator. + * @param lhs The left hand side of the comparison. + * @param rhs The right hand side of the comparison. + * @return True if lhs < rhs, false otherwise. + */ +inline bool operator<(const StopOrder& lhs, const StopOrder& rhs) { + const auto& lhs_stopLimit = std::get<5>(lhs); + const auto& rhs_stopLimit = std::get<5>(rhs); + const auto& lhs_timestamp = std::get<1>(lhs); + const auto& rhs_timestamp = std::get<1>(rhs); + return (lhs_stopLimit < rhs_stopLimit) || + (lhs_stopLimit == rhs_stopLimit && lhs_timestamp < rhs_timestamp); +} + +/** + * Higher comparison operator. + * @param lhs The left hand side of the comparison. + * @param rhs The right hand side of the comparison. + * @return True if lhs > rhs, false otherwise. + */ +inline bool operator>(const StopOrder& lhs, const StopOrder& rhs) { + const auto& lhs_stopLimit = std::get<5>(lhs); + const auto& rhs_stopLimit = std::get<5>(rhs); + const auto& lhs_timestamp = std::get<1>(lhs); + const auto& rhs_timestamp = std::get<1>(rhs); + return (lhs_stopLimit > rhs_stopLimit) || + (lhs_stopLimit == rhs_stopLimit && lhs_timestamp < rhs_timestamp); +} + +/** + * Tuple for a given order in the book. + * 0 - const uint256_t - id - Sequential unique ID of the order. + * 1 - const uint - timestamp - The epoch timestamp of the order's creation. + * 2 - const Address - owner - The address that made the order. + * 3 - uint256_t - tokenAmount - The amount of the asset the order has to offer (tokenA for bids, tokenB for asks). + * 4 - const uint256_t - tokenPrice - The unit price of the asset the order has to offer in WEI of tokenB. + * 5 - const OrderType - type - Whether the order originally is a market or limit. + */ +using Order = std::tuple; + +// using Order = std::tuple; + +/** + * Lesser comparison operator. + * @param lhs The left hand side of the comparison. + * @param rhs The right hand side of the comparison. + * @return True if lhs < rhs, false otherwise. + */ +inline bool operator<(const Order& lhs, const Order& rhs) { + const auto& lhs_assetPrice = std::get<4>(lhs); + const auto& rhs_assetPrice = std::get<4>(rhs); + const auto& lhs_timestamp = std::get<1>(lhs); + const auto& rhs_timestamp = std::get<1>(rhs); + return (lhs_assetPrice < rhs_assetPrice) || + (lhs_assetPrice == rhs_assetPrice && lhs_timestamp < rhs_timestamp); +} + +/** + * Higher comparison operator. + * @param lhs The left hand side of the comparison. + * @param rhs The right hand side of the comparison. + * @return True if lhs > rhs, false otherwise. + */ +inline bool operator>(const Order& lhs, const Order& rhs) { + const auto& lhs_assetPrice = std::get<4>(lhs); + const auto& rhs_assetPrice = std::get<4>(rhs); + const auto& lhs_timestamp = std::get<1>(lhs); + const auto& rhs_timestamp = std::get<1>(rhs); + return (lhs_assetPrice > rhs_assetPrice) || + (lhs_assetPrice == rhs_assetPrice && lhs_timestamp < rhs_timestamp); +} + +inline Order orderFromStopOrder(const StopOrder& stopOrder, const uint64_t& timestamp) +{ + return std::make_tuple(std::get<0>(stopOrder), + timestamp, + std::get<2>(stopOrder), + std::get<3>(stopOrder), + std::get<4>(stopOrder), + std::get<7>(stopOrder)); +} + +/// Contract template for a given exchange pair order book. +class OrderBook : public DynamicContract { +private: + SafeUint256_t nextOrderID_; ///< Counter for the next order ID. + SafeAddress addressAssetA_; ///< Address of the first asset of the pair. HAS TO BE AN ERC20 TOKEN. + SafeAddress addressAssetB_; ///< Address of the second asset of the pair. HAS TO BE AN ERC20 TOKEN. + SafeString tickerAssetA_; ///< Ticker of the first asset of the pair. + SafeString tickerAssetB_; ///< Ticker of the second asset of the pair. + SafeUint256_t spread_; ///< Current market spread. + + SafeUint256_t tickSize_; ///< The tick size of the order book (minimum difference between price levels). \ + Should be pow(10, AssetB_.decimals() - 4), tokens MUST have at least 8 decimals. + + SafeUint256_t lotSize_; ///< The lot size of the order book (minimum difference between order amounts). \ + Should be pow(10, AssetA_.decimals() - 4), tokens MUST have at least 8 decimals. + + SafeUint256_t lastPrice_; ///< The last price of the pair. + const uint256_t precision_ = 10000; ///< Equivalent to 10^4, difference between tick/lot size to the actual token value + + SafeMultiSet> bids_; ///< List of currently active bids, from highest to lowest price. + SafeMultiSet> asks_; ///< List of currently active asks, from lowest to highest price. + SafeMultiSet> stops_; ///< List of stop orders, from lower to highest stop price. + + /** + * Transfer tokens from an address to the order book contract. + * @param address the address where to get the tokens from. + * @param amount the amount to be transferred. + */ + inline void transferToContract(const Address& address, + const uint256_t& amount); + + /** + * Create a order. + * @param tokenAmount the order asset amount. + * @param tokenPrice the order asset prince. + * @return Order the nearly created order. + */ + Order makeOrder(const uint256_t& tokenAmount, + const uint256_t& tokenPrice, + const OrderType orderType) const; + + /** + * Execute the order, i.e, transfer the token amount to the ask owner and + * transfer the asset amount to the bid owner. + * + * @param askOwner the address of the ask owner. + * @param bidOwner the address of the bid owner. + * @param bidOwner the address of the bid owner. + * @param tokenAmount the token amount to be transferred to the ask owner. + * @param assetAmount the asset amount to be transferred to the bid owner. + */ + void executeOrder(const Address& askOwner, + const Address& bidOwner, + const uint256_t& tokenAmount, + const uint256_t& assetAmount); + + /** + * Insert an ask order to the ask order list. + * @param askOrder the ask order to be inserted. + */ + inline void insertAskOrder(Order&& askOrder); + + /** + * Insert an bid order to the ask order list. + * @param bidOrder the bid order to be inserted. + */ + inline void insertBidOrder(Order&& bidOrder); + + /** + * Erase (remove) an ask order from the ask order list. + * @param askOrder the ask order to be removed. + */ + inline void eraseAskOrder(const Order& askOrder); + + /** + * Erase (remove) a bid order from the bid order list. + * @param bidOrder the bid order to be removed. + */ + inline void eraseBidOrder(const Order& bidOrder); + + /** + * Evaluate the bid order, i.e, try to find the a matching ask order and + * execute the order pair, if the order isn't filled add the bid order to + * the bid order list (passive order). + * @param bidOrder the bid order. + */ + void evaluateBidOrder(Order&& bidOrder); + + /** + * Evaluate the bid order, i.e, try to find the a matching ask order and + * execute the order pair, if the order isn't filled the bid order is not + * added to bid order list. + * @param bidOrder the bid order. + */ + void evaluateMarketBidOrder(Order&& bidOrder); + + /** + * Evaluate the ask order, i.e, try to find the a matching bid order and + * execute the order pair, if the order isn't filled add the ask order to + * the ask order list (passive order). + * @param askOrder the ask order. + */ + void evaluateAskOrder(Order&& askOrder); + + /** + * Find a matching ask order for an arbitrary bid order. + * @param bidOrder the bid order. + * @return A order pointer if an ask order was found, nullptr otherwise. + */ + Order* findMatchAskOrder(const Order& bidOrder); + + /** + * Find a matching bid order for an arbitrary ask order. + * @param askOrder the ask order. + * @return A order pointer if a bid order was found, nullptr otherwise. + */ + Order* findMatchBidOrder(const Order& askOrder); + + /** + * Update the last price of the pair. + * @param price The new last price. + */ + inline void updateLastPrice(const uint256_t& price); + + /** + * Update the current spread and mid price based on top bid and top ask prices. + */ + void updateSpreadAndMidPrice(); + + /** + * Get the current epoch timestamp, in milliseconds. + */ + uint64_t getCurrentTimestamp() const; + + /** + * Convert token amount to token lot. + * @param tokenAmount the token amount to convert. + * @return the computed token lot + */ + inline uint256_t tokensLot(const uint256_t& tokenAmount) const + { + return tokenAmount * lotSize_.get(); + } + + /** + * Convert from tick size to token amount. + * @param value The value to convert. + * @return The computed token amount + */ + inline uint256_t tokensTick(const uint256_t& value) const + { + return value * tickSize_.get(); + } + + /** + * Compute the amount of token at the asset's price. + * @param assetAmount the token asset amount. + * @param assetPrice the token asset price. + * @return tokens to be paid regarding the asset's amount and price. + */ + inline uint256_t tokensToBePaid(const uint256_t& assetAmount, + const uint256_t& assetPrice) const + { + return ((this->tokensTick(assetAmount * assetPrice)) / this->precision_); + } + + static inline bool isTickSizable(const uint256_t& tokenPrice) { return true; } + + inline bool isLotSizable(const uint256_t& tokenPrice) const + { + return ((tokenPrice % this->lotSize_.get()) == 0); + } + + /// Call the register functions for the contract. + void registerContractFunctions() override; + + /// Dump method + DBBatch dump() const override; + +public: + using ConstructorArguments = std::tuple< + const Address&, const std::string&, const uint8_t, + const Address&, const std::string&, const uint8_t + >; + /** + * Constructor from scratch. + * @param addA The address of the pair's first asset. + * @param tickerA The ticker of the pair's first asset. + * @param decA The decimal number from the first asset. + * @param addB The address of the pair's second asset. + * @param tickerB The ticker of the pair's second asset. + * @param decB The decimals of the second asset. + * @param address The address of the contract. + * @param creator The address of the creator of the contract. + * @param chainId The chain ID. + */ + OrderBook(const Address& addA, const std::string& tickerA, const uint8_t decA, + const Address& addB, const std::string& tickerB, const uint8_t decB, + const Address& address, const Address& creator, const uint64_t& chainId); + + /** + * Constructor from load. Load contract from database. + * @param address The address of the contract. + * @param db The database to use. + */ + OrderBook(const Address& address, const DB &db); + + /// Getter for `nextOrderID_`. + uint256_t getNextOrderID() const { return this->nextOrderID_.get(); } + + /// Getter for `addressAssetA_`. + Address getAddressAssetA() const { return this->addressAssetA_.get(); } + + /// Getter for `addressAssetB_`. + Address getAddressAssetB() const { return this->addressAssetB_.get(); } + + /// Getter for `tickerAssetA_`. + std::string getTickerAssetA() const { return this->tickerAssetA_.get(); } + + /// Getter for `tickerAssetB_`. + std::string getTickerAssetB() const { return this->tickerAssetB_.get(); } + + /// Getter for `spread_`. + uint256_t getSpread() const { return this->spread_.get(); } + + /// Getter for `tickSize_`. + uint256_t getTickSize() const { return this->tickSize_.get(); } + + /// Getter for `lotSize_`. + uint256_t getLotSize() const { return this->lotSize_.get(); } + + /// Getter for `lastPrice_`. + uint256_t getLastPrice() const { return this->lastPrice_.get(); } + + /// Getter for `precision_`. + uint256_t getPrecision() const { return this->precision_; } + + /** + * Getter for all bids. + * @return A vector with all bids. + */ + std::vector getBids() const; + + /** + * Get the first bid order. + * @return The bid order. + */ + Order getFirstBid() const; + + /** + * Get the first ask order. + * @return The ask order + */ + Order getFirstAsk() const; + + /** + * Getter for all asks. + * @return A vector with all asks. + */ + std::vector getAsks() const; + + /** + * Getter for all users orders + * @param user The user to get orders from. + * @return A tuple with a vector of bids, a vector of asks and a vector of stops. + */ + std::tuple, + std::vector, + std::vector> getUserOrders(const Address& user) const; + + /** + * Add bid limit order to be evaluated, i.e, to be executed or be put in the + * bid order list. + * @param assetAmount the bid order asset amount. + * @param assetPrice the bid order asset price. + */ + void addBidLimitOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice); + + /** + * Remove the bid order from the bid order list. + * @param id the bid order identifier. + */ + void delBidLimitOrder(const uint256_t& id); + + /** + * Add ask limit order to be evaluated, i.e, to be executed or be put in the + * ask order list. + * @param assetAmount the ask order asset amount. + * @param assetPrice the ask order asset price. + */ + void addAskLimitOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice); + + /** + * Remove the ask order from the ask order list. + * @param id the ask order identifier. + */ + void delAskLimitOrder(const uint256_t& id); + + /** + * Add a market ask order to be evaluated. + * @param assetAmount the market ask order asset amount. + * @param assetPrice the market ask order asset price. + */ + void addAskMarketOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice); + + /** + * Add a market bid order to be evaluated. + * @param assetAmount the market bid order asset amount. + * @param assetPrice the market bid order asset price. + */ + void addBidMarketOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice); + + /// Register the contract structure. + static void registerContract() { + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{ "addA", "tickerA", "decA", "addB", "tickerB", "decB"}, + std::make_tuple("getNextOrderID", &OrderBook::getNextOrderID, FunctionTypes::View, std::vector{}), + std::make_tuple("getAddressAssetA", &OrderBook::getAddressAssetA, FunctionTypes::View, std::vector{}), + std::make_tuple("getAddressAssetB", &OrderBook::getAddressAssetB, FunctionTypes::View, std::vector{}), + std::make_tuple("getTickerAssetA", &OrderBook::getTickerAssetA, FunctionTypes::View, std::vector{}), + std::make_tuple("getTickerAssetB", &OrderBook::getTickerAssetB, FunctionTypes::View, std::vector{}), + std::make_tuple("getSpread", &OrderBook::getSpread, FunctionTypes::View, std::vector{}), + std::make_tuple("getTickSize", &OrderBook::getTickSize, FunctionTypes::View, std::vector{}), + std::make_tuple("getLotSize", &OrderBook::getLotSize, FunctionTypes::View, std::vector{}), + std::make_tuple("getLastPrice", &OrderBook::getLastPrice, FunctionTypes::View, std::vector{}), + std::make_tuple("getPrecision", &OrderBook::getPrecision, FunctionTypes::View, std::vector{}), + std::make_tuple("getAsks", &OrderBook::getAsks, FunctionTypes::View, std::vector{}), + std::make_tuple("getBids", &OrderBook::getBids, FunctionTypes::View, std::vector{}), + std::make_tuple("getFirstAsk", &OrderBook::getFirstAsk, FunctionTypes::View, std::vector{}), + std::make_tuple("getFirstBid", &OrderBook::getFirstBid, FunctionTypes::View, std::vector{}), + std::make_tuple("getUserOrders", &OrderBook::getUserOrders, FunctionTypes::View, std::vector{"user"}), + std::make_tuple("addBidLimitOrder", &OrderBook::addBidLimitOrder, FunctionTypes::NonPayable, std::vector{"assetAmount", "assetPrice"}), + std::make_tuple("addAskLimitOrder", &OrderBook::addAskLimitOrder, FunctionTypes::NonPayable, std::vector{"assetAmount", "assetPrice"}), + std::make_tuple("delAskLimitOrder", &OrderBook::delAskLimitOrder, FunctionTypes::NonPayable, std::vector{"id"}), + std::make_tuple("delBidLimitOrder", &OrderBook::delBidLimitOrder, FunctionTypes::NonPayable, std::vector{"id"}), + std::make_tuple("addAskMarketOrder", &OrderBook::addAskMarketOrder, FunctionTypes::NonPayable, std::vector{"assetAmount", "assetPrice"}), + std::make_tuple("addBidMarketOrder", &OrderBook::addBidMarketOrder, FunctionTypes::NonPayable, std::vector{"assetAmount", "assetPrice"}) + ); + }); + } +}; + +#endif // ORDERBOOK_H diff --git a/src/contract/templates/ownable.cpp b/src/contract/templates/ownable.cpp new file mode 100644 index 00000000..818f83da --- /dev/null +++ b/src/contract/templates/ownable.cpp @@ -0,0 +1,98 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "ownable.h" + +#include "../../utils/strconv.h" + +Ownable::Ownable( + const Address& address, const DB& db +) : DynamicContract(address, db), owner_(this) +{ +#ifdef BUILD_TESTNET + auto key = db.get(std::string("owner_"), this->getDBPrefix()); + if (!key.empty()) { + this->owner_ = Address(key); + } else { + Utils::safePrint("Ownable::Ownable for contract " + this->getContractName() + ": Owner not found in DB, setting to this->getContractCreator(): " + this->getContractCreator().hex().get()); + this->owner_ = this->getContractCreator(); + } +#endif +#ifndef BUILD_TESTNET + this->owner_ = Address(db.get(std::string("owner_"), this->getDBPrefix())); +#endif + + this->owner_.commit(); + Ownable::registerContractFunctions(); + this->owner_.enableRegister(); +} + +Ownable::Ownable( + const Address& initialOwner, const Address& address, const Address& creator, const uint64_t& chainId +) : DynamicContract("Ownable", address, creator, chainId), owner_(this) +{ + this->owner_ = initialOwner; + this->owner_.commit(); + Ownable::registerContractFunctions(); + this->owner_.enableRegister(); +} + +Ownable::Ownable( + const std::string& derivedTypeName, + const Address& initialOwner, const Address& address, const Address& creator, const uint64_t& chainId +) : DynamicContract(derivedTypeName, address, creator, chainId), owner_(this) +{ + this->owner_ = initialOwner; + this->owner_.commit(); + Ownable::registerContractFunctions(); + this->owner_.enableRegister(); +} + +void Ownable::registerContractFunctions() { + Ownable::registerContract(); + this->registerMemberFunctions( + std::make_tuple("onlyOwner", &Ownable::onlyOwner, FunctionTypes::NonPayable, this), + std::make_tuple("owner", &Ownable::owner, FunctionTypes::View, this), + std::make_tuple("renounceOwnership", &Ownable::renounceOwnership, FunctionTypes::NonPayable, this), + std::make_tuple("transferOwnership", &Ownable::transferOwnership, FunctionTypes::NonPayable, this) + ); +} + +DBBatch Ownable::dump() const { + DBBatch batch = BaseContract::dump(); + batch.push_back(StrConv::stringToBytes("owner_"), this->owner_.get().asBytes(), this->getDBPrefix()); + return batch; +} + +void Ownable::checkOwner_() const { + if (this->owner_ != this->getCaller()) { + throw DynamicException("Ownable: caller is not the owner"); + } +} + +void Ownable::transferOwnership_(const Address& newOwner) { + Address prevOwner = this->owner_.get(); + this->owner_ = newOwner; + this->ownershipTransferred(prevOwner, newOwner); +} + + +void Ownable::onlyOwner() const { this->checkOwner_(); } + +Address Ownable::owner() const { return this->owner_.get(); } + +void Ownable::renounceOwnership() { + this->onlyOwner(); + this->transferOwnership_(Address()); +} + +void Ownable::transferOwnership(const Address& newOwner) { + this->onlyOwner(); + if (newOwner == Address()) throw DynamicException("Ownable: new owner is the zero address"); + this->transferOwnership_(newOwner); +} + diff --git a/src/contract/templates/ownable.h b/src/contract/templates/ownable.h new file mode 100644 index 00000000..a5527c66 --- /dev/null +++ b/src/contract/templates/ownable.h @@ -0,0 +1,103 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef OWNABLE_H +#define OWNABLE_H + +#include "../dynamiccontract.h" +#include "../variables/safeaddress.h" + +/// Template for an ownable contract. Based on OpenZeppelin v5.0.2 Ownable contract +class Ownable : virtual public DynamicContract { + private: + SafeAddress owner_; ///< Owner of the contract. + + /** + * Check that the contract caller is the owner. + * @throw DynamicException if caller is not the owner. + */ + void checkOwner_() const; + + /** + * Transfer ownership of the contract to a new owner. + * @param newOwner The address that will be the new owner. + */ + void transferOwnership_(const Address& newOwner); + + void registerContractFunctions() override; ///< Register the contract functions. + + public: + using ConstructorArguments = std::tuple
; ///< The constructor argument types. + + /// Event for when ownership of the contract is transferred. + void ownershipTransferred(const EventParam& previousOwner, const EventParam& newOwner) { + this->emitEvent(__func__, std::make_tuple(previousOwner, newOwner)); + } + + /** + * Constructor for loading contract from DB. + * @param address The address of the contract. + * @param db The database to use. + */ + Ownable(const Address& address, const DB& db); + + /** + * Constructor to be used when creating a new contract. + * @param initialOwner The address that will be the initial owner of the contract. + * @param address The address of the contract. + * @param creator The address of the creator of the contract. + * @param chainId The chain ID. + */ + Ownable( + const Address& initialOwner, const Address &address, + const Address &creator, const uint64_t &chainId + ); + + /** + * Constructor for derived types. + * @param derivedTypeName The name of the derived type. + * @param initialOwner The address that will be the initial owner of the contract. + * @param address The address of the contract. + * @param creator The address of the creator of the contract. + * @param chainId The chain ID. + */ + Ownable( + const std::string &derivedTypeName, const Address& initialOwner, + const Address &address, const Address &creator, const uint64_t &chainId + ); + + void onlyOwner() const; ///< Wrapper for checkOwner_(). + Address owner() const; ///< Get the owner's address. + void renounceOwnership(); ///< Renounce ownership of the contract. Ownership is transferred to an empty address (Address()). + + /** + * Wrapper for transferOwnership_(). + * @param newOwner The address of the new owner. + */ + void transferOwnership(const Address& newOwner); + + /// Register the contract. + static void registerContract() { + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{"initialOwner"}, + std::make_tuple("onlyOwner", &Ownable::onlyOwner, FunctionTypes::NonPayable, std::vector{}), + std::make_tuple("owner", &Ownable::owner, FunctionTypes::View, std::vector{}), + std::make_tuple("renounceOwnership", &Ownable::renounceOwnership, FunctionTypes::NonPayable, std::vector{}), + std::make_tuple("transferOwnership", &Ownable::transferOwnership, FunctionTypes::NonPayable, std::vector{"newOwner"}) + ); + ContractReflectionInterface::registerContractEvents( + std::make_tuple("ownershipTransferred", false, &Ownable::ownershipTransferred, std::vector{"previousOwner","newOwner"}) + ); + }); + } + + DBBatch dump() const override; ///< Dump method. +}; + +#endif // OWNABLE_H diff --git a/src/contract/templates/pebble.cpp b/src/contract/templates/pebble.cpp new file mode 100644 index 00000000..48495518 --- /dev/null +++ b/src/contract/templates/pebble.cpp @@ -0,0 +1,316 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "pebble.h" + +#include "../variables/reentrancyguard.h" + +#include "../../utils/uintconv.h" +#include "../../utils/strconv.h" + +Pebble::Pebble(const Address& address, const DB& db) + : DynamicContract(address, db), ERC721(address, db), ERC721URIStorage(address, db), Ownable(address, db), + maxSupply_(this), tokenIds_(this), tokenRarity_(this), + totalNormal_(this), totalGold_(this), totalDiamond_(this), + raritySeed_(this), diamondRarity_(this), goldRarity_(this), + authorizer_(this), minters_(this) +{ + // Load from DB. + this->maxSupply_ = UintConv::bytesToUint256(db.get(std::string("maxSupply_"), this->getDBPrefix())); + this->tokenIds_ = UintConv::bytesToUint256(db.get(std::string("tokenIds_"), this->getDBPrefix())); + this->totalNormal_ = Utils::fromBigEndian(db.get(std::string("totalNormal_"), this->getDBPrefix())); + this->totalGold_ = Utils::fromBigEndian(db.get(std::string("totalGold_"), this->getDBPrefix())); + this->totalDiamond_ = Utils::fromBigEndian(db.get(std::string("totalDiamond_"), this->getDBPrefix())); + this->raritySeed_ = UintConv::bytesToUint256(db.get(std::string("raritySeed_"), this->getDBPrefix())); + this->diamondRarity_ = UintConv::bytesToUint256(db.get(std::string("diamondRarity_"), this->getDBPrefix())); + this->goldRarity_ = UintConv::bytesToUint256(db.get(std::string("goldRarity_"), this->getDBPrefix())); + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("tokenRarity_"))) { + this->tokenRarity_[Utils::fromBigEndian(dbEntry.key)] = static_cast(Utils::fromBigEndian(dbEntry.value)); + } + auto batch = db.getBatch(this->getNewPrefix("minters_")); + for (const auto& dbEntry : batch) { + this->minters_[Address(dbEntry.key)] = dbEntry.value[0] == 1; + } + this->authorizer_ = Address(db.get(std::string("authorizer_"), this->getDBPrefix())); + this->maxSupply_.commit(); + this->tokenIds_.commit(); + this->tokenRarity_.commit(); + this->totalNormal_.commit(); + this->totalGold_.commit(); + this->totalDiamond_.commit(); + this->raritySeed_.commit(); + this->diamondRarity_.commit(); + this->goldRarity_.commit(); + this->minters_.commit(); + this->authorizer_.commit(); + Pebble::registerContractFunctions(); + this->maxSupply_.enableRegister(); + this->tokenIds_.enableRegister(); + this->tokenRarity_.enableRegister(); + this->totalNormal_.enableRegister(); + this->totalGold_.enableRegister(); + this->totalDiamond_.enableRegister(); + this->raritySeed_.enableRegister(); + this->diamondRarity_.enableRegister(); + this->goldRarity_.enableRegister(); + this->minters_.enableRegister(); + this->authorizer_.enableRegister(); +} + +Pebble::Pebble(const uint256_t& maxSupply, const Address& address, const Address& creator, const uint64_t& chainId) + : DynamicContract("Pebble", address, creator, chainId), + ERC721("Pebble", "Pebble", "PBL", address, creator, chainId), + ERC721URIStorage("Pebble", "Pebble", "PBL", address, creator, chainId), + Ownable(creator, address, creator, chainId), + maxSupply_(this, maxSupply), + tokenIds_(this, 0), + tokenRarity_(this), + totalNormal_(this, 0), + totalGold_(this, 0), + totalDiamond_(this, 0), + raritySeed_(this, 1000000), + diamondRarity_(this, 1), + goldRarity_(this, 10), + authorizer_(this, Address()), + minters_(this) { + #ifdef BUILD_TESTNET + if (creator != Address(Hex::toBytes("0xc2f2ba5051975004171e6d4781eeda927e884024"))) { + throw DynamicException("Only the Chain Owner can create this contract"); + } + #endif + this->maxSupply_.commit(); + this->tokenIds_.commit(); + this->tokenRarity_.commit(); + this->totalNormal_.commit(); + this->totalGold_.commit(); + this->totalDiamond_.commit(); + this->raritySeed_.commit(); + this->diamondRarity_.commit(); + this->goldRarity_.commit(); + this->authorizer_.commit(); + this->minters_.commit(); + Pebble::registerContractFunctions(); + this->maxSupply_.enableRegister(); + this->tokenIds_.enableRegister(); + this->tokenRarity_.enableRegister(); + this->totalNormal_.enableRegister(); + this->totalGold_.enableRegister(); + this->totalDiamond_.enableRegister(); + this->raritySeed_.enableRegister(); + this->diamondRarity_.enableRegister(); + this->goldRarity_.enableRegister(); + this->authorizer_.enableRegister(); + this->minters_.enableRegister(); +} + +DBBatch Pebble::dump() const { + // We need to dump all the data from the parent classes as well. + DBBatch batch = ERC721URIStorage::dump(); + const auto ownableDump = Ownable::dump(); + for (const auto& dbItem : ownableDump.getPuts()) { + batch.push_back(dbItem); + } + for (const auto& dbItem : ownableDump.getDels()) { + batch.delete_key(dbItem); + } + // Then, dump the contents of this class. + batch.push_back(StrConv::stringToBytes("maxSupply_"), UintConv::uint256ToBytes(this->maxSupply_.get()), this->getDBPrefix()); + batch.push_back(StrConv::stringToBytes("tokenIds_"), UintConv::uint256ToBytes(this->tokenIds_.get()), this->getDBPrefix()); + batch.push_back(StrConv::stringToBytes("totalNormal_"), UintConv::uint64ToBytes(this->totalNormal_.get()), this->getDBPrefix()); + batch.push_back(StrConv::stringToBytes("totalGold_"), UintConv::uint64ToBytes(this->totalGold_.get()), this->getDBPrefix()); + batch.push_back(StrConv::stringToBytes("totalDiamond_"), UintConv::uint64ToBytes(this->totalDiamond_.get()), this->getDBPrefix()); + batch.push_back(StrConv::stringToBytes("raritySeed_"), UintConv::uint256ToBytes(this->raritySeed_.get()), this->getDBPrefix()); + batch.push_back(StrConv::stringToBytes("diamondRarity_"), UintConv::uint256ToBytes(this->diamondRarity_.get()), this->getDBPrefix()); + batch.push_back(StrConv::stringToBytes("goldRarity_"), UintConv::uint256ToBytes(this->goldRarity_.get()), this->getDBPrefix()); + for (auto it = this->tokenRarity_.cbegin(); it != this->tokenRarity_.cend(); ++it) { + batch.push_back(UintConv::uint256ToBytes(it->first), UintConv::uint8ToBytes(static_cast(it->second)), this->getNewPrefix("tokenRarity_")); + } + for (auto it = this->minters_.cbegin(); it != this->minters_.cend(); ++it) { + batch.push_back(it->first.asBytes(), UintConv::uint8ToBytes(static_cast(it->second)), this->getNewPrefix("minters_")); + } + batch.push_back(StrConv::stringToBytes("authorizer_"), this->authorizer_.get().asBytes(), this->getDBPrefix()); + return batch; +} + +Address Pebble::update_(const Address& to, const uint256_t& tokenId, const Address& auth) { + ERC721URIStorage::update_(to, tokenId, auth); + return ERC721::update_(to, tokenId, auth); +} + +void Pebble::mintNFT(const Address& to, const uint64_t& num) { + #ifdef BUILD_TESTNET + ReentrancyGuard guard(this->reentrancyLock_); + if (this->getBlockHeight() > 2200) { + auto it = this->minters_.find(this->getCaller()); + if (it == this->minters_.cend() || !it->second) { + throw DynamicException("Minter not allowed"); + } + } + if (num > 25) throw DynamicException("You can only mint 25 tokens in a single transaction"); + for (uint64_t i = 0; i < num; ++i) { + if (this->tokenIds_ >= this->maxSupply_) throw DynamicException("Max supply reached"); + this->mint_(to, this->tokenIds_.get()); + Rarity rarity = this->determineRarity(this->getRandom()); + switch (rarity) { + case Rarity::Normal: ++this->totalNormal_; break; + case Rarity::Gold: ++this->totalGold_; break; + case Rarity::Diamond: ++this->totalDiamond_; break; + } + this->tokenRarity_[static_cast(this->tokenIds_.get())] = rarity; + this->MintedNFT(to, this->tokenIds_.get(), rarity); + ++this->tokenIds_; + } + #endif + #ifndef BUILD_TESTNET + ReentrancyGuard guard(this->reentrancyLock_); + auto it = this->minters_.find(this->getCaller()); + if (it == this->minters_.cend() || !it->second) { + throw DynamicException("Minter not allowed"); + } + if (num > 25) throw DynamicException("You can only mint 25 tokens in a single transaction"); + for (uint64_t i = 0; i < num; ++i) { + if (this->tokenIds_ >= this->maxSupply_) throw DynamicException("Max supply reached"); + this->mint_(to, this->tokenIds_.get()); + Rarity rarity = this->determineRarity(this->getRandom()); + switch (rarity) { + case Rarity::Normal: ++this->totalNormal_; break; + case Rarity::Gold: ++this->totalGold_; break; + case Rarity::Diamond: ++this->totalDiamond_; break; + } + this->tokenRarity_[static_cast(this->tokenIds_.get())] = rarity; + this->MintedNFT(to, this->tokenIds_.get(), rarity); + ++this->tokenIds_; + } + #endif +} + +std::string Pebble::getTokenRarity(const uint256_t& tokenId) const { + auto it = this->tokenRarity_.find(static_cast(tokenId)); + if (it == this->tokenRarity_.cend()) return "Unknown"; + return this->rarityToString(it->second); +} + +uint256_t Pebble::totalSupply() const { return this->tokenIds_.get(); } + +uint256_t Pebble::maxSupply() const { return this->maxSupply_.get(); } + +uint64_t Pebble::totalNormal() const { return this->totalNormal_.get(); } + +uint64_t Pebble::totalGold() const { return this->totalGold_.get(); } + +uint64_t Pebble::totalDiamond() const { return this->totalDiamond_.get(); } + +uint256_t Pebble::raritySeed() const { return this->raritySeed_.get(); } + +uint256_t Pebble::goldRarity() const { return this->goldRarity_.get(); } + +uint256_t Pebble::diamondRarity() const { return this->diamondRarity_.get(); } + +void Pebble::setRaritySeed(const uint256_t &seed) { + this->onlyOwner(); this->raritySeed_ = seed; +} + +void Pebble::setGoldRarity(const uint256_t &rarity) { + this->onlyOwner(); this->goldRarity_ = rarity; +} + +void Pebble::setDiamondRarity(const uint256_t &rarity) { + this->onlyOwner(); this->diamondRarity_ = rarity; +} + +std::string Pebble::tokenURI(const uint256_t &tokenId) const { + auto it = this->tokenRarity_.find(static_cast(tokenId)); + if (it == this->tokenRarity_.cend()) return ""; + return std::string("https://s3.amazonaws.com/com.applayer.pebble/") + this->rarityToString(it->second) + ".json"; +} + +Pebble::Rarity Pebble::determineRarity(const uint256_t& randomNumber) const { + auto value = randomNumber % this->raritySeed_.get(); + // 1.000.000 + /** + * gold: 1: 100 000 (0.01%) + * Diamond: 1: 1000 000 (0.001%) + */ + if (value <= this->diamondRarity_.get()) { + return Rarity::Diamond; + } else if (value <= this->goldRarity_.get()) { + return Rarity::Gold; + } else { + return Rarity::Normal; + } +} + +std::string Pebble::rarityToString(const Rarity& rarity) const { + std::string ret = ""; + switch (rarity) { + case Rarity::Normal: ret = "Normal"; break; + case Rarity::Gold: ret = "Gold"; break; + case Rarity::Diamond: ret = "Diamond"; break; + } + return ret; +} + +void Pebble::onlyAuthorizer() { + if (this->getCaller() != this->authorizer_) { + throw DynamicException("Pebble: caller is not the authorizer"); + } +} + +void Pebble::changeAuthorizer(const Address& newAuthorizer) { + this->onlyOwner(); + this->authorizer_ = newAuthorizer; +} + +void Pebble::addMinter(const Address& minter) { + this->onlyAuthorizer(); + this->minters_[minter] = true; +} + +void Pebble::removeMinter(const Address& minter) { + this->onlyAuthorizer(); + this->minters_[minter] = false; +} + +void Pebble::canMint(const Address& minter) const { + auto it = this->minters_.find(minter); + if (it == this->minters_.cend() || !it->second) { + throw DynamicException("Pebble: caller is not a minter"); + } +} + +Address Pebble::getAuthorizer() const { + return this->authorizer_.get(); +} + +void Pebble::registerContractFunctions() { + Pebble::registerContract(); + this->registerMemberFunctions( + std::make_tuple("mintNFT", &Pebble::mintNFT, FunctionTypes::NonPayable, this), + std::make_tuple("getTokenRarity", &Pebble::getTokenRarity, FunctionTypes::View, this), + std::make_tuple("totalSupply", &Pebble::totalSupply, FunctionTypes::View, this), + std::make_tuple("maxSupply", &Pebble::maxSupply, FunctionTypes::View, this), + std::make_tuple("totalNormal", &Pebble::totalNormal, FunctionTypes::View, this), + std::make_tuple("totalGold", &Pebble::totalGold, FunctionTypes::View, this), + std::make_tuple("totalDiamond", &Pebble::totalDiamond, FunctionTypes::View, this), + std::make_tuple("raritySeed", &Pebble::raritySeed, FunctionTypes::View, this), + std::make_tuple("diamondRarity", &Pebble::diamondRarity, FunctionTypes::View, this), + std::make_tuple("goldRarity", &Pebble::goldRarity, FunctionTypes::View, this), + std::make_tuple("setRaritySeed", &Pebble::setRaritySeed, FunctionTypes::NonPayable, this), + std::make_tuple("setDiamondRarity", &Pebble::setDiamondRarity, FunctionTypes::NonPayable, this), + std::make_tuple("setGoldRarity", &Pebble::setGoldRarity, FunctionTypes::NonPayable, this), + std::make_tuple("tokenURI", &Pebble::tokenURI, FunctionTypes::View, this), + std::make_tuple("determineRarity", &Pebble::determineRarity, FunctionTypes::View, this), + std::make_tuple("rarityToString", &Pebble::rarityToString, FunctionTypes::View, this), + std::make_tuple("onlyAuthorizer", &Pebble::onlyAuthorizer, FunctionTypes::NonPayable, this), + std::make_tuple("changeAuthorizer", &Pebble::changeAuthorizer, FunctionTypes::NonPayable, this), + std::make_tuple("addMinter", &Pebble::addMinter, FunctionTypes::NonPayable, this), + std::make_tuple("removeMinter", &Pebble::removeMinter, FunctionTypes::NonPayable, this), + std::make_tuple("canMint", &Pebble::canMint, FunctionTypes::View, this), + std::make_tuple("getAuthorizer", &Pebble::getAuthorizer, FunctionTypes::View, this) + ); +} + diff --git a/src/contract/templates/pebble.h b/src/contract/templates/pebble.h new file mode 100644 index 00000000..55f8bce7 --- /dev/null +++ b/src/contract/templates/pebble.h @@ -0,0 +1,191 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef PEBBLE_H +#define PEBBLE_H + +#include "standards/erc721uristorage.h" +#include "ownable.h" +#include "../variables/safeunorderedmap.h" +#include "../variables/safeuint.h" + +/// Template for the Pebble NFT mining contract. +class Pebble : public virtual ERC721URIStorage, public virtual Ownable { + public: + /// Enum for pebble rarity. MUST be public. + enum class Rarity { Normal, Gold, Diamond }; + + protected: + SafeUint256_t maxSupply_; ///< Max supply of tokens. + SafeUint256_t tokenIds_; ///< Current token id. + SafeUnorderedMap tokenRarity_; ///< Map of token rarities. + SafeUint64_t totalNormal_; ///< Total number of Normal rarity tokens. + SafeUint64_t totalGold_; ///< Total number of Gold rarity tokens. + SafeUint64_t totalDiamond_; ///< Total number of Diamond rarity tokens. + SafeUint256_t raritySeed_; ///< Current Normal token rarity seed. + SafeUint256_t goldRarity_; ///< Current Gold token rarity seed. + SafeUint256_t diamondRarity_; ///< Current Diamond token rarity seed. + SafeAddress authorizer_; ///< Authorizer address. + SafeUnorderedMap minters_; ///< Map of minter addresses. + + void registerContractFunctions() override; ///< Register the contract's functions. + + /** + * Update the state of a given address related to a given token. + * @param to The address related to the token. + * @param tokenId The ID of the token. + * @param auth The authorizer address. + * @return The updated address. + */ + Address update_(const Address& to, const uint256_t& tokenId, const Address& auth) override; + + public: + using ConstructorArguments = std::tuple; ///< The contract's constructor arguments. + + /// Event for when an NFT is minted. + void MintedNFT(const EventParam& user, const EventParam& tokenId, const EventParam& rarity) { + this->emitEvent("MintedNFT", std::make_tuple(user, tokenId, rarity)); + } + + /** + * Constructor for loading the contract from DB. + * @param address The address of the contract. + * @param db The database to use. + */ + Pebble(const Address& address, const DB& db); + + /** + * Constructor for creating the contract from scratch. + * @param maxSupply Max supply of tokens. + * @param address The address of the contract. + * @param creator The address of the creator of the contract. + * @param chainId The chain ID. + */ + Pebble(const uint256_t& maxSupply, const Address& address, const Address& creator, const uint64_t& chainId); + + /** + * Mint an NFT to a given address. + * @param to The address that will receive the NFT. + * @param num The number of NFTs to mint in a single transaction (max 25). + */ + void mintNFT(const Address& to, const uint64_t& num); + + /** + * Get the token rarity for a given token ID. + * @param tokenId The token ID to query. + */ + std::string getTokenRarity(const uint256_t& tokenId) const; + + ///@{ + /** Getter. */ + uint256_t totalSupply() const; + uint256_t maxSupply() const; + uint64_t totalNormal() const; + uint64_t totalGold() const; + uint64_t totalDiamond() const; + uint256_t raritySeed() const; + uint256_t diamondRarity() const; + uint256_t goldRarity() const; + ///@} + + ///@{ + /** Setter. */ + void setRaritySeed(const uint256_t& seed); + void setGoldRarity(const uint256_t& rarity); + void setDiamondRarity(const uint256_t& rarity); + ///@} + + /** + * Get the URI of a given token. + * @param tokenId The token ID to query. + */ + std::string tokenURI(const uint256_t &tokenId) const final; + + /** + * Randomly generate a rarity type. + * @param randomNumber The random number to use. + */ + Rarity determineRarity(const uint256_t& randomNumber) const; + + /** + * Get a given rarity type as a string. + * @param rarity The rarity type to convert. + */ + std::string rarityToString(const Rarity& rarity) const; + + /** + * Check if contract caller is the authorizer. + * @throw DynamicException if caller is not the authorizer. + */ + void onlyAuthorizer(); + + /** + * Change the authorizer address. + * @param newAuthorizer The address that will be the new authorizer. + */ + void changeAuthorizer(const Address& newAuthorizer); + + /** + * Add a minter address to the map. + * @param minter The minter address to add. + */ + void addMinter(const Address& minter); + + /** + * Remove a minter address to the map. + * @param minter The minter address to remove. + */ + void removeMinter(const Address& minter); + + /** + * Check if a given minter address is allowed to mint. + * @param minter The minter address to check. + * @throw DynamicException if address is not allowed to mint. + */ + void canMint(const Address& minter) const; + + Address getAuthorizer() const; ///< Get the authorizer address. + + /// Register contract class via ContractReflectionInterface. + static void registerContract() { + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{"maxSupply"}, + std::make_tuple("mintNFT", &Pebble::mintNFT, FunctionTypes::NonPayable, std::vector{"to"}), + std::make_tuple("getTokenRarity", &Pebble::getTokenRarity, FunctionTypes::View, std::vector{"tokenId"}), + std::make_tuple("totalSupply", &Pebble::totalSupply, FunctionTypes::View, std::vector{}), + std::make_tuple("maxSupply", &Pebble::maxSupply, FunctionTypes::View, std::vector{}), + std::make_tuple("totalNormal", &Pebble::totalNormal, FunctionTypes::View, std::vector{}), + std::make_tuple("totalGold", &Pebble::totalGold, FunctionTypes::View, std::vector{}), + std::make_tuple("totalDiamond", &Pebble::totalDiamond, FunctionTypes::View, std::vector{}), + std::make_tuple("raritySeed", &Pebble::raritySeed, FunctionTypes::View, std::vector{}), + std::make_tuple("diamondRarity", &Pebble::diamondRarity, FunctionTypes::View, std::vector{}), + std::make_tuple("goldRarity", &Pebble::goldRarity, FunctionTypes::View, std::vector{}), + std::make_tuple("setRaritySeed", &Pebble::setRaritySeed, FunctionTypes::NonPayable, std::vector{"seed"}), + std::make_tuple("setDiamondRarity", &Pebble::setDiamondRarity, FunctionTypes::NonPayable, std::vector{"rarity"}), + std::make_tuple("setGoldRarity", &Pebble::setGoldRarity, FunctionTypes::NonPayable, std::vector{"rarity"}), + std::make_tuple("tokenURI", &Pebble::tokenURI, FunctionTypes::View, std::vector{"tokenId"}), + std::make_tuple("determineRarity", &Pebble::determineRarity, FunctionTypes::View, std::vector{"randomNumber"}), + std::make_tuple("rarityToString", &Pebble::rarityToString, FunctionTypes::View, std::vector{"rarity"}), + std::make_tuple("onlyAuthorizer", &Pebble::onlyAuthorizer, FunctionTypes::NonPayable, std::vector{}), + std::make_tuple("changeAuthorizer", &Pebble::changeAuthorizer, FunctionTypes::NonPayable, std::vector{"newAuthorizer"}), + std::make_tuple("addMinter", &Pebble::addMinter, FunctionTypes::NonPayable, std::vector{"minter"}), + std::make_tuple("removeMinter", &Pebble::removeMinter, FunctionTypes::NonPayable, std::vector{"minter"}), + std::make_tuple("canMint", &Pebble::canMint, FunctionTypes::View, std::vector{"minter"}), + std::make_tuple("getAuthorizer", &Pebble::getAuthorizer, FunctionTypes::View, std::vector{}) + ); + ContractReflectionInterface::registerContractEvents( + std::make_tuple("MintedNFT", false, &Pebble::MintedNFT, std::vector{"user","tokenId", "rarity"}) + ); + }); + } + + DBBatch dump() const override; ///< Dump method. +}; + +#endif // PEBBLE_H diff --git a/src/contract/templates/randomnesstest.cpp b/src/contract/templates/randomnesstest.cpp new file mode 100644 index 00000000..5ef92c29 --- /dev/null +++ b/src/contract/templates/randomnesstest.cpp @@ -0,0 +1,53 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "randomnesstest.h" + +#include "../../utils/uintconv.h" +#include "../../utils/strconv.h" + +RandomnessTest::RandomnessTest(const Address& address, + const Address& creator, const uint64_t& chainId +) : DynamicContract("RandomnessTest", address, creator, chainId) { + this->randomValue_.commit(); + registerContractFunctions(); + this->randomValue_.enableRegister(); +} + +RandomnessTest::RandomnessTest(const Address& address, const DB& db) + : DynamicContract(address, db) { + this->randomValue_ = UintConv::bytesToUint256(db.get(std::string("randomValue_"), this->getDBPrefix())); + this->randomValue_.commit(); + registerContractFunctions(); + this->randomValue_.enableRegister(); +} + +RandomnessTest::~RandomnessTest() {} + +void RandomnessTest::registerContractFunctions() { + registerContract(); + this->registerMemberFunctions( + std::make_tuple("setRandom", &RandomnessTest::setRandom, FunctionTypes::NonPayable, this), + std::make_tuple("getRandom", &RandomnessTest::getRandom, FunctionTypes::View, this) + ); +} + +uint256_t RandomnessTest::setRandom() { + randomValue_ = DynamicContract::getRandom(); + return randomValue_.get(); +} + +uint256_t RandomnessTest::getRandom() const { + return randomValue_.get(); +} + +DBBatch RandomnessTest::dump() const { + DBBatch dbBatch = BaseContract::dump(); + dbBatch.push_back(StrConv::stringToBytes("randomValue_"), UintConv::uint256ToBytes(randomValue_.get()), this->getDBPrefix()); + return dbBatch; +} + diff --git a/src/contract/templates/randomnesstest.h b/src/contract/templates/randomnesstest.h new file mode 100644 index 00000000..2fd178f5 --- /dev/null +++ b/src/contract/templates/randomnesstest.h @@ -0,0 +1,85 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef RANDOMNESSTEST_H +#define RANDOMNESSTEST_H + +#include "../dynamiccontract.h" +#include "../../utils/utils.h" + +/** + * RandomnessTest is a simple contract that tests the randomness capabilities of BDK. + * It is used to test the the RandomGen class and the randomness of the BDK. + * The contract is equivalent to the following solidity contract: + * // SPDX-License-Identifier: MIT + * pragma solidity ^0.8.17; + * interface BDKPrecompile { + * function getRandom() external view returns (uint256); + * } + * + * contract RandomnessTest { + * uint256 private randomValue_; + * + * function setRandom() external { + * randomValue_ = BDKPrecompile(0x1000000000000000000000000000100000000001).getRandom(); + * } + * + * function getRandom() view external returns (uint256) { + * return randomValue_; + * } + * } + */ +class RandomnessTest : public DynamicContract { + private: + SafeUint256_t randomValue_; ///< The random value. + void registerContractFunctions() override; ///< Register the contract functions. + + public: + using ConstructorArguments = std::tuple<>; ///< The constructor arguments type. + + /** + * Constructor from create. Create contract and save it to database. + * @param address The address of the contract. + * @param creator The address of the creator of the contract. + * @param chainId The chain ID. + */ + RandomnessTest(const Address& address, + const Address& creator, const uint64_t& chainId + ); + + /** + * Constructor from load. Load contract from database. + * @param address The address of the contract. + * @param db The database to use. + */ + RandomnessTest(const Address& address, const DB& db); + + ~RandomnessTest() override; ///< Destructor. + + uint256_t setRandom(); ///< Set the random value. + + uint256_t getRandom() const; ///< Get the random value. + + /** + * Register the contract structure. + */ + static void registerContract() { + std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{}, + std::make_tuple("setRandom", &RandomnessTest::setRandom, FunctionTypes::NonPayable, std::vector{}), + std::make_tuple("getRandom", &RandomnessTest::getRandom, FunctionTypes::View, std::vector{}) + ); + }); + } + + /// Dump method + DBBatch dump() const override; +}; + +#endif // THROWTESTB_H diff --git a/src/contract/templates/simplecontract.cpp b/src/contract/templates/simplecontract.cpp index 47741c8d..4070d6c3 100644 --- a/src/contract/templates/simplecontract.cpp +++ b/src/contract/templates/simplecontract.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -7,48 +7,68 @@ See the LICENSE.txt file in the project root for more information. #include "simplecontract.h" +#include "../../utils/uintconv.h" +#include "../../utils/strconv.h" +#include "../../utils/utils.h" + SimpleContract::SimpleContract( const std::string& name, - const uint256_t& value, + const uint256_t& number, const std::tuple& tuple, - ContractManagerInterface &interface, const Address& address, const Address& creator, - const uint64_t& chainId, - const std::unique_ptr &db -) : DynamicContract(interface, "SimpleContract", address, creator, chainId, db), - name_(this), value_(this), tuple_(this) + const uint64_t& chainId +) : DynamicContract("SimpleContract", address, creator, chainId), + name_(this), number_(this), tuple_(this), count_(this) { this->name_ = name; - this->value_ = value; + this->number_ = number; this->tuple_ = tuple; + this->count_ = 0; + + this->name_.commit(); + this->number_.commit(); + this->tuple_.commit(); + this->count_.commit(); + registerContractFunctions(); + + this->name_.enableRegister(); + this->number_.enableRegister(); + this->tuple_.enableRegister(); + this->count_.enableRegister(); } SimpleContract::SimpleContract( - ContractManagerInterface &interface, const Address& address, - const std::unique_ptr &db -) : DynamicContract(interface, address, db), name_(this), value_(this), tuple_(this) { - this->name_ = Utils::bytesToString(db->get(std::string("name_"), this->getDBPrefix())); - this->value_ = Utils::bytesToUint256(db->get(std::string("value_"), this->getDBPrefix())); + const DB& db +) : DynamicContract(address, db), name_(this), number_(this), tuple_(this) { + this->name_ = StrConv::bytesToString(db.get(std::string("name_"), this->getDBPrefix())); + this->number_ = UintConv::bytesToUint256(db.get(std::string("number_"), this->getDBPrefix())); this->tuple_ = std::make_tuple( - Utils::bytesToString(db->get(std::string("tuple_name"), this->getDBPrefix())), - Utils::bytesToUint256(db->get(std::string("tuple_value"), this->getDBPrefix())) + StrConv::bytesToString(db.get(std::string("tuple_name"), this->getDBPrefix())), + UintConv::bytesToUint256(db.get(std::string("tuple_number"), this->getDBPrefix())) ); + this->count_ = 0; + + this->name_.commit(); + this->number_.commit(); + this->tuple_.commit(); + this->count_.commit(); + registerContractFunctions(); -} -SimpleContract::~SimpleContract() { - this->db_->put(std::string("name_"), Utils::stringToBytes(this->name_.get()), this->getDBPrefix()); - this->db_->put(std::string("value_"), Utils::uint256ToBytes(this->value_.get()), this->getDBPrefix()); - this->db_->put(std::string("tuple_name"), Utils::stringToBytes(get<0>(this->tuple_)), this->getDBPrefix()); - this->db_->put(std::string("tuple_value"), Utils::uint256ToBytes(get<1>(this->tuple_)), this->getDBPrefix()); + this->name_.enableRegister(); + this->number_.enableRegister(); + this->tuple_.enableRegister(); + this->count_.enableRegister(); } +SimpleContract::~SimpleContract() {}; + void SimpleContract::setName(const std::string& argName) { if (this->getCaller() != this->getContractCreator()) { - throw std::runtime_error("Only contract creator can call this function."); + throw DynamicException("Only contract creator can call this function."); } this->name_ = argName; this->nameChanged(this->name_.get()); @@ -56,69 +76,72 @@ void SimpleContract::setName(const std::string& argName) { void SimpleContract::setNames(const std::vector& argName) { if (this->getCaller() != this->getContractCreator()) { - throw std::runtime_error("Only contract creator can call this function."); + throw DynamicException("Only contract creator can call this function."); } this->name_ = ""; for (const auto& name : argName) this->name_ += name; this->nameChanged(this->name_.get()); } -void SimpleContract::setValue(const uint256_t& argValue) { +void SimpleContract::setNumber(const uint256_t& argNumber) { if (this->getCaller() != this->getContractCreator()) { - throw std::runtime_error("Only contract creator can call this function."); + throw DynamicException("Only contract creator can call this function."); } - this->value_ = argValue; - this->valueChanged(this->value_.get()); + this->number_ = argNumber; + this->numberChanged(this->number_.get()); } -void SimpleContract::setValues(const std::vector& argValue) { - this->value_ = 0; - for (const auto& value : argValue) this->value_ += value; - this->valueChanged(this->value_.get()); +void SimpleContract::setNumbers(const std::vector& argNumber) { + if (this->getCaller() != this->getContractCreator()) { + throw DynamicException("Only contract creator can call this function."); + } + this->number_ = 0; + for (const auto& number : argNumber) this->number_ += number; + this->numberChanged(this->number_.get()); } -void SimpleContract::setNamesAndValues( - const std::vector& argName, const std::vector& argValue +void SimpleContract::setNamesAndNumbers( + const std::vector& argName, const std::vector& argNumber ) { if (this->getCaller() != this->getContractCreator()) { - throw std::runtime_error("Only contract creator can call this function."); + throw DynamicException("Only contract creator can call this function."); } this->name_ = ""; - this->value_ = 0; + this->number_ = 0; for (const auto& name : argName) this->name_ += name; - for (const auto& value : argValue) this->value_ += value; - this->nameAndValueChanged(this->name_.get(), this->value_.get()); + for (const auto& number : argNumber) this->number_ += number; + this->nameAndNumberChanged(this->name_.get(), this->number_.get()); } -void SimpleContract::setNamesAndValuesInTuple( - const std::vector>& argNameAndValue +void SimpleContract::setNamesAndNumbersInTuple( + const std::vector>& argNameAndNumber ) { if (this->getCaller() != this->getContractCreator()) { - throw std::runtime_error("Only contract creator can call this function."); + throw DynamicException("Only contract creator can call this function."); } this->name_ = ""; - this->value_ = 0; - for (const auto& [name, value] : argNameAndValue) { this->name_ += name; this->value_ += value; } - this->nameAndValueTupleChanged(std::make_tuple(this->name_.get(), this->value_.get())); + this->number_ = 0; + for (const auto& [name, number] : argNameAndNumber) { this->name_ += name; this->number_ += number; } + this->nameAndNumberTupleChanged(std::make_tuple(this->name_.get(), this->number_.get())); } -void SimpleContract::setNamesAndValuesInArrayOfArrays( - const std::vector>> &argNameAndValue +void SimpleContract::setNamesAndNumbersInArrayOfArrays( + const std::vector>> &argNameAndNumber ) { if (this->getCaller() != this->getContractCreator()) { - throw std::runtime_error("Only contract creator can call this function."); + throw DynamicException("Only contract creator can call this function."); } this->name_ = ""; - this->value_ = 0; - for (const auto& nameAndValue : argNameAndValue) { - for (const auto& [name, value] : nameAndValue) { this->name_ += name; this->value_ += value; } + this->number_ = 0; + for (const auto& nameAndNumber : argNameAndNumber) { + for (const auto& [name, number] : nameAndNumber) { this->name_ += name; this->number_ += number; } } - this->nameAndValueChanged(this->name_.get(), this->value_.get()); + this->nameAndNumberChanged(this->name_.get(), this->number_.get()); } void SimpleContract::setTuple(const std::tuple& argTuple) { if (this->getCaller() != this->getContractCreator()) { - throw std::runtime_error("Only contract creator can call this function."); + throw DynamicException("Only contract creator can call this function."); } this->tuple_ = argTuple; this->tupleChanged(std::make_tuple(get<0>(this->tuple_), get<1>(this->tuple_))); @@ -126,6 +149,8 @@ void SimpleContract::setTuple(const std::tuple& argTuple std::string SimpleContract::getName() const { return this->name_.get(); } +std::string SimpleContract::getNameNonView() { return this->name_.get(); } + std::vector SimpleContract::getNames(const uint256_t& i) const { std::vector names; for (uint256_t j = 0; j < i; j++) { @@ -134,76 +159,95 @@ std::vector SimpleContract::getNames(const uint256_t& i) const { return names; } -uint256_t SimpleContract::getValue() const { return this->value_.get(); } +uint256_t SimpleContract::getNumber() const { return this->number_.get(); } -uint256_t SimpleContract::getValue(const uint256_t& i) const { return this->value_.get() + i; } +uint256_t SimpleContract::getNumber(const uint256_t& i) const { return this->number_.get() + i; } -std::vector SimpleContract::getValues(const uint256_t& i) const { - std::vector values; - for (uint256_t j = 0; j < i; j++) values.emplace_back(this->value_.get()); - return values; +std::vector SimpleContract::getNumbers(const uint256_t& i) const { + std::vector numbers; + for (uint256_t j = 0; j < i; j++) numbers.emplace_back(this->number_.get()); + return numbers; } -std::tuple SimpleContract::getNameAndValue() const { - return std::make_tuple(this->name_.get(), this->value_.get()); +std::tuple SimpleContract::getNameAndNumber() const { + return std::make_tuple(this->name_.get(), this->number_.get()); } std::tuple, std::vector> -SimpleContract::getNamesAndValues(const uint256_t& i) const { +SimpleContract::getNamesAndNumbers(const uint256_t& i) const { std::vector names; - std::vector values; + std::vector numbers; for (uint256_t j = 0; j < i; j++) { names.emplace_back(this->name_.get()); - values.emplace_back(this->value_.get()); + numbers.emplace_back(this->number_.get()); } - return std::make_tuple(names, values); + return std::make_tuple(names, numbers); } std::vector> -SimpleContract::getNamesAndValuesInTuple(const uint256_t& i) const { - std::vector> namesAndValues; +SimpleContract::getNamesAndNumbersInTuple(const uint256_t& i) const { + std::vector> namesAndNumbers; for (uint256_t j = 0; j < i; j++) { - namesAndValues.emplace_back(std::make_tuple(this->name_.get(), this->value_.get())); + namesAndNumbers.emplace_back(this->name_.get(), this->number_.get()); } - return namesAndValues; + return namesAndNumbers; } std::vector>> -SimpleContract::getNamesAndValuesInArrayOfArrays(const uint256_t& i) const { - std::vector>> namesAndValues; +SimpleContract::getNamesAndNumbersInArrayOfArrays(const uint256_t& i) const { + std::vector>> namesAndNumbers; for (uint256_t j = 0; j < i; j++) { - std::vector> nameAndValuesInternal; + std::vector> nameAndNumbersInternal; for (uint256_t k = 0; k < i; k++) { - nameAndValuesInternal.emplace_back(std::make_tuple(this->name_.get(), this->value_.get())); + nameAndNumbersInternal.emplace_back(this->name_.get(), this->number_.get()); } - namesAndValues.emplace_back(nameAndValuesInternal); + namesAndNumbers.emplace_back(nameAndNumbersInternal); } - return namesAndValues; + return namesAndNumbers; } std::tuple SimpleContract::getTuple() const { return std::make_tuple(get<0>(this->tuple_), get<1>(this->tuple_)); } +uint256_t SimpleContract::getCount() const { return this->count_.get(); } + +void SimpleContract::onBlockNumber() { ++count_; } + void SimpleContract::registerContractFunctions() { registerContract(); - this->registerMemberFunction("setName", &SimpleContract::setName, FunctionTypes::NonPayable, this); - this->registerMemberFunction("setNames", &SimpleContract::setNames, FunctionTypes::NonPayable, this); - this->registerMemberFunction("setValue", &SimpleContract::setValue, FunctionTypes::NonPayable, this); - this->registerMemberFunction("setValues", &SimpleContract::setValues, FunctionTypes::NonPayable, this); - this->registerMemberFunction("setNamesAndValues", &SimpleContract::setNamesAndValues, FunctionTypes::NonPayable, this); - this->registerMemberFunction("setNamesAndValuesInTuple", &SimpleContract::setNamesAndValuesInTuple, FunctionTypes::NonPayable, this); - this->registerMemberFunction("setNamesAndValuesInArrayOfArrays", &SimpleContract::setNamesAndValuesInArrayOfArrays, FunctionTypes::NonPayable, this); - this->registerMemberFunction("setTuple", &SimpleContract::setTuple, FunctionTypes::NonPayable, this); - this->registerMemberFunction("getName", &SimpleContract::getName, FunctionTypes::View, this); - this->registerMemberFunction("getNames", &SimpleContract::getNames, FunctionTypes::View, this); - this->registerMemberFunction("getValue", static_cast(&SimpleContract::getValue), FunctionTypes::View, this); - this->registerMemberFunction("getValue", static_cast(&SimpleContract::getValue), FunctionTypes::View, this); - this->registerMemberFunction("getValues", &SimpleContract::getValues, FunctionTypes::View, this); - this->registerMemberFunction("getNameAndValue", &SimpleContract::getNameAndValue, FunctionTypes::View, this); - this->registerMemberFunction("getNamesAndValues", &SimpleContract::getNamesAndValues, FunctionTypes::View, this); - this->registerMemberFunction("getNamesAndValuesInTuple", &SimpleContract::getNamesAndValuesInTuple, FunctionTypes::View, this); - this->registerMemberFunction("getNamesAndValuesInArrayOfArrays", &SimpleContract::getNamesAndValuesInArrayOfArrays, FunctionTypes::View, this); - this->registerMemberFunction("getTuple", &SimpleContract::getTuple, FunctionTypes::View, this); + this->registerMemberFunctions( + std::make_tuple("setName", &SimpleContract::setName, FunctionTypes::NonPayable, this), + std::make_tuple("setNames", &SimpleContract::setNames, FunctionTypes::NonPayable, this), + std::make_tuple("setNumber", &SimpleContract::setNumber, FunctionTypes::NonPayable, this), + std::make_tuple("setNumbers", &SimpleContract::setNumbers, FunctionTypes::NonPayable, this), + std::make_tuple("setNamesAndNumbers", &SimpleContract::setNamesAndNumbers, FunctionTypes::NonPayable, this), + std::make_tuple("setNamesAndNumbersInTuple", &SimpleContract::setNamesAndNumbersInTuple, FunctionTypes::NonPayable, this), + std::make_tuple("setNamesAndNumbersInArrayOfArrays", &SimpleContract::setNamesAndNumbersInArrayOfArrays, FunctionTypes::NonPayable, this), + std::make_tuple("setTuple", &SimpleContract::setTuple, FunctionTypes::NonPayable, this), + std::make_tuple("getName", &SimpleContract::getName, FunctionTypes::View, this), + std::make_tuple("getNameNonView", &SimpleContract::getNameNonView, FunctionTypes::NonPayable, this), + std::make_tuple("getNames", &SimpleContract::getNames, FunctionTypes::View, this), + std::make_tuple("getNumber", static_cast(&SimpleContract::getNumber), FunctionTypes::View, this), + std::make_tuple("getNumber", static_cast(&SimpleContract::getNumber), FunctionTypes::View, this), + std::make_tuple("getNumbers", &SimpleContract::getNumbers, FunctionTypes::View, this), + std::make_tuple("getNameAndNumber", &SimpleContract::getNameAndNumber, FunctionTypes::View, this), + std::make_tuple("getNamesAndNumbers", &SimpleContract::getNamesAndNumbers, FunctionTypes::View, this), + std::make_tuple("getNamesAndNumbersInTuple", &SimpleContract::getNamesAndNumbersInTuple, FunctionTypes::View, this), + std::make_tuple("getNamesAndNumbersInArrayOfArrays", &SimpleContract::getNamesAndNumbersInArrayOfArrays, FunctionTypes::View, this), + std::make_tuple("getTuple", &SimpleContract::getTuple, FunctionTypes::View, this), + std::make_tuple("getCount", &SimpleContract::getCount, FunctionTypes::View, this) + ); + this->registerBlockObserver("onBlockNumber", 1, &SimpleContract::onBlockNumber, this); +} + +DBBatch SimpleContract::dump() const { + DBBatch dbBatch = BaseContract::dump(); + dbBatch.push_back(StrConv::stringToBytes("name_"), StrConv::stringToBytes(this->name_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("number_"), UintConv::uint256ToBytes(this->number_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("tuple_name"), StrConv::stringToBytes(get<0>(this->tuple_)), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("tuple_number"), UintConv::uint256ToBytes(get<1>(this->tuple_)), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("count_"), UintConv::uint256ToBytes(this->count_.get()), this->getDBPrefix()); + return dbBatch; } diff --git a/src/contract/templates/simplecontract.h b/src/contract/templates/simplecontract.h index adf6d6fa..d12744b1 100644 --- a/src/contract/templates/simplecontract.h +++ b/src/contract/templates/simplecontract.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -11,39 +11,44 @@ See the LICENSE.txt file in the project root for more information. #include "../dynamiccontract.h" #include "../variables/safestring.h" #include "../variables/safetuple.h" -#include "../../utils/utils.h" // SafeUintX_t aliases declared here +#include "../variables/safeuint.h" /** - * SimpleContract is a simple contract that stores a name and a value. - * It is used to test the contract manager. + * SimpleContract is a simple contract that stores a name, number and tuple. + * It is used to test the Contract Manager. */ class SimpleContract : public DynamicContract { private: SafeString name_; ///< The name of the contract. - SafeUint256_t value_; ///< The value of the contract. - SafeTuple tuple_; ///< Name and value as a tuple. + SafeUint256_t number_; ///< The number of the contract. + SafeTuple tuple_; ///< Name and number as a tuple. + SafeUint256_t count_; void registerContractFunctions() override; ///< Register the contract functions. public: /// Event for when the name changes. - void nameChanged(const EventParam& name) { this->emitEvent(__func__, std::make_tuple(name)); } + void nameChanged(const EventParam& name) { + this->emitEvent(__func__, std::make_tuple(name)); + } - /// Event for when the value changes. - void valueChanged(const EventParam& value) { this->emitEvent(__func__, std::make_tuple(value)); } + /// Event for when the number changes. + void numberChanged(const EventParam& number) { + this->emitEvent(__func__, std::make_tuple(number)); + } - /// Event for when the name and value tuple changes. + /// Event for when the name and number tuple changes. void tupleChanged(const EventParam, true>& tuple) { this->emitEvent(__func__, std::make_tuple(tuple)); } - /// Event for when the name and value change. Used for testing JSON ABI generation. - void nameAndValueChanged(const EventParam& name, const EventParam& value) { - this->emitEvent(__func__, std::make_tuple(name, value)); + /// Event for when the name and number change. Used for testing JSON ABI generation. + void nameAndNumberChanged(const EventParam& name, const EventParam& number) { + this->emitEvent(__func__, std::make_tuple(name, number)); } - /// Event for when the name and value change (as tuple). Used for testing JSON ABI generation - void nameAndValueTupleChanged(const EventParam, true>& nameAndValue) { - this->emitEvent(__func__, std::make_tuple(nameAndValue)); + /// Event for when the name and number change (as tuple). Used for testing JSON ABI generation + void nameAndNumberTupleChanged(const EventParam, true>& nameAndNumber) { + this->emitEvent(__func__, std::make_tuple(nameAndNumber)); } /// The constructor argument types. @@ -54,35 +59,29 @@ class SimpleContract : public DynamicContract { /** * Constructor from create. Create contract and save it to database. * @param name The name of the contract. - * @param value The value of the contract. - * @param tuple The name and value tuple of the contract. - * @param interface The interface to the contract manager. + * @param number The number of the contract. + * @param tuple The name and number tuple of the contract. * @param address The address of the contract. * @param creator The address of the creator of the contract. * @param chainId The chain ID. - * @param db The database to use. */ SimpleContract( const std::string& name, - const uint256_t& value, + const uint256_t& number, const std::tuple& tuple, - ContractManagerInterface &interface, const Address& address, const Address& creator, - const uint64_t& chainId, - const std::unique_ptr &db + const uint64_t& chainId ); /** * Constructor from load. Load contract from database. - * @param interface The interface to the contract manager. * @param address The address of the contract. * @param db The database to use. */ SimpleContract( - ContractManagerInterface &interface, const Address& address, - const std::unique_ptr &db + const DB& db ); ~SimpleContract() override; ///< Destructor. @@ -93,97 +92,108 @@ class SimpleContract : public DynamicContract { /// function setNames(string[] memory argName) public, the final name is the concatenation of all names void setNames(const std::vector& argName); - /// function setValue(uint256 argValue) public - void setValue(const uint256_t& argValue); + /// function setNumber(uint256 argNumber) public + void setNumber(const uint256_t& argNumber); - /// function setValues(uint256[] memory argValue) public, the final value is the sum of all values - void setValues(const std::vector& argValue); + /// function setNumbers(uint256[] memory argNumber) public, the final value is the sum of all values + void setNumbers(const std::vector& argNumber); - /// function setNamesAndValues(string[] memory argName, uint256[] memory argValue) public, + /// function setNamesAndNumbers(string[] memory argName, uint256[] memory argNumber) public, /// the final name is the concatenation of all names, the final value is the sum of all values - void setNamesAndValues(const std::vector& argName, const std::vector& argValue); + void setNamesAndNumbers(const std::vector& argName, const std::vector& argNumber); - /// function setNamesAndValuesInTuple(NameAndValue[] memory argNameAndValue) public, + /// function setNamesAndNumbersInTuple(NameAndNumber[] memory argNameAndNumber) public, /// the final name is the concatenation of all names, the final value is the sum of all values - void setNamesAndValuesInTuple(const std::vector>& argNameAndValue); + void setNamesAndNumbersInTuple(const std::vector>& argNameAndNumber); - /// function setNamesAndValuesInArrayOfArrays(NameAndValue[][] memory argNameAndValue) public. + /// function setNamesAndNumbersInArrayOfArrays(NameAndNumber[][] memory argNameAndNumber) public. /// the final name is the concatenation of all names, the final value is the sum of all values - void setNamesAndValuesInArrayOfArrays(const std::vector>>& argNameAndValue); + void setNamesAndNumbersInArrayOfArrays(const std::vector>>& argNameAndNumber); - /// equivalent to function setTuple(string name, uint256 value) public + /// equivalent to function setTuple(string name, uint256 number) public void setTuple(const std::tuple& argTuple); /// function getName() public view returns(string memory) std::string getName() const; + /// function getNameNonView() public returns(string memory) + std::string getNameNonView(); + /// function getNames(const uint256_t& i) public view returns(string[] memory) return string[] of size i with this->name_ as all elements. std::vector getNames(const uint256_t& i) const; - /// function getValue() public view returns(uint256) - uint256_t getValue() const; + /// function getNumber() public view returns(uint256) + uint256_t getNumber() const; // For testing overloading functions... - /// Function getValue(uint256 i) public view returns(uint256) return this->value_ + i. - uint256_t getValue(const uint256_t& i) const; + /// Function getNumber(uint256 i) public view returns(uint256) return this->number_ + i. + uint256_t getNumber(const uint256_t& i) const; - /// function getValues(const uint256_t& i) public view returns(uint256[] memory) return uint256[] of size i with this->value_ as all elements. - std::vector getValues(const uint256_t& i) const; + /// function getNumbers(const uint256_t& i) public view returns(uint256[] memory) return uint256[] of size i with this->number_ as all elements. + std::vector getNumbers(const uint256_t& i) const; - /// function getNameAndValue() public view returns(string memory, uint256) - std::tuple getNameAndValue() const; + /// function getNameAndNumber() public view returns(string memory, uint256) + std::tuple getNameAndNumber() const; - /// function getNamesAndValues(const uint256_t& i) public view returns(string[] memory, uint256[] memory) - /// return string[] of size i with this->name_ as all elements, return uint256[] of size i with this->value_ as all elements. - std::tuple, std::vector> getNamesAndValues(const uint256_t& i) const; + /// function getNamesAndNumbers(const uint256_t& i) public view returns(string[] memory, uint256[] memory) + /// return string[] of size i with this->name_ as all elements, return uint256[] of size i with this->number_ as all elements. + std::tuple, std::vector> getNamesAndNumbers(const uint256_t& i) const; - /// function getNamesAndValuesInTuple(const uint256_t& i) public view returns(NameAndValue[] memory) - /// return (string, uint256)[] of size i with this->name_ and this->value_ as all elements. - std::vector> getNamesAndValuesInTuple(const uint256_t& i) const; + /// function getNamesAndNumbersInTuple(const uint256_t& i) public view returns(NameAndNumber[] memory) + /// return (string, uint256)[] of size i with this->name_ and this->number_ as all elements. + std::vector> getNamesAndNumbersInTuple(const uint256_t& i) const; - /// function getNamesAndValuesInArrayOfArrays(const uint256_t& i) public view returns(NameAndValue[][] memory) - /// return (string, uint256)[][] of size i with this->name_ and this->value_ as all elements. - std::vector>> getNamesAndValuesInArrayOfArrays(const uint256_t& i) const; + /// function getNamesAndNumbersInArrayOfArrays(const uint256_t& i) public view returns(NameAndNumber[][] memory) + /// return (string, uint256)[][] of size i with this->name_ and this->number_ as all elements. + std::vector>> getNamesAndNumbersInArrayOfArrays(const uint256_t& i) const; /// equivalent to function getTuple() public view returns(string memory, uint256) std::tuple getTuple() const; + uint256_t getCount() const; + + void onBlockNumber(); + /// Register the contract structure. static void registerContract() { - ContractReflectionInterface::registerContractMethods< - SimpleContract, const std::string&, const uint256_t&, const std::tuple&, - ContractManagerInterface&, - const Address&, const Address&, const uint64_t&, - const std::unique_ptr& - >( - std::vector{"name_", "value_", "tuple_"}, - std::make_tuple("setName", &SimpleContract::setName, FunctionTypes::NonPayable, std::vector{"argName"}), - std::make_tuple("setNames", &SimpleContract::setNames, FunctionTypes::NonPayable, std::vector{"argName"}), - std::make_tuple("setValue", &SimpleContract::setValue, FunctionTypes::NonPayable, std::vector{"argValue"}), - std::make_tuple("setValues", &SimpleContract::setValues, FunctionTypes::NonPayable, std::vector{"argValue"}), - std::make_tuple("setNamesAndValues", &SimpleContract::setNamesAndValues, FunctionTypes::NonPayable, std::vector{"argName", "argValue"}), - std::make_tuple("setNamesAndValuesInTuple", &SimpleContract::setNamesAndValuesInTuple, FunctionTypes::NonPayable, std::vector{"argNameAndValue"}), - std::make_tuple("setNamesAndValuesInArrayOfArrays", &SimpleContract::setNamesAndValuesInArrayOfArrays, FunctionTypes::NonPayable, std::vector{"argNameAndValue"}), - std::make_tuple("setTuple", &SimpleContract::setTuple, FunctionTypes::NonPayable, std::vector{"argTupĺe"}), - std::make_tuple("getName", &SimpleContract::getName, FunctionTypes::View, std::vector{}), - std::make_tuple("getNames", &SimpleContract::getNames, FunctionTypes::View, std::vector{"i"}), - std::make_tuple("getValue", static_cast(&SimpleContract::getValue), FunctionTypes::View, std::vector{}), - std::make_tuple("getValue", static_cast(&SimpleContract::getValue), FunctionTypes::View, std::vector{}), - std::make_tuple("getValues", &SimpleContract::getValues, FunctionTypes::View, std::vector{"i"}), - std::make_tuple("getNameAndValue", &SimpleContract::getNameAndValue, FunctionTypes::View, std::vector{}), - std::make_tuple("getNamesAndValues", &SimpleContract::getNamesAndValues, FunctionTypes::View, std::vector{"i"}), - std::make_tuple("getNamesAndValuesInTuple", &SimpleContract::getNamesAndValuesInTuple, FunctionTypes::View, std::vector{"i"}), - std::make_tuple("getNamesAndValuesInArrayOfArrays", &SimpleContract::getNamesAndValuesInArrayOfArrays, FunctionTypes::View, std::vector{"i"}), - std::make_tuple("getTuple", &SimpleContract::getTuple, FunctionTypes::View, std::vector{}) - ); - ContractReflectionInterface::registerContractEvents( - std::make_tuple("nameChanged", false, &SimpleContract::nameChanged, std::vector{"name"}), - std::make_tuple("valueChanged", false, &SimpleContract::valueChanged, std::vector{"value"}), - std::make_tuple("tupleChanged", false, &SimpleContract::tupleChanged, std::vector{"tuple"}), - std::make_tuple("nameAndValueChanged", false, &SimpleContract::nameAndValueChanged, std::vector{"name", "value"}), - std::make_tuple("nameAndValueTupleChanged", false, &SimpleContract::nameAndValueTupleChanged, std::vector{"nameAndValue"}) - ); + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{"name_", "number_", "tuple_"}, + std::make_tuple("setName", &SimpleContract::setName, FunctionTypes::NonPayable, std::vector{"argName"}), + std::make_tuple("setNames", &SimpleContract::setNames, FunctionTypes::NonPayable, std::vector{"argName"}), + std::make_tuple("setNumber", &SimpleContract::setNumber, FunctionTypes::NonPayable, std::vector{"argNumber"}), + std::make_tuple("setNumbers", &SimpleContract::setNumbers, FunctionTypes::NonPayable, std::vector{"argNumber"}), + std::make_tuple("setNamesAndNumbers", &SimpleContract::setNamesAndNumbers, FunctionTypes::NonPayable, std::vector{"argName", "argNumber"}), + std::make_tuple("setNamesAndNumbersInTuple", &SimpleContract::setNamesAndNumbersInTuple, FunctionTypes::NonPayable, std::vector{"argNameAndNumber"}), + std::make_tuple("setNamesAndNumbersInArrayOfArrays", &SimpleContract::setNamesAndNumbersInArrayOfArrays, FunctionTypes::NonPayable, std::vector{"argNameAndNumber"}), + std::make_tuple("setTuple", &SimpleContract::setTuple, FunctionTypes::NonPayable, std::vector{"argTuple"}), + std::make_tuple("getName", &SimpleContract::getName, FunctionTypes::View, std::vector{}), + std::make_tuple("getNameNonView", &SimpleContract::getNameNonView, FunctionTypes::NonPayable, std::vector{}), + std::make_tuple("getNames", &SimpleContract::getNames, FunctionTypes::View, std::vector{"i"}), + std::make_tuple("getNumber", static_cast(&SimpleContract::getNumber), FunctionTypes::View, std::vector{}), + std::make_tuple("getNumber", static_cast(&SimpleContract::getNumber), FunctionTypes::View, std::vector{}), + std::make_tuple("getNumbers", &SimpleContract::getNumbers, FunctionTypes::View, std::vector{"i"}), + std::make_tuple("getNameAndNumber", &SimpleContract::getNameAndNumber, FunctionTypes::View, std::vector{}), + std::make_tuple("getNamesAndNumbers", &SimpleContract::getNamesAndNumbers, FunctionTypes::View, std::vector{"i"}), + std::make_tuple("getNamesAndNumbersInTuple", &SimpleContract::getNamesAndNumbersInTuple, FunctionTypes::View, std::vector{"i"}), + std::make_tuple("getNamesAndNumbersInArrayOfArrays", &SimpleContract::getNamesAndNumbersInArrayOfArrays, FunctionTypes::View, std::vector{"i"}), + std::make_tuple("getTuple", &SimpleContract::getTuple, FunctionTypes::View, std::vector{}), + std::make_tuple("getCount", &SimpleContract::getCount, FunctionTypes::View, std::vector{}), + std::make_tuple("onBlockNumber", &SimpleContract::onBlockNumber, FunctionTypes::NonPayable, std::vector{}) + ); + ContractReflectionInterface::registerContractEvents( + std::make_tuple("nameChanged", false, &SimpleContract::nameChanged, std::vector{"name"}), + std::make_tuple("numberChanged", false, &SimpleContract::numberChanged, std::vector{"number"}), + std::make_tuple("tupleChanged", false, &SimpleContract::tupleChanged, std::vector{"tuple"}), + std::make_tuple("nameAndNumberChanged", false, &SimpleContract::nameAndNumberChanged, std::vector{"name", "number"}), + std::make_tuple("nameAndNumberTupleChanged", false, &SimpleContract::nameAndNumberTupleChanged, std::vector{"nameAndNumber"}) + ); + }); } + + /// Dump method + DBBatch dump() const override; }; #endif // SIMPLECONTRACT_H diff --git a/src/contract/templates/snailtracer.cpp b/src/contract/templates/snailtracer.cpp new file mode 100644 index 00000000..ac0b83ba --- /dev/null +++ b/src/contract/templates/snailtracer.cpp @@ -0,0 +1,528 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "snailtracer.h" + +#include "../../utils/intconv.h" +#include "../../utils/strconv.h" + +SnailTracer::SnailTracer( + int256_t w, int256_t h, const Address& address, const Address& creator, const uint64_t& chainId +) : DynamicContract("SnailTracer", address, creator, chainId), width_(w), height_(h) { + // Initialize the image and rendering parameters + camera_ = Ray(Vector(50000000, 52000000, 295600000), norm(Vector(0, -42612, -1000000)), 0, false); + deltaX_ = Vector(width_.get() * 513500 / height_.get(), 0, 0); + deltaY_ = div(mul(norm(cross(deltaX_.raw(), get<1>(camera_))), 513500), 1000000); // camera.direction + + // Initialize the scene bounding boxes + spheres_.push_back(Sphere(100000000000, Vector(100001000000, 40800000, 81600000), Vector(0, 0, 0), Vector(750000, 250000, 250000), Material::Diffuse)); + spheres_.push_back(Sphere(100000000000, Vector(-99901000000, 40800000, 81600000), Vector(0, 0, 0), Vector(250000, 250000, 750000), Material::Diffuse)); + spheres_.push_back(Sphere(100000000000, Vector(50000000, 40800000, 100000000000), Vector(0, 0, 0), Vector(750000, 750000, 750000), Material::Diffuse)); + spheres_.push_back(Sphere(100000000000, Vector(50000000, 40800000, -99830000000), Vector(0, 0, 0), Vector(0, 0, 0), Material::Diffuse)); + spheres_.push_back(Sphere(100000000000, Vector(50000000, 100000000000, 81600000), Vector(0, 0, 0), Vector(750000, 750000, 750000), Material::Diffuse)); + spheres_.push_back(Sphere(100000000000, Vector(50000000, -99918400000, 81600000), Vector(0, 0, 0), Vector(750000, 750000, 750000), Material::Diffuse)); + + // Initialize the reflective sphere and the light source + spheres_.push_back(Sphere(16500000, Vector(27000000, 16500000, 47000000), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + spheres_.push_back(Sphere(600000000, Vector(50000000, 681330000, 81600000), Vector(12000000, 12000000, 12000000), Vector(0, 0, 0), Material::Diffuse)); + //spheres_.push_back(Sphere(16500000, Vector(73000000, 16500000, 78000000), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Refractive)); + // NOTE: this last line is commented in the original code, keeping it 1:1 + + // Ethereum logo front triangles + triangles_.push_back(Triangle(Vector(56500000, 25740000, 78000000), Vector(73000000, 25740000, 94500000), Vector(73000000, 49500000, 78000000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(56500000, 23760000, 78000000), Vector(73000000, 0, 78000000), Vector(73000000, 23760000, 94500000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(89500000, 25740000, 78000000), Vector(73000000, 49500000, 78000000), Vector(73000000, 25740000, 94500000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(89500000, 23760000, 78000000), Vector(73000000, 23760000, 94500000), Vector(73000000, 0, 78000000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + + // Ethereum logo back triangles + triangles_.push_back(Triangle(Vector(56500000, 25740000, 78000000), Vector(73000000, 49500000, 78000000), Vector(73000000, 25740000, 61500000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(56500000, 23760000, 78000000), Vector(73000000, 23760000, 61500000), Vector(73000000, 0, 78000000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(89500000, 25740000, 78000000), Vector(73000000, 25740000, 61500000), Vector(73000000, 49500000, 78000000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(89500000, 23760000, 78000000), Vector(73000000, 0, 78000000), Vector(73000000, 23760000, 61500000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + + // Ethereum logo middle rectangles + triangles_.push_back(Triangle(Vector(56500000, 25740000, 78000000), Vector(73000000, 25740000, 61500000), Vector(89500000, 25740000, 78000000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(56500000, 25740000, 78000000), Vector(89500000, 25740000, 78000000), Vector(73000000, 25740000, 94500000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(56500000, 23760000, 78000000), Vector(89500000, 23760000, 78000000), Vector(73000000, 23760000, 61500000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(56500000, 23760000, 78000000), Vector(73000000, 23760000, 94500000), Vector(89500000, 23760000, 78000000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + + // Calculate all the triangle surface normals + for (std::size_t i = 0; i < triangles_.size(); i++) { // NOTE: original code uses int for i + Triangle& tri = triangles_[i]; + Vector& triA = std::get<0>(tri); + Vector& triB = std::get<1>(tri); + Vector& triC = std::get<2>(tri); + Vector& triNor = std::get<3>(tri); + triNor = norm(cross(sub(triB, triA), sub(triC, triA))); + } + + // Commit and register vars and functions + width_.commit(); + height_.commit(); + camera_.commit(); + deltaX_.commit(); + deltaY_.commit(); + spheres_.commit(); + triangles_.commit(); + registerContractFunctions(); + width_.enableRegister(); + height_.enableRegister(); + camera_.enableRegister(); + deltaX_.enableRegister(); + deltaY_.enableRegister(); + spheres_.enableRegister(); + triangles_.enableRegister(); +} + +SnailTracer::SnailTracer( + const Address& address, + const DB& db +) : DynamicContract(address, db) { + this->width_ = IntConv::bytesToInt256(db.get(std::string("width_"), this->getDBPrefix())); + this->height_ = IntConv::bytesToInt256(db.get(std::string("height_"), this->getDBPrefix())); + this->camera_ = std::get<0>(ABI::Decoder::decodeData(db.get(std::string("camera_"), this->getDBPrefix()))); + this->deltaX_ = std::get<0>(ABI::Decoder::decodeData(db.get(std::string("deltaX_"), this->getDBPrefix()))); + this->deltaY_ = std::get<0>(ABI::Decoder::decodeData(db.get(std::string("deltaY_"), this->getDBPrefix()))); + this->spheres_ = std::get<0>(ABI::Decoder::decodeData>(db.get(std::string("spheres_"), this->getDBPrefix()))); + this->triangles_ = std::get<0>(ABI::Decoder::decodeData>(db.get(std::string("triangles_"), this->getDBPrefix()))); + + width_.commit(); + height_.commit(); + camera_.commit(); + deltaX_.commit(); + deltaY_.commit(); + spheres_.commit(); + triangles_.commit(); + registerContractFunctions(); + width_.enableRegister(); + height_.enableRegister(); + camera_.enableRegister(); + deltaX_.enableRegister(); + deltaY_.enableRegister(); + spheres_.enableRegister(); + triangles_.enableRegister(); +} + +SnailTracer::~SnailTracer() {}; + +std::tuple SnailTracer::TracePixel(const int256_t& x, const int256_t& y, const uint256_t& spp) { + Vector color = trace(x, y, spp); + auto& [colorX, colorY, colorZ] = color; + return std::make_tuple(uint8_t(colorX), uint8_t(colorY), uint8_t(colorZ)); +} + +Bytes SnailTracer::TraceScanline(const int256_t& y, const int256_t& spp) { + for (int256_t x = 0; x < width_.get(); x++) { + Vector color = trace(x, y, spp); + auto& [colorX, colorY, colorZ] = color; + buffer_.push_back(uint8_t(colorX)); + buffer_.push_back(uint8_t(colorY)); + buffer_.push_back(uint8_t(colorZ)); + } + return buffer_.get(); +} + +Bytes SnailTracer::TraceImage(const int256_t& spp) { + for (int256_t y = height_.get() - 1; y >= 0; y--) { + for (int256_t x = 0; x < width_.get(); x++) { + Vector color = trace(x, y, spp); + auto& [colorX, colorY, colorZ] = color; + buffer_.push_back(uint8_t(colorX)); + buffer_.push_back(uint8_t(colorY)); + buffer_.push_back(uint8_t(colorZ)); + } + } + return buffer_.get(); +} + +std::tuple SnailTracer::Benchmark() { + // Configure scene for benchmarking + width_ = 1024; height_ = 768; + deltaX_ = Vector(width_.get() * 513500 / height_.get(), 0, 0); + deltaY_ = div(mul(norm(cross(deltaX_.raw(), get<1>(camera_))), 513500), 1000); // camera.direction + // Trace a few pixels and collect their colors (sanity check) + Vector color; + color = add(color, trace(512, 384, 8)); // Flat diffuse surface, opposite wall + color = add(color, trace(325, 540, 8)); // Reflective surface mirroring left wall + color = add(color, trace(600, 600, 8)); // Refractive surface reflecting right wall + color = add(color, trace(522, 524, 8)); // Reflective surface mirroring the refractive surface reflecting the light + color = div(color, 4); + auto& [x, y, z] = color; + return std::make_tuple(uint8_t(x), uint8_t(y), uint8_t(z)); +} + +SnailTracer::Vector SnailTracer::trace(const int256_t& x, const int256_t& y, const int256_t& spp) { + seed_ = uint32_t(y * width_.get() + x); // Deterministic image irrelevant of render chunks + Vector color; + for (int256_t k = 0; k < spp; k++) { + Vector pixel = add(div(add( + mul(deltaX_.raw(), (1000000 * x + rand() % 500000) / width_.get() - 500000), + mul(deltaY_.raw(), (1000000 * y + rand() % 500000) / height_.get() - 500000) + ), 1000000), get<1>(camera_)); // camera.direction + auto ray = Ray(add(get<0>(camera_), mul(pixel, 140)), norm(pixel), 0, false); // camera.origin + color = add(color, div(radiance(ray), spp)); + } + return div(mul(clamp(color), 255), 1000000); +} + +uint32_t SnailTracer::rand() { + seed_ = 1103515245 * seed_.get() + 12345; + return seed_.get(); +} + +int256_t SnailTracer::clamp(const int256_t& x) { + if (x < 0) return 0; + if (x > 1000000) return 1000000; + return x; +} + +int256_t SnailTracer::sqrt(const int256_t& x) { + int256_t z = (x + 1) / 2; + int256_t y = x; + while (z < y) { + y = z; + z = (x/z + z) / 2; + } + return y; +} + +int256_t SnailTracer::sin(int256_t x) { + // Ensure x is between [0, 2PI) (Taylor expansion is picky with large numbers) + while (x < 0) x += 6283184; + while (x >= 6283184) x -= 6283184; + // Calculate the sin based on the Taylor series + int256_t s = 1; + int256_t n = x; + int256_t d = 1; + int256_t f = 2; + int256_t y; + while (n > d) { + y += s * n / d; + n = n * x * x / 1000000 / 1000000; + d *= f * (f + 1); + s *= -1; + f += 2; + } + return y; +} + +int256_t SnailTracer::cos(const int256_t& x) { + int256_t s = sin(x); + return sqrt(1000000000000 - s*s); +} + +int256_t SnailTracer::abs(const int256_t& x) { + if (x > 0) return x; + return -x; +} + +SnailTracer::Vector SnailTracer::add(const Vector& u, const Vector& v) { + auto& [ux, uy, uz] = u; + auto& [vx, vy, vz] = v; + return Vector(ux+vx, uy+vy, uz+vz); +} + +SnailTracer::Vector SnailTracer::sub(const Vector& u, const Vector& v) { + auto& [ux, uy, uz] = u; + auto& [vx, vy, vz] = v; + return Vector(ux-vx, uy-vy, uz-vz); +} + +SnailTracer::Vector SnailTracer::mul(const Vector& u, const Vector& v) { + auto& [ux, uy, uz] = u; + auto& [vx, vy, vz] = v; + return Vector(ux*vx, uy*vy, uz*vz); +} + +SnailTracer::Vector SnailTracer::mul(const Vector& v, const int256_t& m) { + auto& [vx, vy, vz] = v; + return Vector(m*vx, m*vy, m*vz); +} + +SnailTracer::Vector SnailTracer::div(const Vector& v, const int256_t& d) { + auto& [vx, vy, vz] = v; + return Vector(vx/d, vy/d, vz/d); +} + +int256_t SnailTracer::dot(const Vector& u, const Vector& v) { + auto& [ux, uy, uz] = u; + auto& [vx, vy, vz] = v; + return ux*vx + uy*vy + uz*vz; +} + +SnailTracer::Vector SnailTracer::cross(const Vector& u, const Vector& v) { + auto& [ux, uy, uz] = u; + auto& [vx, vy, vz] = v; + return Vector(uy*vz - uz*vy, uz*vx - ux*vz, ux*vy - uy*vx); +} + +SnailTracer::Vector SnailTracer::norm(const Vector& v) { + auto& [vx, vy, vz] = v; + int256_t length = sqrt(vx*vx + vy*vy + vz*vz); + return Vector(vx * 1000000 / length, vy * 1000000 / length, vz * 1000000 / length); +} + +SnailTracer::Vector SnailTracer::clamp(const Vector& v) { + auto& [vx, vy, vz] = v; + return Vector(clamp(vx), clamp(vy), clamp(vz)); +} + +int256_t SnailTracer::intersect(const Sphere& s, const Ray& r) { + const int256_t& sRad = std::get<0>(s); + const Vector& sPos = std::get<1>(s); + const Vector& rOri = std::get<0>(r); + const Vector& rDir = std::get<1>(r); + + Vector op = sub(sPos, rOri); + int256_t b = dot(op, rDir) / 1000000; + // Bail out if ray misses the sphere + int256_t det = b*b - dot(op, op) + sRad*sRad; + if (det < 0) return 0; + // Calculate the closer intersection point + det = sqrt(det); + if (b - det > 1000) return b - det; + if (b + det > 1000) return b + det; + return 0; +} + +int256_t SnailTracer::intersect(const Triangle& t, const Ray& r) { + const Vector& tA = std::get<0>(t); + const Vector& tB = std::get<1>(t); + const Vector& tC = std::get<2>(t); + const Vector& rOri = std::get<0>(r); + const Vector& rDir = std::get<1>(r); + + Vector e1 = sub(tB, tA); + Vector e2 = sub(tC, tA); + Vector p = cross(rDir, e2); + // Bail out if ray is parallel to the triangle + int256_t det = dot(e1, p) / 1000000; + if (det > -1000 && det < 1000) return 0; + // Calculate and test the 'u' parameter + Vector d = sub(rOri, tA); + int256_t u = dot(d, p) / det; + if (u < 0 || u > 1000000) return 0; + // Calculate and test the 'v' parameter + Vector q = cross(d, e1); + int256_t v = dot(rDir, q) / det; + if (v < 0 || u + v > 1000000) return 0; + // Calculate and return the distance + int256_t dist = dot(e2, q) / det; + if (dist < 1000) return 0; + return dist; +} + +SnailTracer::Vector SnailTracer::radiance(Ray& ray) { + // Place a limit on the depth to prevent stack overflows + auto& [rOri, rDir, rDep, rRef] = ray; + if (rDep > 10) return Vector(0,0,0); + // Find the closest object of intersection + auto [dist, p, id] = traceray(ray); + if (dist == 0) return Vector(0,0,0); + Sphere sphere; + Triangle triangle; + Vector color; + Vector emission; + if (p == Primitive::PSphere) { + sphere = spheres_[std::size_t(id)]; // NOTE: original code uses int as id + color = std::get<3>(sphere); // sphere.color + emission = std::get<2>(sphere); // sphere.emission + } else { + triangle = triangles_[std::size_t(id)]; // NOTE: original code uses int as id + color = std::get<5>(triangle); // triangle.color + emission = std::get<4>(triangle); // triangle.emission + } + // After a number of reflections, randomly stop radiance calculation + auto& [colorX, colorY, colorZ] = color; + int256_t ref = 1; + if (colorZ > ref) ref = colorZ; // original code checks Z twice instead of X, not sure if typo or intended, keeping it 1:1 + if (colorY > ref) ref = colorY; + if (colorZ > ref) ref = colorZ; + rDep++; + if (rDep > 5) { + if (rand() % 1000000 < ref) { + color = div(mul(color, 1000000), ref); + } else { + return emission; + } + } + // Calculate the primitive dependent radiance + Vector result; + if (p == Primitive::PSphere) { + result = radiance(ray, sphere, dist); + } else { + result = radiance(ray, triangle, dist); + } + return add(emission, div(mul(color, result), 1000000)); +} + +SnailTracer::Vector SnailTracer::radiance(const Ray& ray, const Sphere& obj, const int256_t& dist) { + const Vector& rOri = std::get<0>(ray); + const Vector& rDir = std::get<1>(ray); + const Vector& sPos = std::get<1>(obj); + const Material& sRef = std::get<4>(obj); + + // Calculate the sphere intersection point and normal vectors for recursion + Vector intersect = add(rOri, div(mul(rDir, dist), 1000000)); + Vector normal = norm(sub(intersect, sPos)); + if (sRef == Material::Diffuse) { // For diffuse reflectivity + if (dot(normal, rDir) >= 0) { + normal = mul(normal, -1); + } + return diffuse(ray, intersect, normal); + } else { // For specular reflectivity + return specular(ray, intersect, normal); + } +} + +SnailTracer::Vector SnailTracer::radiance(const Ray& ray, const Triangle& obj, const int256_t& dist) { + const Vector& rOri = std::get<0>(ray); + const Vector& rDir = std::get<1>(ray); + const bool& rRef = std::get<3>(ray); + const Vector& tNor = std::get<3>(obj); + + // Calculate the triangle intersection point for refraction + // We're cheating here, we don't have diffuse triangles :P + Vector intersect = add(rOri, div(mul(rDir, dist), 1000000)); + // Calculate the refractive indices based on whether we're in or out + int256_t nnt = 666666; // (1 air / 1.5 glass) + if (rRef) nnt = 1500000; // (1.5 glass / 1 air) + int256_t ddn = dot(tNor, rDir) / 1000000; + if (ddn >= 0) ddn = -ddn; + // If the angle is too shallow, all light is reflected + int256_t cos2t = 1000000000000 - nnt * nnt * (1000000000000 - ddn * ddn) / 1000000000000; + if (cos2t < 0) return specular(ray, intersect, tNor); + return refractive(ray, intersect, tNor, nnt, ddn, cos2t); +} + +SnailTracer::Vector SnailTracer::diffuse(const Ray& ray, const Vector& intersect, const Vector& normal) { + const int256_t& normalX = std::get<0>(normal); + const int256_t& rDep = std::get<2>(ray); + const bool& rRef = std::get<3>(ray); + + // Generate a random angle and distance from center + int256_t r1 = int256_t(6283184) * (rand() % 1000000) / 1000000; + int256_t r2 = rand() % 1000000; + int256_t r2s = sqrt(r2) * 1000; + // Create orthonormal coordinate frame + Vector u = (abs(normalX) > 100000) ? Vector(0, 1000000, 0) : Vector(1000000, 0, 0); + u = norm(cross(u, normal)); + Vector v = norm(cross(normal, u)); + // Generate the random reflection ray and continue path tracing + u = norm(add(add(mul(u, cos(r1) * r2s / 1000000), mul(v, sin(r1) * r2s / 1000000)), mul(normal, sqrt(1000000 - r2) * 1000))); + Ray rayy(intersect, u, rDep, rRef); + return radiance(rayy); +} + +SnailTracer::Vector SnailTracer::specular(const Ray& ray, const Vector& intersect, const Vector& normal) { + const Vector& rDir = std::get<1>(ray); + const int256_t& rDep = std::get<2>(ray); + const bool& rRef = std::get<3>(ray); + + Vector reflection = norm(sub(rDir, mul(normal, 2 * dot(normal, rDir) / 1000000))); + Ray rayy(intersect, reflection, rDep, rRef); + return radiance(rayy); +} + +SnailTracer::Vector SnailTracer::refractive( + const Ray& ray, const Vector& intersect, const Vector& normal, + const int256_t& nnt, const int256_t& ddn, const int256_t& cos2t +) { + const Vector& rDir = std::get<1>(ray); + const int256_t& rDep = std::get<2>(ray); + const bool& rRef = std::get<3>(ray); + + // Calculate the refraction rays for fresnel effects + int256_t sign = rRef ? 1 : -1; + Vector refraction = norm(div(sub(mul(rDir, nnt), mul(normal, sign * (ddn * nnt / 1000000 + sqrt(cos2t)))), 1000000)); + // Calculate the fresnel probabilities + int256_t c = (!rRef) ? 1000000 - dot(refraction, normal) / 1000000 : 1000000 + ddn; + int256_t re = 40000 + (1000000 - 40000) * c * c * c * c * c / int256_t("1000000000000000000000000000000"); + // Split a direct hit, otherwise trace only one ray + if (rDep <= 2) { + Ray ray2(intersect, refraction, rDep, !rRef); + refraction = mul(radiance(ray2), 1000000 - re); // Reuse refraction variable (lame) + refraction = add(refraction, mul(specular(ray2, intersect, normal), re)); + return div(refraction, 1000000); + } + if (rand() % 1000000 < 250000 + re / 2) { + return div(mul(specular(ray, intersect, normal), re), 250000 + re / 2); + } + Ray rayy(intersect, refraction, rDep, !rRef); + return div(mul(radiance(rayy), 1000000 - re), 750000 - re / 2); +} + +std::tuple SnailTracer::traceray(const Ray& ray) { + int256_t dist = 0; Primitive p; uint256_t id; + + // Intersect the ray with all the spheres + for (std::size_t i = 0; i < spheres_.size(); i++) { // NOTE: original code uses int for i + int256_t d = intersect(spheres_[i], ray); + if (d > 0 && (dist == 0 || d < dist)) { + dist = d; p = Primitive::PSphere; id = i; + } + } + // Intersect the ray with all the triangles + for (std::size_t i = 0; i < triangles_.size(); i++) { // NOTE: original code uses int for i + int256_t d = intersect(triangles_[i], ray); + if (d > 0 && (dist == 0 || d < dist)) { + dist = d; p = Primitive::PTriangle; id = i; + } + } + + return std::make_tuple(dist, p, id); +} + +void SnailTracer::registerContractFunctions() { + registerContract(); + this->registerMemberFunctions( + std::make_tuple("TracePixel", &SnailTracer::TracePixel, FunctionTypes::NonPayable, this), + std::make_tuple("TraceScanline", &SnailTracer::TraceScanline, FunctionTypes::NonPayable, this), + std::make_tuple("TraceImage", &SnailTracer::TraceImage, FunctionTypes::NonPayable, this), + std::make_tuple("Benchmark", &SnailTracer::Benchmark, FunctionTypes::NonPayable, this), + std::make_tuple("trace", &SnailTracer::trace, FunctionTypes::NonPayable, this), + std::make_tuple("rand", &SnailTracer::rand, FunctionTypes::NonPayable, this), + std::make_tuple("clamp", static_cast(&SnailTracer::clamp), FunctionTypes::NonPayable, this), + std::make_tuple("sqrt", &SnailTracer::sqrt, FunctionTypes::NonPayable, this), + std::make_tuple("sin", &SnailTracer::sin, FunctionTypes::NonPayable, this), + std::make_tuple("cos", &SnailTracer::cos, FunctionTypes::NonPayable, this), + std::make_tuple("abs", &SnailTracer::abs, FunctionTypes::NonPayable, this), + std::make_tuple("add", &SnailTracer::add, FunctionTypes::NonPayable, this), + std::make_tuple("sub", &SnailTracer::sub, FunctionTypes::NonPayable, this), + std::make_tuple("mul", static_cast(&SnailTracer::mul), FunctionTypes::NonPayable, this), + std::make_tuple("mul", static_cast(&SnailTracer::mul), FunctionTypes::NonPayable, this), + std::make_tuple("div", &SnailTracer::div, FunctionTypes::NonPayable, this), + std::make_tuple("dot", &SnailTracer::dot, FunctionTypes::NonPayable, this), + std::make_tuple("cross", &SnailTracer::cross, FunctionTypes::NonPayable, this), + std::make_tuple("norm", &SnailTracer::norm, FunctionTypes::NonPayable, this), + std::make_tuple("clamp", static_cast(&SnailTracer::clamp), FunctionTypes::NonPayable, this), + std::make_tuple("intersect", static_cast(&SnailTracer::intersect), FunctionTypes::NonPayable, this), + std::make_tuple("intersect", static_cast(&SnailTracer::intersect), FunctionTypes::NonPayable, this), + std::make_tuple("radiance", static_cast(&SnailTracer::radiance), FunctionTypes::NonPayable, this), + std::make_tuple("radiance", static_cast(&SnailTracer::radiance), FunctionTypes::NonPayable, this), + std::make_tuple("radiance", static_cast(&SnailTracer::radiance), FunctionTypes::NonPayable, this), + std::make_tuple("diffuse", &SnailTracer::diffuse, FunctionTypes::NonPayable, this), + std::make_tuple("specular", &SnailTracer::specular, FunctionTypes::NonPayable, this), + std::make_tuple("refractive", &SnailTracer::refractive, FunctionTypes::NonPayable, this), + std::make_tuple("traceray", &SnailTracer::traceray, FunctionTypes::NonPayable, this) + ); +} + +DBBatch SnailTracer::dump() const { + DBBatch dbBatch = BaseContract::dump(); + + dbBatch.push_back(StrConv::stringToBytes("width_"), IntConv::int256ToBytes(width_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("height_"), IntConv::int256ToBytes(height_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("camera_"), ABI::Encoder::encodeData(camera_.raw()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("deltaX_"), ABI::Encoder::encodeData(deltaX_.raw()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("deltaY_"), ABI::Encoder::encodeData(deltaY_.raw()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("spheres_"), ABI::Encoder::encodeData>(spheres_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("triangles_"), ABI::Encoder::encodeData>(triangles_.get()), this->getDBPrefix()); + + return dbBatch; +} + diff --git a/src/contract/templates/snailtracer.h b/src/contract/templates/snailtracer.h new file mode 100644 index 00000000..2a552157 --- /dev/null +++ b/src/contract/templates/snailtracer.h @@ -0,0 +1,360 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef SNAILTRACER_H +#define SNAILTRACER_H + +#include + +#include "../dynamiccontract.h" +#include "../variables/safebytes.h" +#include "../variables/safeint.h" +#include "../variables/safetuple.h" +#include "../variables/safeuint.h" +#include "../variables/safevector.h" +#include "../../utils/utils.h" // int/uint/bytes aliases + +/** + * Ray tracer contract that creates a scene and pre-calculates some constants + * that are the same throughout the path tracing procedure. + */ +class SnailTracer : public DynamicContract { + private: + /// Enum for light-altering surface types. + enum Material { Diffuse, Specular, Refractive }; + + /// Enum for primitive geometric types. + enum Primitive { PSphere, PTriangle }; + + /// Struct for a 3-axis vector (X, Y, Z). + using Vector = std::tuple; + + /// Struct for a parametric line (origin, direction, depth, refract). + using Ray = std::tuple; + + /// Struct for a physical sphere to intersect light rays with (radius, position, emission, color, reflection). + using Sphere = std::tuple; + + /// Struct for a physical triangle to intersect light rays with (a, b, c, normal, emission, color, reflection). + using Triangle = std::tuple; + + using SVector = SafeTuple; ///< SafeVar version of Vector. + using SRay = SafeTuple; ///< SafeVar version of Ray. + using SSphereArr = SafeVector; ///< SafeVar version of an array of Spheres. + using STriangleArr = SafeVector; ///< SafeVar version of an array of Triangles. + + SafeInt256_t width_; ///< Width of the image to be generated (fixed for life). + SafeInt256_t height_; ///< Height of the image to be generated (fixed for life). + SafeBytes buffer_; ///< Ephemeral buffer to accumulate image traces. + SafeUint32_t seed_; ///< Trivial linear congruential pseudo-random seed generated by rand(). + SRay camera_; ///< Camera position for image assembly. + SVector deltaX_; ///< Horizontal FoV angle increment per image pixel. + SVector deltaY_; ///< Vertical FoV angle increment per image pixel. + SSphereArr spheres_; ///< Array of spheres defining the scene to render. + STriangleArr triangles_; ///< Array of triangles defining the scene to render. + + void registerContractFunctions() override; ///< Register the contract functions. + + public: + /// The constructor argument types. + using ConstructorArguments = std::tuple; + + /** + * Constructor to be used when creating a new contract. + * @param w Width to initialize with. + * @param h Height to initialize with. + * @param address The address where the contract will be deployed. + * @param creator The address of the creator of the contract. + * @param chainId The chain where the contract wil be deployed. + */ + SnailTracer( + int256_t w, int256_t h, const Address& address, const Address& creator, const uint64_t& chainId + ); + + /** + * Constructor for loading contract from DB. + * @param address The address where the contract will be deployed. + * @param db Reference to the database object. + */ + SnailTracer(const Address& address, const DB& db); + + ~SnailTracer() override; ///< Destructor. + + /** + * Trace a single pixel of the configured image. + * Meant to be used specifically for high SPP renderings which would have a huge overhead otherwise. + * @param x The X position of the pixel to trace. + * @param y The Y position of the pixel to trace. + * @param spp The SPP (Samples Per Pixel) of the pixel to trace. + * @return The R, G and B values respectively. + */ + std::tuple TracePixel(const int256_t& x, const int256_t& y, const uint256_t& spp); + + /** + * Trace a single horizontal scanline of the configured image. + * Used for lower SPP rendering to avoid overhead of by-pixel calls. + * @param y The Y position of the line to trace. + * @param spp The SPP (Samples Per Pixel) of the line to trace. + * @return The RGB value pixel array. + */ + Bytes TraceScanline(const int256_t& y, const int256_t& spp); + + /** + * Trace an entire image of the configured scene. + * Used only for very small images and SPP values to cut down on cumulative gas and memory costs. + * @param spp The SPP (Samples Per Pixel) of the image to trace. + * @return The RGB pixel value array, top-down, left-to-right. + */ + Bytes TraceImage(const int256_t& spp); + + /** + * Set up an ephemeral image configuration and trace a select few + * hand picked pixels from it to measure execution performance. + * @return The R, G and B values respectively. + */ + std::tuple Benchmark(); + + /** + * Execute path tracing for a single pixel of the result image. + * @param x The X position of the pixel to trace. + * @param y The Y position of the pixel to trace. + * @param spp The SPP (Samples Per Pixel) of the pixel to trace. + * @return The RGB color vector normalized to [0, 256) value range. + */ + Vector trace(const int256_t& x, const int256_t& y, const int256_t& spp); + + uint32_t rand(); ///< Trivial linear congruential pseudo-random number generator. Returns a new random seed. + + /** + * Bound an int value to the allowed [0, 1] range. + * @param x The value to clamp. + * @return The clamped value. + */ + int256_t clamp(const int256_t& x); + + /** + * Calculate the square root of an int value based on the Babylonian method. + * @param x The value to calculate the square root from. + * @return The square root of the value. + */ + int256_t sqrt(const int256_t& x); + + /** + * Calculate the sine of an int value based on Taylor series expansion. + * @param x The value to calculate the sine of. + * @return The sine of the value. + */ + int256_t sin(int256_t x); + + /** + * Calculate the cosine of an int value based on sine and Pythagorean identity. + * @param x The value to calculate the cosine of. + * @return The cosine of the value. + */ + int256_t cos(const int256_t& x); + + /** + * Get the absolute value of an int. + * @param x The value to get the absolute value of. + * @return The absolute value of the value. + */ + int256_t abs(const int256_t& x); + + /** + * Add the internal values of two vectors. + * @param u The first vector to add from. + * @param v The second vector to add from. + * @return A new vector with the resulting sum of both vectors. + */ + Vector add(const Vector& u, const Vector& v); + + /** + * Subtract the internal values of two vectors. + * @param u The first vector to subtract from. + * @param v The second vector to subtract from. + * @return A new vector with the resulting subtraction of both vectors. + */ + Vector sub(const Vector& u, const Vector& v); + + /** + * Multiply the internal values of two vectors. + * @param u The first vector to multiply from. + * @param v The second vector to multiply from. + * @return A new vector with the resulting multiplication of both vectors. + */ + Vector mul(const Vector& u, const Vector& v); + + /** + * Overload of mul() that takes an integer instead of a second vector. + * @param v The vector to multiply from. + * @param m The integer to multiply from. + * @return A new vector with the resulting multiplication of the vector by the integer. + */ + Vector mul(const Vector& v, const int256_t& m); + + /** + * Divide the internal values of a vector by a given integer. + * @param v The vector to divide from. + * @param d The integer to divide from. + * @return A new vector with the resulting division of the vector by the integer. + */ + Vector div(const Vector& v, const int256_t& d); + + /** + * Calculate the dot product between two vectors. + * @param u The first vector to get the dot product from. + * @param v The second vector to get the dot product from. + * @return The dot product value as an integer. + */ + int256_t dot(const Vector& u, const Vector& v); + + /** + * Calculate the cross product between two vectors. + * @param u The first vector to get the cross product from. + * @param v The second vector to get the cross product from. + * @return A new vector with the resulting cross product from both vectors. + */ + Vector cross(const Vector& u, const Vector& v); + + /** + * Calculate the normalization for a given vector. + * @param v The vector to normalize. + * @return A normalized copy of the vector. + */ + Vector norm(const Vector& v); + + /** + * Bound a vector's values to the allowed [0, 1] range. + * @param v The vector to clamp. + * @return A clamped copy of the vector. + */ + Vector clamp(const Vector& v); + + /** + * Calculate the intersection of a ray with a sphere. + * @param s The sphere to intersect with. + * @param r The ray to intersect with. + * @return The distance until the first intersection point, or zero in case of no intersection. + */ + int256_t intersect(const Sphere& s, const Ray& r); + + /** + * Calculate the intersection of a ray with a triangle. + * @param t The triangle to intersect with. + * @param r The ray to intersect with. + * @return The distance until the first intersection point, or zero in case of no intersection. + */ + int256_t intersect(const Triangle& t, const Ray& r); + + /** + * Calculate the radiance of a ray. + * @param ray The ray to calculate the radiance from. + * @return A vector representing the radiance. + */ + Vector radiance(Ray& ray); + + /** + * Overload of radiance() for spheres. + * @param ray The ray to calculate the radiance from. + * @param obj The Sphere object to use as base for calculation. + * @param dist The distance between the ray and the object. + * @return A vector representing the radiance. + */ + Vector radiance(const Ray& ray, const Sphere& obj, const int256_t& dist); + + /** + * Overload of radiance() for triangles. + * @param ray The ray to calculate the radiance from. + * @param obj The Triangle object to use as base for calculation. + * @param dist The distance between the ray and the object. + * @return A vector representing the radiance. + */ + Vector radiance(const Ray& ray, const Triangle& obj, const int256_t& dist); + + /** + * Calculate the diffusion of a ray. + * @param ray The ray to calculate the diffusion from. + * @param intersect A vector representing an intersection. + * @param normal A vector representing the normal. + * @return A vector representing the diffusion. + */ + Vector diffuse(const Ray& ray, const Vector& intersect, const Vector& normal); + + /** + * Calculate the specular reflection of a ray. + * @param ray The ray to calculate the specular from. + * @param intersect A vector representing an intersection. + * @param normal A vector representing the normal. + * @return A vector representing the specular. + */ + Vector specular(const Ray& ray, const Vector& intersect, const Vector& normal); + + /** + * Calculate the refractive reflection of a ray. + * @param ray The ray to calculate the refractive from. + * @param intersect A vector representing an intersection. + * @param normal A vector representing the normal. + * @param nnt TODO + * @param ddn TODO + * @param cos2t TODO + * @return A vector representing the refractive. + */ + Vector refractive( + const Ray& ray, const Vector& intersect, const Vector& normal, + const int256_t& nnt, const int256_t& ddn, const int256_t& cos2t + ); + + /** + * Calculate the intersection of a ray with all the objects. + * @param ray The ray to intersect. + * @return The intersection of the ray with the closest object. + */ + std::tuple traceray(const Ray& ray); + + /// Register the contract structure. + static void registerContract() { + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{"w", "h"}, + std::make_tuple("TracePixel", &SnailTracer::TracePixel, FunctionTypes::NonPayable, std::vector{"x", "y", "spp"}), + std::make_tuple("TraceScanline", &SnailTracer::TraceScanline, FunctionTypes::NonPayable, std::vector{"y", "spp"}), + std::make_tuple("TraceImage", &SnailTracer::TraceImage, FunctionTypes::NonPayable, std::vector{"spp"}), + std::make_tuple("Benchmark", &SnailTracer::Benchmark, FunctionTypes::NonPayable, std::vector{}), + std::make_tuple("trace", &SnailTracer::trace, FunctionTypes::NonPayable, std::vector{"x", "y", "spp"}), + std::make_tuple("rand", &SnailTracer::rand, FunctionTypes::NonPayable, std::vector{}), + std::make_tuple("clamp", static_cast(&SnailTracer::clamp), FunctionTypes::NonPayable, std::vector{"x"}), + std::make_tuple("sqrt", &SnailTracer::sqrt, FunctionTypes::NonPayable, std::vector{"x"}), + std::make_tuple("sin", &SnailTracer::sin, FunctionTypes::NonPayable, std::vector{"x"}), + std::make_tuple("cos", &SnailTracer::cos, FunctionTypes::NonPayable, std::vector{"x"}), + std::make_tuple("abs", &SnailTracer::abs, FunctionTypes::NonPayable, std::vector{"x"}), + std::make_tuple("add", &SnailTracer::add, FunctionTypes::NonPayable, std::vector{"u", "v"}), + std::make_tuple("sub", &SnailTracer::sub, FunctionTypes::NonPayable, std::vector{"u", "v"}), + std::make_tuple("mul", static_cast(&SnailTracer::mul), FunctionTypes::NonPayable, std::vector{"u", "v"}), + std::make_tuple("mul", static_cast(&SnailTracer::mul), FunctionTypes::NonPayable, std::vector{"v", "m"}), + std::make_tuple("div", &SnailTracer::div, FunctionTypes::NonPayable, std::vector{"v", "d"}), + std::make_tuple("dot", &SnailTracer::dot, FunctionTypes::NonPayable, std::vector{"u", "v"}), + std::make_tuple("cross", &SnailTracer::cross, FunctionTypes::NonPayable, std::vector{"u", "v"}), + std::make_tuple("norm", &SnailTracer::norm, FunctionTypes::NonPayable, std::vector{"v"}), + std::make_tuple("clamp", static_cast(&SnailTracer::clamp), FunctionTypes::NonPayable, std::vector{"v"}), + std::make_tuple("intersect", static_cast(&SnailTracer::intersect), FunctionTypes::NonPayable, std::vector{"s", "r"}), + std::make_tuple("intersect", static_cast(&SnailTracer::intersect), FunctionTypes::NonPayable, std::vector{"t", "r"}), + std::make_tuple("radiance", static_cast(&SnailTracer::radiance), FunctionTypes::NonPayable, std::vector{"ray"}), + std::make_tuple("radiance", static_cast(&SnailTracer::radiance), FunctionTypes::NonPayable, std::vector{"ray", "obj", "dist"}), + std::make_tuple("radiance", static_cast(&SnailTracer::radiance), FunctionTypes::NonPayable, std::vector{"ray", "obj", "dist"}), + std::make_tuple("diffuse", &SnailTracer::diffuse, FunctionTypes::NonPayable, std::vector{"ray", "intersect", "normal"}), + std::make_tuple("specular", &SnailTracer::specular, FunctionTypes::NonPayable, std::vector{"ray", "intersect", "normal"}), + std::make_tuple("refractive", &SnailTracer::refractive, FunctionTypes::NonPayable, std::vector{"ray", "intersect", "normal", "nnt", "ddn", "cos2t"}), + std::make_tuple("traceray", &SnailTracer::traceray, FunctionTypes::NonPayable, std::vector{"ray"}) + ); + }); + } + + DBBatch dump() const override; ///< Dump method. +}; + +#endif // SNAILTRACER_H diff --git a/src/contract/templates/snailtraceroptimized.cpp b/src/contract/templates/snailtraceroptimized.cpp new file mode 100644 index 00000000..aef80804 --- /dev/null +++ b/src/contract/templates/snailtraceroptimized.cpp @@ -0,0 +1,528 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "snailtraceroptimized.h" + +#include "../../utils/intconv.h" +#include "../../utils/strconv.h" + +SnailTracerOptimized::SnailTracerOptimized( + int136_t w, int136_t h, const Address& address, const Address& creator, const uint64_t& chainId +) : DynamicContract("SnailTracerOptimized", address, creator, chainId), width_(w), height_(h) { + // Initialize the image and rendering parameters + camera_ = Ray(Vector(50000000, 52000000, 295600000), norm(Vector(0, -42612, -1000000)), 0, false); + deltaX_ = Vector(width_.get() * 513500 / height_.get(), 0, 0); + deltaY_ = div(mul(norm(cross(deltaX_.raw(), get<1>(camera_))), 513500), 1000000); // camera.direction + + // Initialize the scene bounding boxes + spheres_.push_back(Sphere(100000000000, Vector(100001000000, 40800000, 81600000), Vector(0, 0, 0), Vector(750000, 250000, 250000), Material::Diffuse)); + spheres_.push_back(Sphere(100000000000, Vector(-99901000000, 40800000, 81600000), Vector(0, 0, 0), Vector(250000, 250000, 750000), Material::Diffuse)); + spheres_.push_back(Sphere(100000000000, Vector(50000000, 40800000, 100000000000), Vector(0, 0, 0), Vector(750000, 750000, 750000), Material::Diffuse)); + spheres_.push_back(Sphere(100000000000, Vector(50000000, 40800000, -99830000000), Vector(0, 0, 0), Vector(0, 0, 0), Material::Diffuse)); + spheres_.push_back(Sphere(100000000000, Vector(50000000, 100000000000, 81600000), Vector(0, 0, 0), Vector(750000, 750000, 750000), Material::Diffuse)); + spheres_.push_back(Sphere(100000000000, Vector(50000000, -99918400000, 81600000), Vector(0, 0, 0), Vector(750000, 750000, 750000), Material::Diffuse)); + + // Initialize the reflective sphere and the light source + spheres_.push_back(Sphere(16500000, Vector(27000000, 16500000, 47000000), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + spheres_.push_back(Sphere(600000000, Vector(50000000, 681330000, 81600000), Vector(12000000, 12000000, 12000000), Vector(0, 0, 0), Material::Diffuse)); + //spheres_.push_back(Sphere(16500000, Vector(73000000, 16500000, 78000000), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Refractive)); + // NOTE: this last line is commented in the original code, keeping it 1:1 + + // Ethereum logo front triangles + triangles_.push_back(Triangle(Vector(56500000, 25740000, 78000000), Vector(73000000, 25740000, 94500000), Vector(73000000, 49500000, 78000000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(56500000, 23760000, 78000000), Vector(73000000, 0, 78000000), Vector(73000000, 23760000, 94500000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(89500000, 25740000, 78000000), Vector(73000000, 49500000, 78000000), Vector(73000000, 25740000, 94500000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(89500000, 23760000, 78000000), Vector(73000000, 23760000, 94500000), Vector(73000000, 0, 78000000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + + // Ethereum logo back triangles + triangles_.push_back(Triangle(Vector(56500000, 25740000, 78000000), Vector(73000000, 49500000, 78000000), Vector(73000000, 25740000, 61500000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(56500000, 23760000, 78000000), Vector(73000000, 23760000, 61500000), Vector(73000000, 0, 78000000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(89500000, 25740000, 78000000), Vector(73000000, 25740000, 61500000), Vector(73000000, 49500000, 78000000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(89500000, 23760000, 78000000), Vector(73000000, 0, 78000000), Vector(73000000, 23760000, 61500000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + + // Ethereum logo middle rectangles + triangles_.push_back(Triangle(Vector(56500000, 25740000, 78000000), Vector(73000000, 25740000, 61500000), Vector(89500000, 25740000, 78000000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(56500000, 25740000, 78000000), Vector(89500000, 25740000, 78000000), Vector(73000000, 25740000, 94500000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(56500000, 23760000, 78000000), Vector(89500000, 23760000, 78000000), Vector(73000000, 23760000, 61500000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + triangles_.push_back(Triangle(Vector(56500000, 23760000, 78000000), Vector(73000000, 23760000, 94500000), Vector(89500000, 23760000, 78000000), Vector(0, 0, 0), Vector(0, 0, 0), Vector(999000, 999000, 999000), Material::Specular)); + + // Calculate all the triangle surface normals + for (std::size_t i = 0; i < triangles_.size(); i++) { // NOTE: original code uses int for i + Triangle& tri = triangles_[i]; + Vector& triA = std::get<0>(tri); + Vector& triB = std::get<1>(tri); + Vector& triC = std::get<2>(tri); + Vector& triNor = std::get<3>(tri); + triNor = norm(cross(sub(triB, triA), sub(triC, triA))); + } + + // Commit and register vars and functions + width_.commit(); + height_.commit(); + camera_.commit(); + deltaX_.commit(); + deltaY_.commit(); + spheres_.commit(); + triangles_.commit(); + registerContractFunctions(); + width_.enableRegister(); + height_.enableRegister(); + camera_.enableRegister(); + deltaX_.enableRegister(); + deltaY_.enableRegister(); + spheres_.enableRegister(); + triangles_.enableRegister(); +} + +SnailTracerOptimized::SnailTracerOptimized( + const Address& address, + const DB& db +) : DynamicContract(address, db) { + this->width_ = IntConv::bytesToInt136(db.get(std::string("width_"), this->getDBPrefix())); + this->height_ = IntConv::bytesToInt136(db.get(std::string("height_"), this->getDBPrefix())); + this->camera_ = std::get<0>(ABI::Decoder::decodeData(db.get(std::string("camera_"), this->getDBPrefix()))); + this->deltaX_ = std::get<0>(ABI::Decoder::decodeData(db.get(std::string("deltaX_"), this->getDBPrefix()))); + this->deltaY_ = std::get<0>(ABI::Decoder::decodeData(db.get(std::string("deltaY_"), this->getDBPrefix()))); + this->spheres_ = std::get<0>(ABI::Decoder::decodeData>(db.get(std::string("spheres_"), this->getDBPrefix()))); + this->triangles_ = std::get<0>(ABI::Decoder::decodeData>(db.get(std::string("triangles_"), this->getDBPrefix()))); + + width_.commit(); + height_.commit(); + camera_.commit(); + deltaX_.commit(); + deltaY_.commit(); + spheres_.commit(); + triangles_.commit(); + registerContractFunctions(); + width_.enableRegister(); + height_.enableRegister(); + camera_.enableRegister(); + deltaX_.enableRegister(); + deltaY_.enableRegister(); + spheres_.enableRegister(); + triangles_.enableRegister(); +} + +SnailTracerOptimized::~SnailTracerOptimized() {}; + +std::tuple SnailTracerOptimized::TracePixel(const int136_t& x, const int136_t& y, const uint136_t& spp) { + Vector color = trace(x, y, spp); + auto& [colorX, colorY, colorZ] = color; + return std::make_tuple(uint8_t(colorX), uint8_t(colorY), uint8_t(colorZ)); +} + +Bytes SnailTracerOptimized::TraceScanline(const int136_t& y, const int136_t& spp) { + for (int136_t x = 0; x < width_.get(); x++) { + Vector color = trace(x, y, spp); + auto& [colorX, colorY, colorZ] = color; + buffer_.push_back(uint8_t(colorX)); + buffer_.push_back(uint8_t(colorY)); + buffer_.push_back(uint8_t(colorZ)); + } + return buffer_.get(); +} + +Bytes SnailTracerOptimized::TraceImage(const int136_t& spp) { + for (int136_t y = height_.get() - 1; y >= 0; y--) { + for (int136_t x = 0; x < width_.get(); x++) { + Vector color = trace(x, y, spp); + auto& [colorX, colorY, colorZ] = color; + buffer_.push_back(uint8_t(colorX)); + buffer_.push_back(uint8_t(colorY)); + buffer_.push_back(uint8_t(colorZ)); + } + } + return buffer_.get(); +} + +std::tuple SnailTracerOptimized::Benchmark() { + // Configure scene for benchmarking + width_ = 1024; height_ = 768; + deltaX_ = Vector(width_.get() * 513500 / height_.get(), 0, 0); + deltaY_ = div(mul(norm(cross(deltaX_.raw(), get<1>(camera_))), 513500), 1000); // camera.direction + // Trace a few pixels and collect their colors (sanity check) + Vector color; + color = add(color, trace(512, 384, 8)); // Flat diffuse surface, opposite wall + color = add(color, trace(325, 540, 8)); // Reflective surface mirroring left wall + color = add(color, trace(600, 600, 8)); // Refractive surface reflecting right wall + color = add(color, trace(522, 524, 8)); // Reflective surface mirroring the refractive surface reflecting the light + color = div(color, 4); + auto& [x, y, z] = color; + return std::make_tuple(uint8_t(x), uint8_t(y), uint8_t(z)); +} + +SnailTracerOptimized::Vector SnailTracerOptimized::trace(const int136_t& x, const int136_t& y, const int136_t& spp) { + seed_ = uint32_t(y * width_.get() + x); // Deterministic image irrelevant of render chunks + Vector color; + for (int136_t k = 0; k < spp; k++) { + Vector pixel = add(div(add( + mul(deltaX_.raw(), (1000000 * x + rand() % 500000) / width_.get() - 500000), + mul(deltaY_.raw(), (1000000 * y + rand() % 500000) / height_.get() - 500000) + ), 1000000), get<1>(camera_)); // camera.direction + auto ray = Ray(add(get<0>(camera_), mul(pixel, 140)), norm(pixel), 0, false); // camera.origin + color = add(color, div(radiance(ray), spp)); + } + return div(mul(clamp(color), 255), 1000000); +} + +uint32_t SnailTracerOptimized::rand() { + seed_ = 1103515245 * seed_.get() + 12345; + return seed_.get(); +} + +int136_t SnailTracerOptimized::clamp(const int136_t& x) { + if (x < 0) return 0; + if (x > 1000000) return 1000000; + return x; +} + +int136_t SnailTracerOptimized::sqrt(const int136_t& x) { + int136_t z = (x + 1) / 2; + int136_t y = x; + while (z < y) { + y = z; + z = (x/z + z) / 2; + } + return y; +} + +int136_t SnailTracerOptimized::sin(int136_t x) { + // Ensure x is between [0, 2PI) (Taylor expansion is picky with large numbers) + while (x < 0) x += 6283184; + while (x >= 6283184) x -= 6283184; + // Calculate the sin based on the Taylor series + int136_t s = 1; + int136_t n = x; + int136_t d = 1; + int136_t f = 2; + int136_t y; + while (n > d) { + y += s * n / d; + n = n * x * x / 1000000 / 1000000; + d *= f * (f + 1); + s *= -1; + f += 2; + } + return y; +} + +int136_t SnailTracerOptimized::cos(const int136_t& x) { + int136_t s = sin(x); + return sqrt(1000000000000 - s*s); +} + +int136_t SnailTracerOptimized::abs(const int136_t& x) { + if (x > 0) return x; + return -x; +} + +SnailTracerOptimized::Vector SnailTracerOptimized::add(const Vector& u, const Vector& v) { + auto& [ux, uy, uz] = u; + auto& [vx, vy, vz] = v; + return Vector(ux+vx, uy+vy, uz+vz); +} + +SnailTracerOptimized::Vector SnailTracerOptimized::sub(const Vector& u, const Vector& v) { + auto& [ux, uy, uz] = u; + auto& [vx, vy, vz] = v; + return Vector(ux-vx, uy-vy, uz-vz); +} + +SnailTracerOptimized::Vector SnailTracerOptimized::mul(const Vector& u, const Vector& v) { + auto& [ux, uy, uz] = u; + auto& [vx, vy, vz] = v; + return Vector(ux*vx, uy*vy, uz*vz); +} + +SnailTracerOptimized::Vector SnailTracerOptimized::mul(const Vector& v, const int136_t& m) { + auto& [vx, vy, vz] = v; + return Vector(m*vx, m*vy, m*vz); +} + +SnailTracerOptimized::Vector SnailTracerOptimized::div(const Vector& v, const int136_t& d) { + auto& [vx, vy, vz] = v; + return Vector(vx/d, vy/d, vz/d); +} + +int136_t SnailTracerOptimized::dot(const Vector& u, const Vector& v) { + auto& [ux, uy, uz] = u; + auto& [vx, vy, vz] = v; + return ux*vx + uy*vy + uz*vz; +} + +SnailTracerOptimized::Vector SnailTracerOptimized::cross(const Vector& u, const Vector& v) { + auto& [ux, uy, uz] = u; + auto& [vx, vy, vz] = v; + return Vector(uy*vz - uz*vy, uz*vx - ux*vz, ux*vy - uy*vx); +} + +SnailTracerOptimized::Vector SnailTracerOptimized::norm(const Vector& v) { + auto& [vx, vy, vz] = v; + int136_t length = sqrt(vx*vx + vy*vy + vz*vz); + return Vector(vx * 1000000 / length, vy * 1000000 / length, vz * 1000000 / length); +} + +SnailTracerOptimized::Vector SnailTracerOptimized::clamp(const Vector& v) { + auto& [vx, vy, vz] = v; + return Vector(clamp(vx), clamp(vy), clamp(vz)); +} + +int136_t SnailTracerOptimized::intersect(const Sphere& s, const Ray& r) { + const int136_t& sRad = std::get<0>(s); + const Vector& sPos = std::get<1>(s); + const Vector& rOri = std::get<0>(r); + const Vector& rDir = std::get<1>(r); + + Vector op = sub(sPos, rOri); + int136_t b = dot(op, rDir) / 1000000; + // Bail out if ray misses the sphere + int136_t det = b*b - dot(op, op) + sRad*sRad; + if (det < 0) return 0; + // Calculate the closer intersection point + det = sqrt(det); + if (b - det > 1000) return b - det; + if (b + det > 1000) return b + det; + return 0; +} + +int136_t SnailTracerOptimized::intersect(const Triangle& t, const Ray& r) { + const Vector& tA = std::get<0>(t); + const Vector& tB = std::get<1>(t); + const Vector& tC = std::get<2>(t); + const Vector& rOri = std::get<0>(r); + const Vector& rDir = std::get<1>(r); + + Vector e1 = sub(tB, tA); + Vector e2 = sub(tC, tA); + Vector p = cross(rDir, e2); + // Bail out if ray is parallel to the triangle + int136_t det = dot(e1, p) / 1000000; + if (det > -1000 && det < 1000) return 0; + // Calculate and test the 'u' parameter + Vector d = sub(rOri, tA); + int136_t u = dot(d, p) / det; + if (u < 0 || u > 1000000) return 0; + // Calculate and test the 'v' parameter + Vector q = cross(d, e1); + int136_t v = dot(rDir, q) / det; + if (v < 0 || u + v > 1000000) return 0; + // Calculate and return the distance + int136_t dist = dot(e2, q) / det; + if (dist < 1000) return 0; + return dist; +} + +SnailTracerOptimized::Vector SnailTracerOptimized::radiance(Ray& ray) { + // Place a limit on the depth to prevent stack overflows + auto& [rOri, rDir, rDep, rRef] = ray; + if (rDep > 10) return Vector(0,0,0); + // Find the closest object of intersection + auto [dist, p, id] = traceray(ray); + if (dist == 0) return Vector(0,0,0); + Sphere sphere; + Triangle triangle; + Vector color; + Vector emission; + if (p == Primitive::PSphere) { + sphere = spheres_[std::size_t(id)]; // NOTE: original code uses int as id + color = std::get<3>(sphere); // sphere.color + emission = std::get<2>(sphere); // sphere.emission + } else { + triangle = triangles_[std::size_t(id)]; // NOTE: original code uses int as id + color = std::get<5>(triangle); // triangle.color + emission = std::get<4>(triangle); // triangle.emission + } + // After a number of reflections, randomly stop radiance calculation + auto& [colorX, colorY, colorZ] = color; + int136_t ref = 1; + if (colorZ > ref) ref = colorZ; // original code checks Z twice instead of X, not sure if typo or intended, keeping it 1:1 + if (colorY > ref) ref = colorY; + if (colorZ > ref) ref = colorZ; + rDep++; + if (rDep > 5) { + if (rand() % 1000000 < ref) { + color = div(mul(color, 1000000), ref); + } else { + return emission; + } + } + // Calculate the primitive dependent radiance + Vector result; + if (p == Primitive::PSphere) { + result = radiance(ray, sphere, dist); + } else { + result = radiance(ray, triangle, dist); + } + return add(emission, div(mul(color, result), 1000000)); +} + +SnailTracerOptimized::Vector SnailTracerOptimized::radiance(const Ray& ray, const Sphere& obj, const int136_t& dist) { + const Vector& rOri = std::get<0>(ray); + const Vector& rDir = std::get<1>(ray); + const Vector& sPos = std::get<1>(obj); + const Material& sRef = std::get<4>(obj); + + // Calculate the sphere intersection point and normal vectors for recursion + Vector intersect = add(rOri, div(mul(rDir, dist), 1000000)); + Vector normal = norm(sub(intersect, sPos)); + if (sRef == Material::Diffuse) { // For diffuse reflectivity + if (dot(normal, rDir) >= 0) { + normal = mul(normal, -1); + } + return diffuse(ray, intersect, normal); + } else { // For specular reflectivity + return specular(ray, intersect, normal); + } +} + +SnailTracerOptimized::Vector SnailTracerOptimized::radiance(const Ray& ray, const Triangle& obj, const int136_t& dist) { + const Vector& rOri = std::get<0>(ray); + const Vector& rDir = std::get<1>(ray); + const bool& rRef = std::get<3>(ray); + const Vector& tNor = std::get<3>(obj); + + // Calculate the triangle intersection point for refraction + // We're cheating here, we don't have diffuse triangles :P + Vector intersect = add(rOri, div(mul(rDir, dist), 1000000)); + // Calculate the refractive indices based on whether we're in or out + int136_t nnt = 666666; // (1 air / 1.5 glass) + if (rRef) nnt = 1500000; // (1.5 glass / 1 air) + int136_t ddn = dot(tNor, rDir) / 1000000; + if (ddn >= 0) ddn = -ddn; + // If the angle is too shallow, all light is reflected + int136_t cos2t = 1000000000000 - nnt * nnt * (1000000000000 - ddn * ddn) / 1000000000000; + if (cos2t < 0) return specular(ray, intersect, tNor); + return refractive(ray, intersect, tNor, nnt, ddn, cos2t); +} + +SnailTracerOptimized::Vector SnailTracerOptimized::diffuse(const Ray& ray, const Vector& intersect, const Vector& normal) { + const int136_t& normalX = std::get<0>(normal); + const int136_t& rDep = std::get<2>(ray); + const bool& rRef = std::get<3>(ray); + + // Generate a random angle and distance from center + int136_t r1 = int136_t(6283184) * (rand() % 1000000) / 1000000; + int136_t r2 = rand() % 1000000; + int136_t r2s = sqrt(r2) * 1000; + // Create orthonormal coordinate frame + Vector u = (abs(normalX) > 100000) ? Vector(0, 1000000, 0) : Vector(1000000, 0, 0); + u = norm(cross(u, normal)); + Vector v = norm(cross(normal, u)); + // Generate the random reflection ray and continue path tracing + u = norm(add(add(mul(u, cos(r1) * r2s / 1000000), mul(v, sin(r1) * r2s / 1000000)), mul(normal, sqrt(1000000 - r2) * 1000))); + Ray rayy(intersect, u, rDep, rRef); + return radiance(rayy); +} + +SnailTracerOptimized::Vector SnailTracerOptimized::specular(const Ray& ray, const Vector& intersect, const Vector& normal) { + const Vector& rDir = std::get<1>(ray); + const int136_t& rDep = std::get<2>(ray); + const bool& rRef = std::get<3>(ray); + + Vector reflection = norm(sub(rDir, mul(normal, 2 * dot(normal, rDir) / 1000000))); + Ray rayy(intersect, reflection, rDep, rRef); + return radiance(rayy); +} + +SnailTracerOptimized::Vector SnailTracerOptimized::refractive( + const Ray& ray, const Vector& intersect, const Vector& normal, + const int136_t& nnt, const int136_t& ddn, const int136_t& cos2t +) { + const Vector& rDir = std::get<1>(ray); + const int136_t& rDep = std::get<2>(ray); + const bool& rRef = std::get<3>(ray); + + // Calculate the refraction rays for fresnel effects + int136_t sign = rRef ? 1 : -1; + Vector refraction = norm(div(sub(mul(rDir, nnt), mul(normal, sign * (ddn * nnt / 1000000 + sqrt(cos2t)))), 1000000)); + // Calculate the fresnel probabilities + int136_t c = (!rRef) ? 1000000 - dot(refraction, normal) / 1000000 : 1000000 + ddn; + int136_t re = 40000 + (1000000 - 40000) * c * c * c * c * c / int136_t("1000000000000000000000000000000"); + // Split a direct hit, otherwise trace only one ray + if (rDep <= 2) { + Ray ray2(intersect, refraction, rDep, !rRef); + refraction = mul(radiance(ray2), 1000000 - re); // Reuse refraction variable (lame) + refraction = add(refraction, mul(specular(ray2, intersect, normal), re)); + return div(refraction, 1000000); + } + if (rand() % 1000000 < 250000 + re / 2) { + return div(mul(specular(ray, intersect, normal), re), 250000 + re / 2); + } + Ray rayy(intersect, refraction, rDep, !rRef); + return div(mul(radiance(rayy), 1000000 - re), 750000 - re / 2); +} + +std::tuple SnailTracerOptimized::traceray(const Ray& ray) { + int136_t dist = 0; Primitive p; uint136_t id; + + // Intersect the ray with all the spheres + for (std::size_t i = 0; i < spheres_.size(); i++) { // NOTE: original code uses int for i + int136_t d = intersect(spheres_[i], ray); + if (d > 0 && (dist == 0 || d < dist)) { + dist = d; p = Primitive::PSphere; id = i; + } + } + // Intersect the ray with all the triangles + for (std::size_t i = 0; i < triangles_.size(); i++) { // NOTE: original code uses int for i + int136_t d = intersect(triangles_[i], ray); + if (d > 0 && (dist == 0 || d < dist)) { + dist = d; p = Primitive::PTriangle; id = i; + } + } + + return std::make_tuple(dist, p, id); +} + +void SnailTracerOptimized::registerContractFunctions() { + registerContract(); + this->registerMemberFunctions( + std::make_tuple("TracePixel", &SnailTracerOptimized::TracePixel, FunctionTypes::NonPayable, this), + std::make_tuple("TraceScanline", &SnailTracerOptimized::TraceScanline, FunctionTypes::NonPayable, this), + std::make_tuple("TraceImage", &SnailTracerOptimized::TraceImage, FunctionTypes::NonPayable, this), + std::make_tuple("Benchmark", &SnailTracerOptimized::Benchmark, FunctionTypes::NonPayable, this), + std::make_tuple("trace", &SnailTracerOptimized::trace, FunctionTypes::NonPayable, this), + std::make_tuple("rand", &SnailTracerOptimized::rand, FunctionTypes::NonPayable, this), + std::make_tuple("clamp", static_cast(&SnailTracerOptimized::clamp), FunctionTypes::NonPayable, this), + std::make_tuple("sqrt", &SnailTracerOptimized::sqrt, FunctionTypes::NonPayable, this), + std::make_tuple("sin", &SnailTracerOptimized::sin, FunctionTypes::NonPayable, this), + std::make_tuple("cos", &SnailTracerOptimized::cos, FunctionTypes::NonPayable, this), + std::make_tuple("abs", &SnailTracerOptimized::abs, FunctionTypes::NonPayable, this), + std::make_tuple("add", &SnailTracerOptimized::add, FunctionTypes::NonPayable, this), + std::make_tuple("sub", &SnailTracerOptimized::sub, FunctionTypes::NonPayable, this), + std::make_tuple("mul", static_cast(&SnailTracerOptimized::mul), FunctionTypes::NonPayable, this), + std::make_tuple("mul", static_cast(&SnailTracerOptimized::mul), FunctionTypes::NonPayable, this), + std::make_tuple("div", &SnailTracerOptimized::div, FunctionTypes::NonPayable, this), + std::make_tuple("dot", &SnailTracerOptimized::dot, FunctionTypes::NonPayable, this), + std::make_tuple("cross", &SnailTracerOptimized::cross, FunctionTypes::NonPayable, this), + std::make_tuple("norm", &SnailTracerOptimized::norm, FunctionTypes::NonPayable, this), + std::make_tuple("clamp", static_cast(&SnailTracerOptimized::clamp), FunctionTypes::NonPayable, this), + std::make_tuple("intersect", static_cast(&SnailTracerOptimized::intersect), FunctionTypes::NonPayable, this), + std::make_tuple("intersect", static_cast(&SnailTracerOptimized::intersect), FunctionTypes::NonPayable, this), + std::make_tuple("radiance", static_cast(&SnailTracerOptimized::radiance), FunctionTypes::NonPayable, this), + std::make_tuple("radiance", static_cast(&SnailTracerOptimized::radiance), FunctionTypes::NonPayable, this), + std::make_tuple("radiance", static_cast(&SnailTracerOptimized::radiance), FunctionTypes::NonPayable, this), + std::make_tuple("diffuse", &SnailTracerOptimized::diffuse, FunctionTypes::NonPayable, this), + std::make_tuple("specular", &SnailTracerOptimized::specular, FunctionTypes::NonPayable, this), + std::make_tuple("refractive", &SnailTracerOptimized::refractive, FunctionTypes::NonPayable, this), + std::make_tuple("traceray", &SnailTracerOptimized::traceray, FunctionTypes::NonPayable, this) + ); +} + +DBBatch SnailTracerOptimized::dump() const { + DBBatch dbBatch = BaseContract::dump(); + + dbBatch.push_back(StrConv::stringToBytes("width_"), IntConv::int136ToBytes(width_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("height_"), IntConv::int136ToBytes(height_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("camera_"), ABI::Encoder::encodeData(camera_.raw()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("deltaX_"), ABI::Encoder::encodeData(deltaX_.raw()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("deltaY_"), ABI::Encoder::encodeData(deltaY_.raw()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("spheres_"), ABI::Encoder::encodeData>(spheres_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("triangles_"), ABI::Encoder::encodeData>(triangles_.get()), this->getDBPrefix()); + + return dbBatch; +} + diff --git a/src/contract/templates/snailtraceroptimized.h b/src/contract/templates/snailtraceroptimized.h new file mode 100644 index 00000000..d8eae187 --- /dev/null +++ b/src/contract/templates/snailtraceroptimized.h @@ -0,0 +1,361 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef SNAILTRACEROPTIMIZED_H +#define SNAILTRACEROPTIMIZED_H + +#include + +#include "../dynamiccontract.h" +#include "../variables/safebytes.h" +#include "../variables/safeint.h" +#include "../variables/safetuple.h" +#include "../variables/safeuint.h" +#include "../variables/safevector.h" +#include "../../utils/utils.h" // bytes alias + +/** + * Variant of SnailTracer that uses int136 instead of int256. + * Any number lower than that will cause calculation errors like SIGFPE (division by zero), + * under/overflows or simply lack of bit space for results in the original contract's math logic. + */ +class SnailTracerOptimized : public DynamicContract { + private: + /// Enum for light-altering surface types. + enum Material { Diffuse, Specular, Refractive }; + + /// Enum for primitive geometric types. + enum Primitive { PSphere, PTriangle }; + + /// Struct for a 3-axis vector (X, Y, Z). + using Vector = std::tuple; + + /// Struct for a parametric line (origin, direction, depth, refract). + using Ray = std::tuple; + + /// Struct for a physical sphere to intersect light rays with (radius, position, emission, color, reflection). + using Sphere = std::tuple; + + /// Struct for a physical triangle to intersect light rays with (a, b, c, normal, emission, color, reflection). + using Triangle = std::tuple; + + using SVector = SafeTuple; ///< SafeVar version of Vector. + using SRay = SafeTuple; ///< SafeVar version of Ray. + using SSphereArr = SafeVector; ///< SafeVar version of an array of Spheres. + using STriangleArr = SafeVector; ///< SafeVar version of an array of Triangles. + + SafeInt136_t width_; ///< Width of the image to be generated (fixed for life). + SafeInt136_t height_; ///< Height of the image to be generated (fixed for life). + SafeBytes buffer_; ///< Ephemeral buffer to accumulate image traces. + SafeUint32_t seed_; ///< Trivial linear congruential pseudo-random seed generated by rand(). + SRay camera_; ///< Camera position for image assembly. + SVector deltaX_; ///< Horizontal FoV angle increment per image pixel. + SVector deltaY_; ///< Vertical FoV angle increment per image pixel. + SSphereArr spheres_; ///< Array of spheres defining the scene to render. + STriangleArr triangles_; ///< Array of triangles defining the scene to render. + + void registerContractFunctions() override; ///< Register the contract functions. + + public: + /// The constructor argument types. + using ConstructorArguments = std::tuple; + + /** + * Constructor to be used when creating a new contract. + * @param w Width to initialize with. + * @param h Height to initialize with. + * @param address The address where the contract will be deployed. + * @param creator The address of the creator of the contract. + * @param chainId The chain where the contract wil be deployed. + */ + SnailTracerOptimized( + int136_t w, int136_t h, const Address& address, const Address& creator, const uint64_t& chainId + ); + + /** + * Constructor for loading contract from DB. + * @param address The address where the contract will be deployed. + * @param db Reference to the database object. + */ + SnailTracerOptimized(const Address& address, const DB& db); + + ~SnailTracerOptimized() override; ///< Destructor. + + /** + * Trace a single pixel of the configured image. + * Meant to be used specifically for high SPP renderings which would have a huge overhead otherwise. + * @param x The X position of the pixel to trace. + * @param y The Y position of the pixel to trace. + * @param spp The SPP (Samples Per Pixel) of the pixel to trace. + * @return The R, G and B values respectively. + */ + std::tuple TracePixel(const int136_t& x, const int136_t& y, const uint136_t& spp); + + /** + * Trace a single horizontal scanline of the configured image. + * Used for lower SPP rendering to avoid overhead of by-pixel calls. + * @param y The Y position of the line to trace. + * @param spp The SPP (Samples Per Pixel) of the line to trace. + * @return The RGB value pixel array. + */ + Bytes TraceScanline(const int136_t& y, const int136_t& spp); + + /** + * Trace an entire image of the configured scene. + * Used only for very small images and SPP values to cut down on cumulative gas and memory costs. + * @param spp The SPP (Samples Per Pixel) of the image to trace. + * @return The RGB pixel value array, top-down, left-to-right. + */ + Bytes TraceImage(const int136_t& spp); + + /** + * Set up an ephemeral image configuration and trace a select few + * hand picked pixels from it to measure execution performance. + * @return The R, G and B values respectively. + */ + std::tuple Benchmark(); + + /** + * Execute path tracing for a single pixel of the result image. + * @param x The X position of the pixel to trace. + * @param y The Y position of the pixel to trace. + * @param spp The SPP (Samples Per Pixel) of the pixel to trace. + * @return The RGB color vector normalized to [0, 256) value range. + */ + Vector trace(const int136_t& x, const int136_t& y, const int136_t& spp); + + uint32_t rand(); ///< Trivial linear congruential pseudo-random number generator. Returns a new random seed. + + /** + * Bound an int value to the allowed [0, 1] range. + * @param x The value to clamp. + * @return The clamped value. + */ + int136_t clamp(const int136_t& x); + + /** + * Calculate the square root of an int value based on the Babylonian method. + * @param x The value to calculate the square root from. + * @return The square root of the value. + */ + int136_t sqrt(const int136_t& x); + + /** + * Calculate the sine of an int value based on Taylor series expansion. + * @param x The value to calculate the sine of. + * @return The sine of the value. + */ + int136_t sin(int136_t x); + + /** + * Calculate the cosine of an int value based on sine and Pythagorean identity. + * @param x The value to calculate the cosine of. + * @return The cosine of the value. + */ + int136_t cos(const int136_t& x); + + /** + * Get the absolute value of an int. + * @param x The value to get the absolute value of. + * @return The absolute value of the value. + */ + int136_t abs(const int136_t& x); + + /** + * Add the internal values of two vectors. + * @param u The first vector to add from. + * @param v The second vector to add from. + * @return A new vector with the resulting sum of both vectors. + */ + Vector add(const Vector& u, const Vector& v); + + /** + * Subtract the internal values of two vectors. + * @param u The first vector to subtract from. + * @param v The second vector to subtract from. + * @return A new vector with the resulting subtraction of both vectors. + */ + Vector sub(const Vector& u, const Vector& v); + + /** + * Multiply the internal values of two vectors. + * @param u The first vector to multiply from. + * @param v The second vector to multiply from. + * @return A new vector with the resulting multiplication of both vectors. + */ + Vector mul(const Vector& u, const Vector& v); + + /** + * Overload of mul() that takes an integer instead of a second vector. + * @param v The vector to multiply from. + * @param m The integer to multiply from. + * @return A new vector with the resulting multiplication of the vector by the integer. + */ + Vector mul(const Vector& v, const int136_t& m); + + /** + * Divide the internal values of a vector by a given integer. + * @param v The vector to divide from. + * @param d The integer to divide from. + * @return A new vector with the resulting division of the vector by the integer. + */ + Vector div(const Vector& v, const int136_t& d); + + /** + * Calculate the dot product between two vectors. + * @param u The first vector to get the dot product from. + * @param v The second vector to get the dot product from. + * @return The dot product value as an integer. + */ + int136_t dot(const Vector& u, const Vector& v); + + /** + * Calculate the cross product between two vectors. + * @param u The first vector to get the cross product from. + * @param v The second vector to get the cross product from. + * @return A new vector with the resulting cross product from both vectors. + */ + Vector cross(const Vector& u, const Vector& v); + + /** + * Calculate the normalization for a given vector. + * @param v The vector to normalize. + * @return A normalized copy of the vector. + */ + Vector norm(const Vector& v); + + /** + * Bound a vector's values to the allowed [0, 1] range. + * @param v The vector to clamp. + * @return A clamped copy of the vector. + */ + Vector clamp(const Vector& v); + + /** + * Calculate the intersection of a ray with a sphere. + * @param s The sphere to intersect with. + * @param r The ray to intersect with. + * @return The distance until the first intersection point, or zero in case of no intersection. + */ + int136_t intersect(const Sphere& s, const Ray& r); + + /** + * Calculate the intersection of a ray with a triangle. + * @param t The triangle to intersect with. + * @param r The ray to intersect with. + * @return The distance until the first intersection point, or zero in case of no intersection. + */ + int136_t intersect(const Triangle& t, const Ray& r); + + /** + * Calculate the radiance of a ray. + * @param ray The ray to calculate the radiance from. + * @return A vector representing the radiance. + */ + Vector radiance(Ray& ray); + + /** + * Overload of radiance() for spheres. + * @param ray The ray to calculate the radiance from. + * @param obj The Sphere object to use as base for calculation. + * @param dist The distance between the ray and the object. + * @return A vector representing the radiance. + */ + Vector radiance(const Ray& ray, const Sphere& obj, const int136_t& dist); + + /** + * Overload of radiance() for triangles. + * @param ray The ray to calculate the radiance from. + * @param obj The Triangle object to use as base for calculation. + * @param dist The distance between the ray and the object. + * @return A vector representing the radiance. + */ + Vector radiance(const Ray& ray, const Triangle& obj, const int136_t& dist); + + /** + * Calculate the diffusion of a ray. + * @param ray The ray to calculate the diffusion from. + * @param intersect A vector representing an intersection. + * @param normal A vector representing the normal. + * @return A vector representing the diffusion. + */ + Vector diffuse(const Ray& ray, const Vector& intersect, const Vector& normal); + + /** + * Calculate the specular reflection of a ray. + * @param ray The ray to calculate the specular from. + * @param intersect A vector representing an intersection. + * @param normal A vector representing the normal. + * @return A vector representing the specular. + */ + Vector specular(const Ray& ray, const Vector& intersect, const Vector& normal); + + /** + * Calculate the refractive reflection of a ray. + * @param ray The ray to calculate the refractive from. + * @param intersect A vector representing an intersection. + * @param normal A vector representing the normal. + * @param nnt TODO + * @param ddn TODO + * @param cos2t TODO + * @return A vector representing the refractive. + */ + Vector refractive( + const Ray& ray, const Vector& intersect, const Vector& normal, + const int136_t& nnt, const int136_t& ddn, const int136_t& cos2t + ); + + /** + * Calculate the intersection of a ray with all the objects. + * @param ray The ray to intersect. + * @return The intersection of the ray with the closest object. + */ + std::tuple traceray(const Ray& ray); + + /// Register the contract structure. + static void registerContract() { + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{"w", "h"}, + std::make_tuple("TracePixel", &SnailTracerOptimized::TracePixel, FunctionTypes::NonPayable, std::vector{"x", "y", "spp"}), + std::make_tuple("TraceScanline", &SnailTracerOptimized::TraceScanline, FunctionTypes::NonPayable, std::vector{"y", "spp"}), + std::make_tuple("TraceImage", &SnailTracerOptimized::TraceImage, FunctionTypes::NonPayable, std::vector{"spp"}), + std::make_tuple("Benchmark", &SnailTracerOptimized::Benchmark, FunctionTypes::NonPayable, std::vector{}), + std::make_tuple("trace", &SnailTracerOptimized::trace, FunctionTypes::NonPayable, std::vector{"x", "y", "spp"}), + std::make_tuple("rand", &SnailTracerOptimized::rand, FunctionTypes::NonPayable, std::vector{}), + std::make_tuple("clamp", static_cast(&SnailTracerOptimized::clamp), FunctionTypes::NonPayable, std::vector{"x"}), + std::make_tuple("sqrt", &SnailTracerOptimized::sqrt, FunctionTypes::NonPayable, std::vector{"x"}), + std::make_tuple("sin", &SnailTracerOptimized::sin, FunctionTypes::NonPayable, std::vector{"x"}), + std::make_tuple("cos", &SnailTracerOptimized::cos, FunctionTypes::NonPayable, std::vector{"x"}), + std::make_tuple("abs", &SnailTracerOptimized::abs, FunctionTypes::NonPayable, std::vector{"x"}), + std::make_tuple("add", &SnailTracerOptimized::add, FunctionTypes::NonPayable, std::vector{"u", "v"}), + std::make_tuple("sub", &SnailTracerOptimized::sub, FunctionTypes::NonPayable, std::vector{"u", "v"}), + std::make_tuple("mul", static_cast(&SnailTracerOptimized::mul), FunctionTypes::NonPayable, std::vector{"u", "v"}), + std::make_tuple("mul", static_cast(&SnailTracerOptimized::mul), FunctionTypes::NonPayable, std::vector{"v", "m"}), + std::make_tuple("div", &SnailTracerOptimized::div, FunctionTypes::NonPayable, std::vector{"v", "d"}), + std::make_tuple("dot", &SnailTracerOptimized::dot, FunctionTypes::NonPayable, std::vector{"u", "v"}), + std::make_tuple("cross", &SnailTracerOptimized::cross, FunctionTypes::NonPayable, std::vector{"u", "v"}), + std::make_tuple("norm", &SnailTracerOptimized::norm, FunctionTypes::NonPayable, std::vector{"v"}), + std::make_tuple("clamp", static_cast(&SnailTracerOptimized::clamp), FunctionTypes::NonPayable, std::vector{"v"}), + std::make_tuple("intersect", static_cast(&SnailTracerOptimized::intersect), FunctionTypes::NonPayable, std::vector{"s", "r"}), + std::make_tuple("intersect", static_cast(&SnailTracerOptimized::intersect), FunctionTypes::NonPayable, std::vector{"t", "r"}), + std::make_tuple("radiance", static_cast(&SnailTracerOptimized::radiance), FunctionTypes::NonPayable, std::vector{"ray"}), + std::make_tuple("radiance", static_cast(&SnailTracerOptimized::radiance), FunctionTypes::NonPayable, std::vector{"ray", "obj", "dist"}), + std::make_tuple("radiance", static_cast(&SnailTracerOptimized::radiance), FunctionTypes::NonPayable, std::vector{"ray", "obj", "dist"}), + std::make_tuple("diffuse", &SnailTracerOptimized::diffuse, FunctionTypes::NonPayable, std::vector{"ray", "intersect", "normal"}), + std::make_tuple("specular", &SnailTracerOptimized::specular, FunctionTypes::NonPayable, std::vector{"ray", "intersect", "normal"}), + std::make_tuple("refractive", &SnailTracerOptimized::refractive, FunctionTypes::NonPayable, std::vector{"ray", "intersect", "normal", "nnt", "ddn", "cos2t"}), + std::make_tuple("traceray", &SnailTracerOptimized::traceray, FunctionTypes::NonPayable, std::vector{"ray"}) + ); + }); + } + + DBBatch dump() const override; ///< Dump method. +}; + +#endif // SNAILTRACEROPTIMIZED_H diff --git a/src/contract/templates/standards/erc20.cpp b/src/contract/templates/standards/erc20.cpp new file mode 100644 index 00000000..71d6b062 --- /dev/null +++ b/src/contract/templates/standards/erc20.cpp @@ -0,0 +1,282 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "erc20.h" + +#include "../../../utils/uintconv.h" +#include "../../../utils/strconv.h" + +ERC20::ERC20(const Address& address, const DB& db) +: DynamicContract(address, db), name_(this), symbol_(this), decimals_(this), + totalSupply_(this), balances_(this), allowed_(this) + #ifndef BUILD_TESTNET + ,counter_(this), values_(this), addresses_(this) + #endif +{ + this->name_ = StrConv::bytesToString(db.get(std::string("name_"), this->getDBPrefix())); + this->symbol_ = StrConv::bytesToString(db.get(std::string("symbol_"), this->getDBPrefix())); + this->decimals_ = UintConv::bytesToUint8(db.get(std::string("decimals_"), this->getDBPrefix())); + this->totalSupply_ = UintConv::bytesToUint256(db.get(std::string("totalSupply_"), this->getDBPrefix())); + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("balances_"))) { + this->balances_[Address(dbEntry.key)] = Utils::fromBigEndian(dbEntry.value); + } + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("allowed_"))) { + View key(dbEntry.key); + Address owner(key.subspan(0,20)); + Address spender(key.subspan(20)); + this->allowed_[owner][spender] = UintConv::bytesToUint256(dbEntry.value); + } + + this->name_.commit(); + this->symbol_.commit(); + this->decimals_.commit(); + this->totalSupply_.commit(); + this->balances_.commit(); + this->allowed_.commit(); + #ifndef BUILD_TESTNET + this->counter_.commit(); + this->values_.commit(); + this->addresses_.commit(); + #endif + + this->registerContractFunctions(); + + this->name_.enableRegister(); + this->symbol_.enableRegister(); + this->decimals_.enableRegister(); + this->totalSupply_.enableRegister(); + this->balances_.enableRegister(); + this->allowed_.enableRegister(); + #ifndef BUILD_TESTNET + this->counter_.enableRegister(); + this->values_.enableRegister(); + this->addresses_.enableRegister(); + #endif +} + +ERC20::ERC20( + const std::string& erc20name_, const std::string& erc20symbol_, + const uint8_t& erc20decimals_, const uint256_t& mintValue, + const Address& address, const Address& creator, const uint64_t& chainId +) : DynamicContract("ERC20", address, creator, chainId), + name_(this), symbol_(this), decimals_(this), totalSupply_(this), balances_(this), allowed_(this) + #ifndef BUILD_TESTNET + , counter_(this), values_(this), addresses_(this) + #endif +{ + this->name_ = erc20name_; + this->symbol_ = erc20symbol_; + this->decimals_ = erc20decimals_; + this->mintValue_(creator, mintValue); + + this->name_.commit(); + this->symbol_.commit(); + this->decimals_.commit(); + this->totalSupply_.commit(); + this->balances_.commit(); + this->allowed_.commit(); + #ifndef BUILD_TESTNET + this->counter_.commit(); + this->values_.commit(); + this->addresses_.commit(); + #endif + + this->registerContractFunctions(); + + this->name_.enableRegister(); + this->symbol_.enableRegister(); + this->decimals_.enableRegister(); + this->totalSupply_.enableRegister(); + this->balances_.enableRegister(); + this->allowed_.enableRegister(); + #ifndef BUILD_TESTNET + this->counter_.enableRegister(); + this->values_.enableRegister(); + this->addresses_.enableRegister(); + #endif +} + +ERC20::ERC20( + const std::string &derivedTypeName, const std::string& erc20name_, const std::string& erc20symbol_, + const uint8_t& erc20decimals_, const uint256_t& mintValue, + const Address& address, const Address& creator, const uint64_t& chainId +) : DynamicContract(derivedTypeName, address, creator, chainId), + name_(this), symbol_(this), decimals_(this), totalSupply_(this), balances_(this), allowed_(this) + #ifndef BUILD_TESTNET + , counter_(this), values_(this), addresses_(this) + #endif +{ + this->name_ = erc20name_; + this->symbol_ = erc20symbol_; + this->decimals_ = erc20decimals_; + this->mintValue_(creator, mintValue); + + this->name_.commit(); + this->symbol_.commit(); + this->decimals_.commit(); + this->totalSupply_.commit(); + this->balances_.commit(); + this->allowed_.commit(); + #ifndef BUILD_TESTNET + this->counter_.commit(); + this->values_.commit(); + this->addresses_.commit(); + #endif + + this->registerContractFunctions(); + + this->name_.enableRegister(); + this->symbol_.enableRegister(); + this->decimals_.enableRegister(); + this->totalSupply_.enableRegister(); + this->balances_.enableRegister(); + this->allowed_.enableRegister(); + #ifndef BUILD_TESTNET + this->counter_.enableRegister(); + this->values_.enableRegister(); + this->addresses_.enableRegister(); + #endif +} + +void ERC20::registerContractFunctions() { + registerContract(); + // We need to register the member functions separately + // because ERC20 derives from multiple standard interfaces + // IERC20Metadata + this->registerMemberFunctions( + std::make_tuple("name", &ERC20::name, FunctionTypes::View, this), + std::make_tuple("symbol", &ERC20::symbol, FunctionTypes::View, this), + std::make_tuple("decimals", &ERC20::decimals, FunctionTypes::View, this) + ); + // IERC20 + this->registerMemberFunctions( + std::make_tuple("totalSupply", &ERC20::totalSupply, FunctionTypes::View, this), + std::make_tuple("balanceOf", &ERC20::balanceOf, FunctionTypes::View, this), + std::make_tuple("allowance", &ERC20::allowance, FunctionTypes::View, this), + std::make_tuple("transfer", &ERC20::transfer, FunctionTypes::NonPayable, this), + std::make_tuple("approve", &ERC20::approve, FunctionTypes::NonPayable, this), + std::make_tuple("transferFrom", &ERC20::transferFrom, FunctionTypes::NonPayable, this) + ); + #ifndef BUILD_TESTNET + this->registerMemberFunctions( + std::make_tuple("generate", &ERC20::generate, FunctionTypes::NonPayable, this), + std::make_tuple("addall", &ERC20::addall, FunctionTypes::NonPayable, this) + ); + #endif +} + +#ifndef BUILD_TESTNET + +void ERC20::generate(const std::vector
&addresses) { + this->counter_ = addresses.size(); + for (const auto& address : addresses) { + this->values_[this->counter_.get()][address] = 0; + this->addresses_.push_back(address); + } + return; +} + +void ERC20::addall() { + for (uint64_t i = 0; i < this->counter_.get(); i++) { + // Use this->address_.at() CONST, we need to enforce constness + this->values_[i][std::as_const(this->addresses_).at(i)] += 1; + } + return; +} + +#endif + +void ERC20::mintValue_(const Address& address, const uint256_t& value) { + balances_[address] += value; + totalSupply_ += value; + // TODO: Allow contract events during constructor, mintValue_ is called during constructor. + // this->Transfer(Address(), address, value); +} + +void ERC20::mint_(const Address& address, const uint256_t& value) { + balances_[address] += value; + totalSupply_ += value; + this->Transfer(Address(), address, value); +} + +void ERC20::burnValue_(const Address& address, const uint256_t& value) { + balances_[address] -= value; + totalSupply_ -= value; + this->Transfer(address, Address(), value); +} + +std::string ERC20::name() const { return this->name_.get(); } + +std::string ERC20::symbol() const { return this->symbol_.get(); } + +uint8_t ERC20::decimals() const { return this->decimals_.get(); } + +uint256_t ERC20::totalSupply() const { return this->totalSupply_.get(); } + +uint256_t ERC20::balanceOf(const Address& owner) const { + const auto& it = std::as_const(this->balances_).find(owner); + return (it == this->balances_.cend()) ? 0 : it->second; +} + +bool ERC20::transfer(const Address &to, const uint256_t &value) { + this->balances_[this->getCaller()] -= value; + this->balances_[to] += value; + this->Transfer(this->getCaller(), to, value); + return true; +} + +bool ERC20::approve(const Address &spender, const uint256_t &value) { + this->allowed_[this->getCaller()][spender] = value; + this->Approval(this->getCaller(), spender, value); + return true; +} + +uint256_t ERC20::allowance(const Address& owner, const Address& spender) const { + uint256_t ret = 0; + if (const auto& it = std::as_const(this->allowed_).find(owner); it != this->allowed_.cend()) { + if (const auto& it2 = it->second.find(spender); it2 != it->second.cend()) ret = it2->second; + } + return ret; +} + +bool ERC20::transferFrom( + const Address &from, const Address &to, const uint256_t &value +) { + this->allowed_[from][this->getCaller()] -= value; + this->balances_[from] -= value; + this->balances_[to] += value; + this->Transfer(from, to, value); + return true; +} + +DBBatch ERC20::dump() const +{ + DBBatch dbBatch = BaseContract::dump(); + + // Name, Symbol, Decimals, Total Supply + dbBatch.push_back(StrConv::stringToBytes("name_"), StrConv::stringToBytes(name_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("symbol_"), StrConv::stringToBytes(symbol_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("decimals_"), UintConv::uint8ToBytes(decimals_.get()), this->getDBPrefix()); + dbBatch.push_back(StrConv::stringToBytes("totalSupply_"), UintConv::uint256ToBytes(totalSupply_.get()), this->getDBPrefix()); + // Balances + for (auto it = balances_.cbegin(); it != balances_.cend(); ++it) { + const auto& key = it->first; + Bytes value = Utils::uintToBytes(it->second); + dbBatch.push_back(key, value, this->getNewPrefix("balances_")); + } + // Allowed + for (auto i = allowed_.cbegin(); i != allowed_.cend(); ++i) { + for (auto j = i->second.cbegin(); j != i->second.cend(); ++j) { + // Key = Address + Address, Value = uint256_t + auto key = i->first.asBytes(); + Utils::appendBytes(key, j->first.asBytes()); + dbBatch.push_back(key, UintConv::uint256ToBytes(j->second), this->getNewPrefix("allowed_")); + } + } + return dbBatch; +} + diff --git a/src/contract/templates/erc20.h b/src/contract/templates/standards/erc20.h similarity index 57% rename from src/contract/templates/erc20.h rename to src/contract/templates/standards/erc20.h index 1e15a073..e68efc63 100644 --- a/src/contract/templates/erc20.h +++ b/src/contract/templates/standards/erc20.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -10,17 +10,17 @@ See the LICENSE.txt file in the project root for more information. #include -#include "../../utils/contractreflectioninterface.h" -#include "../../utils/db.h" -#include "../../utils/utils.h" -#include "../abi.h" -#include "../dynamiccontract.h" -#include "../variables/safestring.h" -#include "../variables/safeuint.h" -#include "../variables/safeunorderedmap.h" +#include "../../../utils/db.h" +#include "../../../utils/utils.h" +#include "../../abi.h" +#include "../../dynamiccontract.h" +#include "../../variables/safestring.h" +#include "../../variables/safeuint.h" +#include "../../variables/safeunorderedmap.h" +#include "contract/variables/safevector.h" /// Template for an ERC20 contract. -class ERC20 : public DynamicContract { +class ERC20 : virtual public DynamicContract { protected: /// Solidity: string internal name_; SafeString name_; @@ -38,16 +38,26 @@ class ERC20 : public DynamicContract { SafeUnorderedMap balances_; /// Solidity: mapping(address => mapping(address => uint256)) internal allowed_; - SafeUnorderedMap> allowed_; + SafeUnorderedMap> allowed_; /** * Mint new tokens and assign them to the specified address. * Updates both balance of the address and total supply of the ERC-20 token. * @param address The account that will receive the created tokens. * @param value The amount of tokens that will be created. + * OBS: DOES NOT EMIT A EVENT */ void mintValue_(const Address& address, const uint256_t& value); + /** + * Mint new tokens and assign them to the specified address. + * Updates both balance of the address and total supply of the ERC-20 token. + * @param address The account that will receive the created tokens. + * @param value The amount of tokens that will be created. + * Same as mintValue_, but EMITS A EVENT, used outside the constructor + */ + void mint_(const Address& address, const uint256_t& value); + /** * Burn tokens from the specified address. * Updates both balance of the address and total supply of the ERC-20 token. @@ -59,22 +69,26 @@ class ERC20 : public DynamicContract { /// Function for calling the register functions for contracts. void registerContractFunctions() override; + + #ifndef BUILD_TESTNET + SafeUnorderedMap> values_; + SafeVector
addresses_; + SafeUint64_t counter_; + #endif + public: /** - * ConstructorArguments is a tuple of the contract constructor arguments in the order they appear in the constructor. - */ + * ConstructorArguments is a tuple of the contract constructor arguments in the order they appear in the constructor. + */ using ConstructorArguments = std::tuple; + /** * Constructor for loading contract from DB. - * @param interface Reference to the contract manager interface. * @param address The address where the contract will be deployed. * @param db Reference to the database object. - */ - ERC20( - ContractManagerInterface& interface, - const Address& address, const std::unique_ptr& db - ); + */ + ERC20(const Address& address, const DB& db); /** * Constructor to be used when creating a new contract. @@ -82,18 +96,14 @@ class ERC20 : public DynamicContract { * @param erc20symbol The symbol of the ERC20 token. * @param erc20decimals The decimals of the ERC20 token. * @param mintValue The amount of tokens that will be minted. - * @param interface Reference to the contract manager interface. * @param address The address where the contract will be deployed. * @param creator The address of the creator of the contract. * @param chainId The chain where the contract wil be deployed. - * @param db Reference to the database object. */ ERC20( const std::string &erc20name, const std::string &erc20symbol, const uint8_t &erc20decimals, const uint256_t &mintValue, - ContractManagerInterface &interface, - const Address &address, const Address &creator, const uint64_t &chainId, - const std::unique_ptr &db + const Address &address, const Address &creator, const uint64_t &chainId ); /// Constructor for derived types! @@ -101,13 +111,29 @@ class ERC20 : public DynamicContract { const std::string &derivedTypeName, const std::string &erc20name, const std::string &erc20symbol, const uint8_t &erc20decimals, const uint256_t &mintValue, - ContractManagerInterface &interface, - const Address &address, const Address &creator, const uint64_t &chainId, - const std::unique_ptr &db + const Address &address, const Address &creator, const uint64_t &chainId ); /// Destructor. - ~ERC20() override; + ~ERC20() override = default; + + + /// Event for when a token is transferred. + /// event Transfer(address indexed from, address indexed to, uint256 value); + void Transfer(const EventParam& from, const EventParam& to, const EventParam& value) { + this->emitEvent("Transfer", std::make_tuple(from, to, value)); + } + + /// Event for when a token is approved. + /// event Approval(address indexed owner, address indexed spender, uint256 value); + void Approval(const EventParam& owner, const EventParam& spender, const EventParam& value) { + this->emitEvent("Approval", std::make_tuple(owner, spender, value)); + } + + #ifndef BUILD_TESTNET + void generate(const std::vector
& addresses); + void addall(); + #endif /** * Get the name of the ERC20 token. Solidity counterpart: @@ -155,7 +181,7 @@ class ERC20 : public DynamicContract { * @param to The address to transfer to. * @param value The amount to be transferred. */ - void transfer(const Address& to, const uint256_t& value); + bool transfer(const Address& to, const uint256_t& value); /** * Set the allowance of the specified address to the specified amount of ERC20 tokens. @@ -163,7 +189,7 @@ class ERC20 : public DynamicContract { * @param spender The address to approve. * @param value The amount to be approved. */ - void approve(const Address& spender, const uint256_t& value); + bool approve(const Address& spender, const uint256_t& value); /** * Get the amount which spender is still allowed to withdraw from owner. @@ -181,30 +207,39 @@ class ERC20 : public DynamicContract { * @param to The address to transfer to. * @param value The amount to be transferred. */ - void transferFrom( + bool transferFrom( const Address& from, const Address& to, const uint256_t& value ); /// Register contract class via ContractReflectionInterface. static void registerContract() { - ContractReflectionInterface::registerContractMethods< - ERC20, const std::string &, const std::string &, const uint8_t &, - const uint256_t &, ContractManagerInterface &, - const Address &, const Address &, const uint64_t &, - const std::unique_ptr & - >( - std::vector{"erc20name", "erc20symbol", "erc20decimals", "mintValue"}, - std::make_tuple("name", &ERC20::name, FunctionTypes::View, std::vector{}), - std::make_tuple("symbol", &ERC20::symbol, FunctionTypes::View, std::vector{}), - std::make_tuple("decimals", &ERC20::decimals, FunctionTypes::View, std::vector{}), - std::make_tuple("totalSupply", &ERC20::totalSupply, FunctionTypes::View, std::vector{}), - std::make_tuple("balanceOf", &ERC20::balanceOf, FunctionTypes::View, std::vector{"owner"}), - std::make_tuple("transfer", &ERC20::transfer, FunctionTypes::NonPayable, std::vector{"to", "value"}), - std::make_tuple("approve", &ERC20::approve, FunctionTypes::NonPayable, std::vector{"spender", "value"}), - std::make_tuple("allowance", &ERC20::allowance, FunctionTypes::View, std::vector{"owner", "spender"}), - std::make_tuple("transferFrom", &ERC20::transferFrom, FunctionTypes::NonPayable, std::vector{"from", "to", "value"}) - ); + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{"erc20name", "erc20symbol", "erc20decimals", "mintValue"}, + std::make_tuple("name", &ERC20::name, FunctionTypes::View, std::vector{}), + std::make_tuple("symbol", &ERC20::symbol, FunctionTypes::View, std::vector{}), + std::make_tuple("decimals", &ERC20::decimals, FunctionTypes::View, std::vector{}), + std::make_tuple("totalSupply", &ERC20::totalSupply, FunctionTypes::View, std::vector{}), + std::make_tuple("balanceOf", &ERC20::balanceOf, FunctionTypes::View, std::vector{"owner"}), + std::make_tuple("transfer", &ERC20::transfer, FunctionTypes::NonPayable, std::vector{"to", "value"}), + std::make_tuple("approve", &ERC20::approve, FunctionTypes::NonPayable, std::vector{"spender", "value"}), + std::make_tuple("allowance", &ERC20::allowance, FunctionTypes::View, std::vector{"owner", "spender"}), + #ifndef BUILD_TESTNET + std::make_tuple("generate", &ERC20::generate, FunctionTypes::NonPayable, std::vector{"addresses"}), + std::make_tuple("addall", &ERC20::addall, FunctionTypes::NonPayable, std::vector{}), + #endif + std::make_tuple("transferFrom", &ERC20::transferFrom, FunctionTypes::NonPayable, std::vector{"from", "to", "value"}) + ); + ContractReflectionInterface::registerContractEvents( + std::make_tuple("Transfer", false, &ERC20::Transfer, std::vector{"from","to", "value"}), + std::make_tuple("Approval", false, &ERC20::Approval, std::vector{"owner","spender", "value"}) + ); + }); } + + /// Dump method + DBBatch dump() const override; }; #endif /// ERC20_H diff --git a/src/contract/templates/standards/erc721.cpp b/src/contract/templates/standards/erc721.cpp new file mode 100644 index 00000000..35df4ad2 --- /dev/null +++ b/src/contract/templates/standards/erc721.cpp @@ -0,0 +1,330 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "erc721.h" + +#include "ierc721receiver.hpp" +#include "../../../utils/strconv.h" + +ERC721::ERC721(const Address& address, const DB& db +) : DynamicContract(address, db), name_(this), symbol_(this), + owners_(this), balances_(this), tokenApprovals_(this), operatorAddressApprovals_(this) +{ + this->name_ = StrConv::bytesToString(db.get(std::string("name_"), this->getDBPrefix())); + this->symbol_ = StrConv::bytesToString(db.get(std::string("symbol_"), this->getDBPrefix())); + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("owners_"))) { + View valueView(dbEntry.value); + this->owners_[Utils::fromBigEndian(dbEntry.key)] = Address(valueView.subspan(0, 20)); + } + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("balances_"))) { + this->balances_[Address(dbEntry.key)] = Utils::fromBigEndian(dbEntry.value); + } + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("tokenApprovals_"))) { + this->tokenApprovals_[Utils::fromBigEndian(dbEntry.key)] = Address(dbEntry.value); + } + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("operatorAddressApprovals_"))) { + View keyView(dbEntry.key); + Address owner(keyView.subspan(0, 20)); + Address operatorAddress(keyView.subspan(20)); + this->operatorAddressApprovals_[owner][operatorAddress] = dbEntry.value[0]; + } + + this->name_.commit(); + this->symbol_.commit(); + this->owners_.commit(); + this->balances_.commit(); + this->tokenApprovals_.commit(); + this->operatorAddressApprovals_.commit(); + + ERC721::registerContractFunctions(); + + this->name_.enableRegister(); + this->symbol_.enableRegister(); + this->owners_.enableRegister(); + this->balances_.enableRegister(); + this->tokenApprovals_.enableRegister(); + this->operatorAddressApprovals_.enableRegister(); +} + +ERC721::ERC721( + const std::string &erc721name, const std::string &erc721symbol_, + const Address &address, const Address &creator, const uint64_t &chainId +) : DynamicContract("ERC721", address, creator, chainId), name_(this, erc721name), + symbol_(this, erc721symbol_), owners_(this), balances_(this), tokenApprovals_(this), operatorAddressApprovals_(this) +{ + this->name_.commit(); + this->symbol_.commit(); + this->owners_.commit(); + this->balances_.commit(); + this->tokenApprovals_.commit(); + this->operatorAddressApprovals_.commit(); + + ERC721::registerContractFunctions(); + + this->name_.enableRegister(); + this->symbol_.enableRegister(); + this->owners_.enableRegister(); + this->balances_.enableRegister(); + this->tokenApprovals_.enableRegister(); + this->operatorAddressApprovals_.enableRegister(); +} + +ERC721::ERC721( + const std::string &derivedTypeName, + const std::string &erc721name, const std::string &erc721symbol_, + const Address &address, const Address &creator, const uint64_t &chainId +) : DynamicContract(derivedTypeName, address, creator, chainId), name_(this, erc721name), + symbol_(this, erc721symbol_), owners_(this), balances_(this), tokenApprovals_(this), operatorAddressApprovals_(this) +{ + this->name_.commit(); + this->symbol_.commit(); + this->owners_.commit(); + this->balances_.commit(); + this->tokenApprovals_.commit(); + this->operatorAddressApprovals_.commit(); + + ERC721::registerContractFunctions(); + + this->name_.enableRegister(); + this->symbol_.enableRegister(); + this->owners_.enableRegister(); + this->balances_.enableRegister(); + this->tokenApprovals_.enableRegister(); + this->operatorAddressApprovals_.enableRegister(); +} + +void ERC721::registerContractFunctions() { + ERC721::registerContract(); + // IERC721Metadata + this->registerMemberFunctions( + std::make_tuple("name", &ERC721::name, FunctionTypes::View, this), + std::make_tuple("symbol", &ERC721::symbol, FunctionTypes::View, this), + std::make_tuple("tokenURI", &ERC721::tokenURI, FunctionTypes::View, this) + ); + // IERC721 + this->registerMemberFunctions( + std::make_tuple("balanceOf", &ERC721::balanceOf, FunctionTypes::View, this), + std::make_tuple("ownerOf", &ERC721::ownerOf, FunctionTypes::View, this), + std::make_tuple("safeTransferFrom", static_cast(&ERC721::safeTransferFrom), FunctionTypes::NonPayable, this), + std::make_tuple("safeTransferFrom", static_cast(&ERC721::safeTransferFrom), FunctionTypes::NonPayable, this), + std::make_tuple("transferFrom", &ERC721::transferFrom, FunctionTypes::NonPayable, this), + std::make_tuple("approve", &ERC721::approve, FunctionTypes::NonPayable, this), + std::make_tuple("getApproved", &ERC721::getApproved, FunctionTypes::View, this), + std::make_tuple("setApprovalForAll", &ERC721::setApprovalForAll, FunctionTypes::NonPayable, this), + std::make_tuple("isApprovedForAll", &ERC721::isApprovedForAll, FunctionTypes::View, this) + ); +} + +Address ERC721::ownerOf_(const uint256_t& tokenId) const { + auto it = this->owners_.find(static_cast(tokenId)); + if (it == this->owners_.cend()) return Address(); + return it->second; +} + +Address ERC721::getApproved_(const uint256_t& tokenId) const { + auto it = this->tokenApprovals_.find(static_cast(tokenId)); + if (it == this->tokenApprovals_.cend()) return Address(); + return it->second; +} + +Address ERC721::update_(const Address& to, const uint256_t& tokenId, const Address& auth) { + Address from = this->ownerOf_(tokenId); + if (auth) { + this->checkAuthorized_(from, auth, tokenId); + } + if (from) { + this->tokenApprovals_[static_cast(tokenId)] = Address(); + this->balances_[from]--; + } + if (to) { + this->balances_[to]++; + } + this->owners_[static_cast(tokenId)] = to; + this->Transfer(from, to, tokenId); + return from; +} + +void ERC721::checkAuthorized_(const Address& owner, const Address& spender, const uint256_t& tokenId) const { + if (!this->isAuthorized_(owner, spender, tokenId)) { + if (owner) { + throw DynamicException("ERC721::checkAuthorized_: Not authorized"); + } + throw DynamicException("ERC721::checkAuthorized_: inexistent token"); + } +} + +bool ERC721::isAuthorized_(const Address& owner, const Address& spender, const uint256_t& tokenId) const { + if (spender == owner) { return true; } + if (spender == Address()) { return false; } + if (this->isApprovedForAll(owner, spender)) { return true; } + if (this->getApproved_(tokenId) == spender) { return true; } + return false; +} + +void ERC721::mint_(const Address& to, const uint256_t& tokenId) { + if (to == Address()) { + throw DynamicException("ERC721::mint_: mint to the zero address"); + } + [[maybe_unused]] Address prevOwner = this->update_(to, tokenId, Address()); +} + +void ERC721::burn_(const uint256_t& tokenId) { + Address prevOwner = this->update_(Address(), tokenId, this->getCaller()); + if (prevOwner == Address()) { + throw DynamicException("ERC721::burn_: inexistent token"); + } +} + +void ERC721::transfer_(const Address& from, const Address& to, const uint256_t& tokenId) { + if (to == Address()) { + throw DynamicException("ERC721::transfer_: transfer to the zero address"); + } + + Address prevOwner = this->update_(to, tokenId, this->getCaller()); + if (prevOwner == Address()) { + throw DynamicException("ERC721::transfer_: inexistent token"); + } else if (prevOwner != from) { + throw DynamicException("ERC721::transfer_: incorrect owner"); + } +} + +Address ERC721::approve_(const Address& to, const uint256_t& tokenId, const Address& auth) { + Address owner = this->ownerOf(tokenId); + + if (auth != Address() && owner != auth && !this->isApprovedForAll(owner, auth)) { + throw DynamicException("ERC721::approve_: Not authorized"); + } + + this->tokenApprovals_[static_cast(tokenId)] = to; + this->Approval(owner, to, tokenId); + + return owner; +} + +std::string ERC721::name() const { + return this->name_.get(); +} + +std::string ERC721::symbol() const { + return this->symbol_.get(); +} + +uint256_t ERC721::balanceOf(const Address& owner) const { + if (owner == Address()) throw DynamicException("ERC721::balanceOf: zero address"); + auto it = this->balances_.find(owner); + if (it == this->balances_.cend()) return 0; + return it->second; +} + +Address ERC721::ownerOf(const uint256_t& tokenId) const { + Address owner = this->ownerOf_(tokenId); + if (owner == Address()) throw DynamicException("ERC721::ownerOf: inexistent token"); + return owner; +} + +std::string ERC721::tokenURI(const uint256_t& tokenId) const { + this->requireMinted_(tokenId); + return this->baseURI_() + tokenId.str(); +} + +void ERC721::approve(const Address& to, const uint256_t& tokenId) { + approve_(to, tokenId, this->getCaller()); +} + +Address ERC721::getApproved(const uint256_t &tokenId) const { + this->requireMinted_(tokenId); + return this->getApproved_(tokenId); +} + +void ERC721::setApprovalForAll(const Address& operatorAddress, const bool& approved) { + this->setApprovalForAll_(this->getCaller(), operatorAddress, approved); +} + +void ERC721::setApprovalForAll_(const Address& owner, const Address& operatorAddress, bool approved) { + if (operatorAddress == Address()) { + throw DynamicException("ERC721::setApprovalForAll_: zero address"); + } + this->operatorAddressApprovals_[owner][operatorAddress] = approved; + this->ApprovalForAll(owner, operatorAddress, approved); +} + +void ERC721::requireMinted_(const uint256_t& tokenId) const { + if (this->ownerOf_(tokenId) == Address()) { + throw DynamicException("ERC721::requireMinted_: inexistent token"); + } +} + +void ERC721::checkOnERC721Received_(const Address& from, const Address& to, const uint256_t& tokenId, const Bytes& data) { + if (this->isContract(to)) { + auto result = this->callContractFunction(to, &IERC721Receiver::onERC721Received, from, to, tokenId, data); + if (result != IERC721Receiver::onERC721ReceiverSelector()) { + throw DynamicException("ERC721::checkOnERC721Received_: transfer to non ERC721Receiver implementer"); + } + } +} + +bool ERC721::isApprovedForAll(const Address& owner, const Address& operatorAddress) const { + auto it = this->operatorAddressApprovals_.find(owner); + if (it == this->operatorAddressApprovals_.cend()) return false; + auto it2 = it->second.find(operatorAddress); + if (it2 == it->second.cend()) return false; + return it2->second; +} + +void ERC721::transferFrom(const Address& from, const Address& to, const uint256_t& tokenId) { + this->transfer_(from, to, tokenId); +} + +void ERC721::safeTransferFrom(const Address& from, const Address& to, const uint256_t& tokenId, const Bytes& data) { + this->transfer_(from, to, tokenId); + this->checkOnERC721Received_(from, to, tokenId, data); +} + +void ERC721::safeTransferFrom(const Address& from, const Address& to, const uint256_t& tokenId) { + this->safeTransferFrom(from, to, tokenId, Bytes()); +} + +DBBatch ERC721::dump() const { + DBBatch dbBatch = BaseContract::dump(); + boost::unordered_flat_map data { + {"name_", StrConv::stringToBytes(name_.get())}, + {"symbol_", StrConv::stringToBytes(symbol_.get())} + }; + + for (auto it = data.cbegin(); it != data.cend(); ++it) { + dbBatch.push_back(StrConv::stringToBytes(it->first), + it->second, + this->getDBPrefix()); + } + for (auto it = owners_.cbegin(), end = owners_.cend(); it != end; ++it) { + // key: uint -> value: Address + dbBatch.push_back(Utils::uintToBytes(it->first), + it->second, + this->getNewPrefix("owners_")); + } + for (auto it = balances_.cbegin(), end = balances_.cend(); it != end; ++it) { + // key: Address -> value: uint + dbBatch.push_back(it->first, + Utils::uintToBytes(it->second), + this->getNewPrefix("balances_")); + } + for (auto it = tokenApprovals_.cbegin(), end = tokenApprovals_.cend(); it != end; ++it) { + // key: uint -> value: Address + dbBatch.push_back(Utils::uintToBytes(it->first), + it->second, + this->getNewPrefix("tokenApprovals_")); + } + for (auto i = operatorAddressApprovals_.cbegin(); i != operatorAddressApprovals_.cend(); ++i) { + for (auto j = i->second.cbegin(); j != i->second.cend(); ++j) { + // key: address + address -> bool + Bytes key = i->first.asBytes(); + Utils::appendBytes(key, j->first.asBytes()); + Bytes value = {uint8_t(j->second)}; + } + } + return dbBatch; +} diff --git a/src/contract/templates/erc721.h b/src/contract/templates/standards/erc721.h similarity index 59% rename from src/contract/templates/erc721.h rename to src/contract/templates/standards/erc721.h index 85e1ec58..536f463c 100644 --- a/src/contract/templates/erc721.h +++ b/src/contract/templates/standards/erc721.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -10,17 +10,18 @@ See the LICENSE.txt file in the project root for more information. #include -#include "../../utils/contractreflectioninterface.h" -#include "../../utils/db.h" -#include "../../utils/utils.h" -#include "../abi.h" -#include "../dynamiccontract.h" -#include "../variables/safestring.h" -#include "../variables/safeuint.h" -#include "../variables/safeunorderedmap.h" +#include "../../../utils/contractreflectioninterface.h" +#include "../../../utils/db.h" +#include "../../../utils/utils.h" +#include "../../abi.h" +#include "../../dynamiccontract.h" +#include "../../variables/safestring.h" +#include "../../variables/safeuint.h" +#include "../../variables/safeunorderedmap.h" /// Template for an ERC721 contract. -class ERC721 : public DynamicContract { +/// Based on OpenZeppelin v5.0.2 ERC721 implementation. +class ERC721 : virtual public DynamicContract { protected: /// Solidity: string internal name_; SafeString name_; @@ -29,29 +30,25 @@ class ERC721 : public DynamicContract { SafeString symbol_; /// Solidity: mapping(uint256 tokenId => address owner) internal owners_; - SafeUnorderedMap owners_; + SafeUnorderedMap owners_; /// Solidity: mapping(address => uint256) internal balances_; - SafeUnorderedMap balances_; + SafeUnorderedMap balances_; /// Solidity: mapping(uint256 => address) internal tokenApp; - SafeUnorderedMap tokenApprovals_; + SafeUnorderedMap tokenApprovals_; - /// Solidity: mapping(address => mapping(address => bool)) internal - /// operatorAddressApprovals_; - SafeUnorderedMap> - operatorAddressApprovals_; + /// Solidity: mapping(address => mapping(address => bool)) internal operatorAddressApprovals_; + SafeUnorderedMap> operatorAddressApprovals_; - /** - * Get the baseURI of the contract. - */ + /// Get the baseURI of the contract. virtual std::string baseURI_() const { return ""; } - /** Returns the owner of the Token + /** + * Returns the owner of the Token * @param tokenId The id of the token. * @return The address of the owner of the token. - * Solidity counterpart: function ownerOf_(uint256 tokenId) internal view - * virtual returns (address) + * Solidity counterpart: function ownerOf_(uint256 tokenId) internal view virtual returns (address) */ Address ownerOf_(const uint256_t &tokenId) const; @@ -59,8 +56,7 @@ class ERC721 : public DynamicContract { * Returns the approved address for tokenId * @param tokenId The id of the token. * @return The address of the approved address for tokenId. - * Solidity counterpart: function getApproved_(uint256 tokenId) internal view - * virtual returns (address) { + * Solidity counterpart: function getApproved_(uint256 tokenId) internal view virtual returns (address) */ Address getApproved_(const uint256_t &tokenId) const; @@ -75,7 +71,7 @@ class ERC721 : public DynamicContract { * update_(address to, uint256 tokenId, address auth) internal returns * (address) */ - Address update_(const Address &to, const uint256_t &tokenId, + virtual Address update_(const Address &to, const uint256_t &tokenId, const Address &auth); /** @@ -160,6 +156,11 @@ class ERC721 : public DynamicContract { */ void requireMinted_(const uint256_t &tokenId) const; + /** + * Check if the receipient can receive a ERC721 token. + */ + void checkOnERC721Received_(const Address& from, const Address& to, const uint256_t& tokenId, const Bytes& data); + void registerContractFunctions() override; public: @@ -172,46 +173,64 @@ class ERC721 : public DynamicContract { /** * Constructor for loading contract from DB. - * @param interface Reference to the contract manager interface. * @param address The address where the contract will be deployed. * @param db Reference to the database object. */ - ERC721(ContractManagerInterface &interface, const Address &address, - const std::unique_ptr &db); + ERC721(const Address &address, const DB& db); /** * Constructor to be used when creating a new contract. - * @param erc721name The name of the ERC20 token. - * @param erc721symbol The symbol of the ERC20 token. - * @param interface Reference to the contract manager interface. + * @param erc721name The name of the ERC721 token. + * @param erc721symbol The symbol of the ERC721 token. * @param address The address where the contract will be deployed. * @param creator The address of the creator of the contract. * @param chainId The chain where the contract wil be deployed. - * @param db Reference to the database object. */ ERC721(const std::string &erc721name, const std::string &erc721symbol, - ContractManagerInterface &interface, const Address &address, - const Address &creator, const uint64_t &chainId, - const std::unique_ptr &db); + const Address &address, const Address &creator, const uint64_t &chainId + ); /** * Constructor to be used when creating a new contract. * @param derivedTypeName The name of the derived type. - * @param erc721name The name of the ERC20 token. - * @param erc721symbol The symbol of the ERC20 token. - * @param interface Reference to the contract manager interface. + * @param erc721name The name of the ERC721 token. + * @param erc721symbol The symbol of the ERC721 token. * @param address The address where the contract will be deployed. * @param creator The address of the creator of the contract. * @param chainId The chain where the contract wil be deployed. - * @param db Reference to the database object. */ ERC721(const std::string &derivedTypeName, const std::string &erc721name, - const std::string &erc721symbol, ContractManagerInterface &interface, - const Address &address, const Address &creator, - const uint64_t &chainId, const std::unique_ptr &db); + const std::string &erc721symbol, const Address &address, + const Address &creator, const uint64_t &chainId + ); /// Destructor. - ~ERC721() override; + ~ERC721() override = default; + + + /// Event for when a token is transferred. + /// event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + void Transfer(const EventParam &from, + const EventParam &to, + const EventParam &tokenId) { + this->emitEvent("Transfer", std::make_tuple(from, to, tokenId)); + } + + /// Event for when a token is approved. + /// event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + void Approval(const EventParam &owner, + const EventParam &approved, + const EventParam &tokenId) { + this->emitEvent("Approval", std::make_tuple(owner, approved, tokenId)); + } + + /// Event for when an operator is approved. + /// event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + void ApprovalForAll(const EventParam &owner, + const EventParam &operatorAddress, + const EventParam &approved) { + this->emitEvent("ApprovalForAll", std::make_tuple(owner, operatorAddress, approved)); + } /** * Get the name of the ERC721 token. @@ -254,7 +273,7 @@ class ERC721 : public DynamicContract { * @param tokenId The tokenId to query the URI of. * @return The URI of the specified tokenId. */ - std::string tokenURI(const uint256_t &tokenId) const; + virtual std::string tokenURI(const uint256_t &tokenId) const; /** * Approve a token to be transferred by a third party. @@ -305,35 +324,70 @@ class ERC721 : public DynamicContract { void transferFrom(const Address &from, const Address &to, const uint256_t &tokenId); + /** + * Safely transfer a token from one address to another. + * Solidity counterpart: function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; + * @param from The address to transfer the token from. + * @param to The address to transfer the token to. + * @param tokenId The tokenId to transfer. + * @param data The data to pass to the recipient contract. + */ + void safeTransferFrom(const Address &from, const Address &to, const uint256_t &tokenId, const Bytes& data); + + /** + * Safely transfer a token from one address to another. + * Solidity counterpart: function safeTransferFrom(address from, address to, uint256 tokenId) external; + * @param from The address to transfer the token from. + * @param to The address to transfer the token to. + * @param tokenId The tokenId to transfer. + */ + void safeTransferFrom(const Address &from, const Address &to, const uint256_t &tokenId); + /// Register contract class via ContractReflectionInterface. static void registerContract() { - ContractReflectionInterface::registerContractMethods< - ERC721, const std::string &, const std::string &, - ContractManagerInterface &, const Address &, const Address &, - const uint64_t &, const std::unique_ptr &>( - std::vector{"erc721name", "erc721symbol"}, - std::make_tuple("name", &ERC721::name, FunctionTypes::View, - std::vector{}), - std::make_tuple("symbol", &ERC721::symbol, FunctionTypes::View, - std::vector{}), - std::make_tuple("balanceOf", &ERC721::balanceOf, FunctionTypes::View, - std::vector{"owner"}), - std::make_tuple("ownerOf", &ERC721::ownerOf, FunctionTypes::View, - std::vector{"tokenId"}), - std::make_tuple("tokenURI", &ERC721::tokenURI, FunctionTypes::View, - std::vector{"tokenId"}), - std::make_tuple("approve", &ERC721::approve, FunctionTypes::NonPayable, - std::vector{"to", "tokenId"}), - std::make_tuple("getApproved", &ERC721::getApproved, FunctionTypes::View, - std::vector{"tokenId"}), - std::make_tuple("setApprovalForAll", &ERC721::setApprovalForAll, - FunctionTypes::NonPayable, - std::vector{"operatorAddress", "approved"}), - std::make_tuple("isApprovedForAll", &ERC721::isApprovedForAll, FunctionTypes::View, - std::vector{"owner", "operatorAddress"}), - std::make_tuple("transferFrom", &ERC721::transferFrom, FunctionTypes::NonPayable, - std::vector{"from", "to", "tokenId"})); + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{"erc721name", "erc721symbol"}, + std::make_tuple("name", &ERC721::name, FunctionTypes::View, + std::vector{}), + std::make_tuple("symbol", &ERC721::symbol, FunctionTypes::View, + std::vector{}), + std::make_tuple("balanceOf", &ERC721::balanceOf, FunctionTypes::View, + std::vector{"owner"}), + std::make_tuple("ownerOf", &ERC721::ownerOf, FunctionTypes::View, + std::vector{"tokenId"}), + std::make_tuple("tokenURI", &ERC721::tokenURI, FunctionTypes::View, + std::vector{"tokenId"}), + std::make_tuple("approve", &ERC721::approve, FunctionTypes::NonPayable, + std::vector{"to", "tokenId"}), + std::make_tuple("getApproved", &ERC721::getApproved, FunctionTypes::View, + std::vector{"tokenId"}), + std::make_tuple("setApprovalForAll", &ERC721::setApprovalForAll, + FunctionTypes::NonPayable, + std::vector{"operatorAddress", "approved"}), + std::make_tuple("isApprovedForAll", &ERC721::isApprovedForAll, FunctionTypes::View, + std::vector{"owner", "operatorAddress"}), + std::make_tuple("transferFrom", &ERC721::transferFrom, FunctionTypes::NonPayable, + std::vector{"from", "to", "tokenId"}), + std::make_tuple("safeTransferFrom", + static_cast(&ERC721::safeTransferFrom), + FunctionTypes::NonPayable, + std::vector{"from", "to", "tokenId", "data"}), + std::make_tuple("safeTransferFrom", + static_cast(&ERC721::safeTransferFrom), + FunctionTypes::NonPayable, + std::vector{"from", "to", "tokenId"})); + ContractReflectionInterface::registerContractEvents( + std::make_tuple("Transfer", false, &ERC721::Transfer, std::vector{"from","to", "tokenId"}), + std::make_tuple("Approval", false, &ERC721::Approval, std::vector{"owner","approved", "tokenId"}), + std::make_tuple("ApprovalForAll", false, &ERC721::ApprovalForAll, std::vector{"owner","operatorAddress", "approved"}) + ); + }); } + + /// Dump method + DBBatch dump() const override; }; #endif // ERC721_H diff --git a/src/contract/templates/standards/erc721uristorage.cpp b/src/contract/templates/standards/erc721uristorage.cpp new file mode 100644 index 00000000..cf5e8612 --- /dev/null +++ b/src/contract/templates/standards/erc721uristorage.cpp @@ -0,0 +1,89 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "erc721uristorage.h" + +#include "../../../utils/strconv.h" + +ERC721URIStorage::ERC721URIStorage(const Address& address, const DB& db) + : DynamicContract(address, db), + ERC721(address, db), + _tokenURIs(this) { + for (const auto& dbEntry : db.getBatch(this->getNewPrefix("tokenURIs_"))) { + this->_tokenURIs[Utils::fromBigEndian(dbEntry.key)] = StrConv::bytesToString(dbEntry.value); + } + ERC721URIStorage::registerContractFunctions(); +} + +ERC721URIStorage::ERC721URIStorage( + const std::string &erc721_name, const std::string &erc721_symbol, + const Address &address, const Address &creator, const uint64_t &chainId +) : DynamicContract("ERC721URIStorage", address, creator, chainId), + ERC721("ERC721URIStorage", erc721_name, erc721_symbol, address, creator, chainId), + _tokenURIs(this) { + ERC721URIStorage::registerContractFunctions(); +} + +ERC721URIStorage::ERC721URIStorage( + const std::string &derivedTypeName, + const std::string &erc721_name, const std::string &erc721_symbol, + const Address &address, const Address &creator, const uint64_t &chainId +) : DynamicContract(derivedTypeName, address, creator, chainId), + ERC721(derivedTypeName, erc721_name, erc721_symbol, address, creator, chainId), + _tokenURIs(this) { + ERC721URIStorage::registerContractFunctions(); +} + +DBBatch ERC721URIStorage::dump() const { + DBBatch batchedOperations = ERC721::dump(); + for (auto it = this->_tokenURIs.cbegin(); it != this->_tokenURIs.cend(); ++it) { + batchedOperations.push_back( + Utils::uintToBytes(it->first), + StrConv::stringToBytes(it->second), + this->getNewPrefix("tokenURIs_") + ); + } + return batchedOperations; +} + +void ERC721URIStorage::registerContractFunctions() { + ERC721URIStorage::registerContract(); + this->registerMemberFunctions(std::make_tuple("tokenURI", &ERC721URIStorage::tokenURI, FunctionTypes::View, this)); + this->registerInterface(Functor(UintConv::bytesToUint32(Hex::toBytes("0x49064906")))); +} + +void ERC721URIStorage::setTokenURI_(const uint256_t &tokenId, const std::string &_tokenURI) { + if (this->ownerOf_(tokenId) == Address()) { + throw DynamicException("ERC721URIStorage::_setTokenURI: Token does not exist."); + } + this->_tokenURIs[tokenId] = _tokenURI; +} + +Address ERC721URIStorage::update_(const Address& to, const uint256_t& tokenId, const Address& auth) { + Address prevOwner; + if (typeid(*this) == typeid(ERC721URIStorage)) { + prevOwner = ERC721::update_(to, tokenId, auth); + } else { + prevOwner = this->ownerOf_(tokenId); + } + if (prevOwner == Address() && this->_tokenURIs.find(tokenId) != this->_tokenURIs.end()) { + this->_tokenURIs.erase(tokenId); + } + return prevOwner; +} + +std::string ERC721URIStorage::tokenURI(const uint256_t &tokenId) const { + this->requireMinted_(tokenId); + auto it = this->_tokenURIs.find(tokenId); + std::string _tokenURI; + if (it != this->_tokenURIs.cend()) _tokenURI = it->second; + std::string base = this->baseURI_(); + if (base.size() == 0) return _tokenURI; + if (_tokenURI.size() > 0) return base + _tokenURI; + return ERC721::tokenURI(tokenId); +} + diff --git a/src/contract/templates/standards/erc721uristorage.h b/src/contract/templates/standards/erc721uristorage.h new file mode 100644 index 00000000..d0db3fbf --- /dev/null +++ b/src/contract/templates/standards/erc721uristorage.h @@ -0,0 +1,99 @@ +/* + Copyright (c) [2023-2024] [AppLayer Developers] + This software is distributed under the MIT License. + See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef ERC721URISTORAGE_H +#define ERC721URISTORAGE_H + +#include "erc721.h" + +/// Template for an ERC721URIStorage contract. +/// Roughly based on the OpenZeppelin implementation. +class ERC721URIStorage : virtual public ERC721 { + protected: + /// Solidity: mapping(uint256 tokenId => string) private _tokenURIs; + SafeUnorderedMap _tokenURIs; + + /** + * Set the token URI for a given token. + * @param tokenId + * @param _tokenURI + * Solidity: function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual + */ + void setTokenURI_(const uint256_t &tokenId, const std::string &_tokenURI); + + /** + * Transfers the tokenId from the current owner to the specified address. + * @param to The address that will receive the token. + * @param tokenId The id of the token that will be transferred. + * @param auth The address that is authorized to transfer the token. + * @return The address of the old + * If auth is non-zero, the function will check if the auth address is authorized to transfer the token. + * Solidity counterpart: function _update(address to, uint256 tokenId, address auth) internal returns (address) + */ + Address update_(const Address& to, const uint256_t& tokenId, const Address& auth) override; + + void registerContractFunctions() override; + + public: + /** + * ConstructorArguments is a tuple of the contract constructor arguments in the order they appear in the constructor. + */ + using ConstructorArguments = std::tuple; + + /** + * Constructor for loading contract from DB. + * @param address The address where the contract will be deployed. + * @param db Reference to the database object. + */ + ERC721URIStorage(const Address& address, const DB& db); + + /** + * Constructor to be used when creating a new contract. + * @param erc721_name The name of the ERC20 token. + * @param erc721_symbol The symbol of the ERC20 token. + * @param address The address where the contract will be deployed. + * @param creator The address of the creator of the contract. + * @param chainId The chain where the contract wil be deployed. + */ + ERC721URIStorage( + const std::string &erc721_name, const std::string &erc721_symbol, + const Address &address, const Address &creator, const uint64_t &chainId + ); + + /** + * Constructor to be used when creating a new contract. + * @param derivedTypeName The name of the derived type. + * @param erc721_name The name of the ERC20 token. + * @param erc721_symbol The symbol of the ERC20 token. + * @param address The address where the contract will be deployed. + * @param creator The address of the creator of the contract. + * @param chainId The chain where the contract wil be deployed. + */ + ERC721URIStorage( + const std::string &derivedTypeName, + const std::string &erc721_name, const std::string &erc721_symbol, + const Address &address, const Address &creator, const uint64_t &chainId + ); + + /// Solidity: function tokenURI(uint256 tokenId) public view virtual override returns (string memory) + virtual std::string tokenURI(const uint256_t &tokenId) const override; + + /// Register contract class via ContractReflectionInterface. + static void registerContract() { + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{"erc721_name", "erc721_symbol"}, + std::make_tuple("tokenOfOwnerByIndex", &ERC721URIStorage::tokenURI, FunctionTypes::View, std::vector{"tokenId"}) + ); + }); + } + + /// Dump method + DBBatch dump() const override; +}; + +#endif // ERC721URISTORAGE_H diff --git a/src/contract/templates/standards/ierc721receiver.hpp b/src/contract/templates/standards/ierc721receiver.hpp new file mode 100644 index 00000000..b600a02b --- /dev/null +++ b/src/contract/templates/standards/ierc721receiver.hpp @@ -0,0 +1,20 @@ +#include "erc721.h" + +class IERC721Receiver : public DynamicContract { + public: + static const Bytes4& onERC721ReceiverSelector() { + const static Bytes4 selector = Bytes4(Hex::toBytes("0x150b7a02")); + return selector; + } + Bytes4 onERC721Received(const Address& op,const Address& from,const uint256_t& tokenId,const Bytes& data) { + return Bytes4(); + } + void static registerContract() { + DynamicContract::registerContractMethods< + IERC721Receiver + >( + std::vector{}, + std::make_tuple("onERC721Received", &IERC721Receiver::onERC721Received, FunctionTypes::NonPayable, std::vector{"operator", "from", "tokenId", "data"}) + ); + } +}; \ No newline at end of file diff --git a/src/contract/templates/testThrowVars.cpp b/src/contract/templates/testThrowVars.cpp new file mode 100644 index 00000000..ca65bcf4 --- /dev/null +++ b/src/contract/templates/testThrowVars.cpp @@ -0,0 +1,37 @@ +#include "testThrowVars.h" + +TestThrowVars::TestThrowVars( + const std::string& var1, const std::string& var2, const std::string& var3, + const Address& address, + const Address& creator, const uint64_t& chainId +) : DynamicContract("TestThrowVars", address, creator, chainId), + var1_(this), var2_(this), var3_(this) +{ + this->var1_ = "var1"; + this->var2_ = "var2"; + this->var3_ = "var3"; + + this->var1_.commit(); + this->var2_.commit(); + this->var3_.commit(); + + this->registerContractFunctions(); + + throw DynamicException("Throw from create ctor"); + + this->var1_.enableRegister(); + this->var2_.enableRegister(); + this->var3_.enableRegister(); +} + +TestThrowVars::TestThrowVars(const Address& address, const DB& db +) : DynamicContract(address, db), var1_(this), var2_(this), var3_(this) { + // Do nothing +} + +TestThrowVars::~TestThrowVars() = default; + +DBBatch TestThrowVars::dump() const { return BaseContract::dump(); } + +void TestThrowVars::registerContractFunctions() { registerContract(); } + diff --git a/src/contract/templates/testThrowVars.h b/src/contract/templates/testThrowVars.h new file mode 100644 index 00000000..c9a024e5 --- /dev/null +++ b/src/contract/templates/testThrowVars.h @@ -0,0 +1,65 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef TESTTHROWVARS_H +#define TESTTHROWVARS_H + +#include "../dynamiccontract.h" +#include "../variables/safestring.h" + +/// Contract for testing throws. +class TestThrowVars : public DynamicContract { + private: + ///@{ + /** Test variable. */ + SafeString var1_; + SafeString var2_; + SafeString var3_; + ///@} + void registerContractFunctions() override; ///< Register the contract's functions. + + public: + /// The contract's constructor arguments. + using ConstructorArguments = std::tuple; + + /** + * Constructor from create. Create contract and save it to database. + * @param var1 The value of the respective test variable. + * @param var2 The value of the respective test variable. + * @param var3 The value of the respective test variable. + * @param address The address of the contract. + * @param creator The address of the creator of the contract. + * @param chainId The chain ID. + */ + TestThrowVars( + const std::string& var1, const std::string& var2, const std::string& var3, + const Address& address, const Address& creator, const uint64_t& chainId + ); + + /** + * Constructor from load. Load contract from database. + * @param address The address of the contract. + * @param db The database to use. + */ + TestThrowVars(const Address& address, const DB& db); + + ~TestThrowVars() override; ///< Destructor. + + /// Register the contract. + static void registerContract() { + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{"var1", "var2", "var3"} + ); + }); + } + + DBBatch dump() const override; ///< Dump method. +}; + +#endif // TESTTHROWVARS_H diff --git a/src/contract/templates/throwtestA.cpp b/src/contract/templates/throwtestA.cpp index 6f6e1478..73ae31cf 100644 --- a/src/contract/templates/throwtestA.cpp +++ b/src/contract/templates/throwtestA.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,23 +8,28 @@ See the LICENSE.txt file in the project root for more information. #include "throwtestA.h" ThrowTestA::ThrowTestA( - ContractManagerInterface &interface, - const Address& address, const Address& creator, - const uint64_t& chainId, const std::unique_ptr &db -) : DynamicContract(interface, "ThrowTestA", address, creator, chainId, db) { + const Address& address, const Address& creator, const uint64_t& chainId +) : DynamicContract("ThrowTestA", address, creator, chainId), num_(this) { + this->num_.commit(); registerContractFunctions(); + this->num_.enableRegister(); } -ThrowTestA::ThrowTestA( - ContractManagerInterface &interface, - const Address& address, - const std::unique_ptr &db -) : DynamicContract(interface, address, db) { +ThrowTestA::ThrowTestA(const Address& address, const DB& db) : DynamicContract(address, db) { + this->num_ = UintConv::bytesToUint8(db.get(std::string("num_"), this->getDBPrefix())); + this->num_.commit(); registerContractFunctions(); + this->num_.enableRegister(); } ThrowTestA::~ThrowTestA() { return; } +DBBatch ThrowTestA::dump() const { + DBBatch dbBatch = BaseContract::dump(); + dbBatch.push_back(StrConv::stringToBytes("num_"), UintConv::uint8ToBytes(this->num_.get()), this->getDBPrefix()); + return dbBatch; +} + uint8_t ThrowTestA::getNumA() const { return this->num_.get(); } void ThrowTestA::setNumA(const uint8_t& valA, @@ -37,7 +42,9 @@ void ThrowTestA::setNumA(const uint8_t& valA, void ThrowTestA::registerContractFunctions() { registerContract(); - this->registerMemberFunction("getNumA", &ThrowTestA::getNumA, FunctionTypes::View, this); - this->registerMemberFunction("setNumA", &ThrowTestA::setNumA, FunctionTypes::NonPayable, this); + this->registerMemberFunctions( + std::make_tuple("getNumA", &ThrowTestA::getNumA, FunctionTypes::View, this), + std::make_tuple("setNumA", &ThrowTestA::setNumA, FunctionTypes::NonPayable, this) + ); } diff --git a/src/contract/templates/throwtestA.h b/src/contract/templates/throwtestA.h index 75d277d8..339e9886 100644 --- a/src/contract/templates/throwtestA.h +++ b/src/contract/templates/throwtestA.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -26,23 +26,20 @@ class ThrowTestA : public DynamicContract { /** * Constructor. - * @param interface The interface to the contract manager. * @param address The address of the contract. * @param creator The address of the creator of the contract. * @param chainId The chain ID. - * @param db The database to use. */ - ThrowTestA(ContractManagerInterface &interface, const Address& address, - const Address& creator, const uint64_t& chainId, const std::unique_ptr &db + ThrowTestA(const Address& address, + const Address& creator, const uint64_t& chainId ); /** * Constructor for contract loading. - * @param interface The interface to the contract manager. * @param address The address of the contract. * @param db The database to use. */ - ThrowTestA(ContractManagerInterface &interface, const Address& address, const std::unique_ptr &db); + ThrowTestA(const Address& address, const DB& db); ~ThrowTestA() override; ///< Destructor. @@ -65,14 +62,18 @@ class ThrowTestA : public DynamicContract { * Register the contract structure. */ static void registerContract() { - ContractReflectionInterface::registerContractMethods< - ThrowTestA, ContractManagerInterface&, const Address&, const Address&, const uint64_t&, const std::unique_ptr& - >( - std::vector{}, - std::make_tuple("getNumA", &ThrowTestA::getNumA, FunctionTypes::View, std::vector{}), - std::make_tuple("setNumA", &ThrowTestA::setNumA, FunctionTypes::NonPayable, std::vector{"valA", "addB", "valB", "addC", "valC"}) - ); + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{}, + std::make_tuple("getNumA", &ThrowTestA::getNumA, FunctionTypes::View, std::vector{}), + std::make_tuple("setNumA", &ThrowTestA::setNumA, FunctionTypes::NonPayable, std::vector{"valA", "addB", "valB", "addC", "valC"}) + ); + }); } + + /// Dump method + DBBatch dump() const override; }; #endif // THROWTESTA_H diff --git a/src/contract/templates/throwtestB.cpp b/src/contract/templates/throwtestB.cpp index 3bd84bd0..4cadc822 100644 --- a/src/contract/templates/throwtestB.cpp +++ b/src/contract/templates/throwtestB.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,33 +8,42 @@ See the LICENSE.txt file in the project root for more information. #include "throwtestB.h" ThrowTestB::ThrowTestB( - ContractManagerInterface &interface, - const Address& address, const Address& creator, - const uint64_t& chainId, const std::unique_ptr &db -) : DynamicContract(interface, "ThrowTestB", address, creator, chainId, db) { + const Address& address, const Address& creator, const uint64_t& chainId +) : DynamicContract("ThrowTestB", address, creator, chainId), num_(this) { + this->num_.commit(); registerContractFunctions(); + this->num_.enableRegister(); } -ThrowTestB::ThrowTestB( - ContractManagerInterface &interface, - const Address& address, - const std::unique_ptr &db -) : DynamicContract(interface, address, db) { +ThrowTestB::ThrowTestB(const Address& address, const DB& db) : DynamicContract(address, db) { + this->num_ = UintConv::bytesToUint8(db.get(std::string("num_"), this->getDBPrefix())); + this->num_.commit(); registerContractFunctions(); + this->num_.enableRegister(); } ThrowTestB::~ThrowTestB() { return; } +DBBatch ThrowTestB::dump() const { + DBBatch dbBatch = BaseContract::dump(); + dbBatch.push_back(StrConv::stringToBytes("num_"), UintConv::uint8ToBytes(this->num_.get()), this->getDBPrefix()); + return dbBatch; +} + uint8_t ThrowTestB::getNumB() const { return this->num_.get(); } -[[noreturn]] void ThrowTestB::setNumB(const uint8_t& valB, const Address& addC, const uint8_t& valC) { +[[noreturn]] void ThrowTestB::setNumB( + const uint8_t& valB, const Address&, const uint8_t& +) { this->num_ = valB; - throw std::runtime_error("Intended throw in ThrowTestB"); + throw DynamicException("Intended throw in ThrowTestB"); } void ThrowTestB::registerContractFunctions() { registerContract(); - this->registerMemberFunction("getNumB", &ThrowTestB::getNumB, FunctionTypes::View, this); - this->registerMemberFunction("setNumB", &ThrowTestB::setNumB, FunctionTypes::NonPayable, this); + this->registerMemberFunctions( + std::make_tuple("getNumB", &ThrowTestB::getNumB, FunctionTypes::View, this), + std::make_tuple("setNumB", &ThrowTestB::setNumB, FunctionTypes::NonPayable, this) + ); } diff --git a/src/contract/templates/throwtestB.h b/src/contract/templates/throwtestB.h index 2e280674..240329b6 100644 --- a/src/contract/templates/throwtestB.h +++ b/src/contract/templates/throwtestB.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -26,23 +26,18 @@ class ThrowTestB : public DynamicContract { /** * Constructor from create. Create contract and save it to database. - * @param interface The interface to the contract manager. * @param address The address of the contract. * @param creator The address of the creator of the contract. * @param chainId The chain ID. - * @param db The database to use. */ - ThrowTestB(ContractManagerInterface &interface, const Address& address, - const Address& creator, const uint64_t& chainId, const std::unique_ptr &db - ); + ThrowTestB(const Address& address, const Address& creator, const uint64_t& chainId); /** * Constructor from load. Load contract from database. - * @param interface The interface to the contract manager. * @param address The address of the contract. * @param db The database to use. */ - ThrowTestB(ContractManagerInterface &interface, const Address& address, const std::unique_ptr &db); + ThrowTestB(const Address& address, const DB& db); ~ThrowTestB() override; ///< Destructor. @@ -54,14 +49,18 @@ class ThrowTestB : public DynamicContract { * Register the contract structure. */ static void registerContract() { - ContractReflectionInterface::registerContractMethods< - ThrowTestB, ContractManagerInterface&, const Address&, const Address&, const uint64_t&, const std::unique_ptr& - >( - std::vector{}, - std::make_tuple("getNumB", &ThrowTestB::getNumB, FunctionTypes::View, std::vector{}), - std::make_tuple("setNumB", &ThrowTestB::setNumB, FunctionTypes::NonPayable, std::vector{"valB", "addC", "valC"}) - ); + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{}, + std::make_tuple("getNumB", &ThrowTestB::getNumB, FunctionTypes::View, std::vector{}), + std::make_tuple("setNumB", &ThrowTestB::setNumB, FunctionTypes::NonPayable, std::vector{"valB", "addC", "valC"}) + ); + }); } + + /// Dump method + DBBatch dump() const override; }; #endif // THROWTESTB_H diff --git a/src/contract/templates/throwtestC.cpp b/src/contract/templates/throwtestC.cpp index 6c0afb00..ca307288 100644 --- a/src/contract/templates/throwtestC.cpp +++ b/src/contract/templates/throwtestC.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,23 +8,28 @@ See the LICENSE.txt file in the project root for more information. #include "throwtestC.h" ThrowTestC::ThrowTestC( - ContractManagerInterface &interface, - const Address& address, const Address& creator, - const uint64_t& chainId, const std::unique_ptr &db -) : DynamicContract(interface, "ThrowTestC", address, creator, chainId, db) { + const Address& address, const Address& creator, const uint64_t& chainId +) : DynamicContract("ThrowTestC", address, creator, chainId), num_(this) { + this->num_.commit(); registerContractFunctions(); + this->num_.enableRegister(); } -ThrowTestC::ThrowTestC( - ContractManagerInterface &interface, - const Address& address, - const std::unique_ptr &db -) : DynamicContract(interface, address, db) { +ThrowTestC::ThrowTestC(const Address& address, const DB& db) : DynamicContract(address, db) { + this->num_ = UintConv::bytesToUint8(db.get(std::string("num_"), this->getDBPrefix())); + this->num_.commit(); registerContractFunctions(); + this->num_.enableRegister(); } ThrowTestC::~ThrowTestC() { return; } +DBBatch ThrowTestC::dump() const { + DBBatch dbBatch = BaseContract::dump(); + dbBatch.push_back(StrConv::stringToBytes("num_"), UintConv::uint8ToBytes(this->num_.get()), this->getDBPrefix()); + return dbBatch; +} + uint8_t ThrowTestC::getNumC() const { return this->num_.get(); } void ThrowTestC::setNumC(const uint8_t& valC) { @@ -33,7 +38,9 @@ void ThrowTestC::setNumC(const uint8_t& valC) { void ThrowTestC::registerContractFunctions() { registerContract(); - this->registerMemberFunction("getNumC", &ThrowTestC::getNumC, FunctionTypes::View, this); - this->registerMemberFunction("setNumC", &ThrowTestC::setNumC, FunctionTypes::NonPayable, this); + this->registerMemberFunctions( + std::make_tuple("getNumC", &ThrowTestC::getNumC, FunctionTypes::View, this), + std::make_tuple("setNumC", &ThrowTestC::setNumC, FunctionTypes::NonPayable, this) + ); } diff --git a/src/contract/templates/throwtestC.h b/src/contract/templates/throwtestC.h index 27fa6174..a4b9329e 100644 --- a/src/contract/templates/throwtestC.h +++ b/src/contract/templates/throwtestC.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -25,23 +25,18 @@ class ThrowTestC : public DynamicContract { /** * Constructor from create. Create contract and save it to database. - * @param interface The interface to the contract manager. * @param address The address of the contract. * @param creator The address of the creator of the contract. * @param chainId The chain ID. - * @param db The database to use. */ - ThrowTestC(ContractManagerInterface &interface, const Address& address, - const Address& creator, const uint64_t& chainId, const std::unique_ptr &db - ); + ThrowTestC(const Address& address, const Address& creator, const uint64_t& chainId); /** * Constructor from load. Load contract from database. - * @param interface The interface to the contract manager. * @param address The address of the contract. * @param db The database to use. */ - ThrowTestC(ContractManagerInterface &interface, const Address& address, const std::unique_ptr &db); + ThrowTestC(const Address& address, const DB& db); ~ThrowTestC() override; ///< Destructor. @@ -53,14 +48,18 @@ class ThrowTestC : public DynamicContract { * Register the contract structure. */ static void registerContract() { - ContractReflectionInterface::registerContractMethods< - ThrowTestC, ContractManagerInterface&, const Address&, const Address&, const uint64_t&, const std::unique_ptr& - >( - std::vector{}, - std::make_tuple("getNumC", &ThrowTestC::getNumC, FunctionTypes::View, std::vector{}), - std::make_tuple("setNumC", &ThrowTestC::setNumC, FunctionTypes::NonPayable, std::vector{"valC"}) - ); + static std::once_flag once; + std::call_once(once, []() { + DynamicContract::registerContractMethods( + std::vector{}, + std::make_tuple("getNumC", &ThrowTestC::getNumC, FunctionTypes::View, std::vector{}), + std::make_tuple("setNumC", &ThrowTestC::setNumC, FunctionTypes::NonPayable, std::vector{"valC"}) + ); + }); } + + /// Dump method + DBBatch dump() const override; }; #endif // THROWTESTB_H diff --git a/src/contract/trace/call.cpp b/src/contract/trace/call.cpp new file mode 100644 index 00000000..57312db6 --- /dev/null +++ b/src/contract/trace/call.cpp @@ -0,0 +1,48 @@ +#include "call.h" +#include "contract/abi.h" + +namespace trace { + +json Call::toJson() const { + using enum CallType; + json res; + + switch (this->type) { + case CALL: res["type"] = "CALL"; break; + case STATICCALL: res["type"] = "STATICCALL"; break; + case DELEGATECALL: res["type"] = "DELEGATECALL"; break; + } + + res["from"] = this->from.hex(true); + res["to"] = this->to.hex(true); + + const uint256_t value = UintConv::bytesToUint256(this->value); + res["value"] = Hex::fromBytes(Utils::uintToBytes(value), true).forRPC(); + + res["gas"] = Hex::fromBytes(Utils::uintToBytes(this->gas), true).forRPC(); + res["gasUsed"] = Hex::fromBytes(Utils::uintToBytes(this->gasUsed), true).forRPC(); + res["input"] = Hex::fromBytes(this->input, true); + + if (!this->output.empty()) res["output"] = Hex::fromBytes(this->output, true); + + switch (this->status) { + case CallStatus::EXECUTION_REVERTED: { + res["error"] = "execution reverted"; + try { + std::string revertReason = ABI::Decoder::decodeError(this->output); + res["revertReason"] = std::move(revertReason); + } catch (const std::exception& ignored) {} + break; + } + case CallStatus::OUT_OF_GAS: res["error"] = "out of gas"; break; + } + + if (!this->calls.empty()) { + res["calls"] = json::array(); + for (const auto& subcall : this->calls) res["calls"].push_back(subcall.toJson()); + } + + return res; +} + +} // namespace trace diff --git a/src/contract/trace/call.h b/src/contract/trace/call.h new file mode 100644 index 00000000..1194dfc5 --- /dev/null +++ b/src/contract/trace/call.h @@ -0,0 +1,34 @@ +#ifndef BDK_CONTRACT_TRACE_CALL_H +#define BDK_CONTRACT_TRACE_CALL_H + +#include +#include "libs/zpp_bits.h" +#include "utils/bytes.h" +#include "utils/fixedbytes.h" +#include "utils/address.h" +#include "utils/utils.h" +#include "callstatus.h" +#include "calltype.h" + +namespace trace { + +struct Call { + json toJson() const; + + using serialize = zpp::bits::members<10>; + + CallType type; + CallStatus status; + Address from; + Address to; + FixedBytes<32> value; + uint64_t gas; + uint64_t gasUsed; + Bytes input; + Bytes output; + boost::container::stable_vector calls; +}; + +} // namespace trace + +#endif // BDK_CONTRACT_TRACE_CALL_H diff --git a/src/contract/trace/callstatus.h b/src/contract/trace/callstatus.h new file mode 100644 index 00000000..c8debb47 --- /dev/null +++ b/src/contract/trace/callstatus.h @@ -0,0 +1,14 @@ +#ifndef BDK_CONTRACT_TRACE_CALLSTATUS_H +#define BDK_CONTRACT_TRACE_CALLSTATUS_H + +namespace trace { + +enum class CallStatus { + SUCCEEDED, + EXECUTION_REVERTED, + OUT_OF_GAS +}; + +} // namespace trace + +#endif // BDK_CONTRACT_TRACE_CALLSTATUS_H diff --git a/src/contract/trace/calltype.h b/src/contract/trace/calltype.h new file mode 100644 index 00000000..affbee2f --- /dev/null +++ b/src/contract/trace/calltype.h @@ -0,0 +1,28 @@ +#ifndef BDK_CONTRACT_TRACE_CALLTYPE_H +#define BDK_CONTRACT_TRACE_CALLTYPE_H + +#include "contract/concepts.h" + +namespace trace { + +enum class CallType { + CALL, + STATICCALL, + DELEGATECALL +}; + +template +constexpr CallType getMessageCallType(const M& msg) { + if constexpr (concepts::DelegateCallMessage) { + return CallType::DELEGATECALL; + } else if (concepts::StaticCallMessage) { + return CallType::STATICCALL; + } else { + return CallType::CALL; + } +} + +} // namespace trace + + +#endif // BDK_CONTRACT_TRACE_CALLTYPE_H diff --git a/src/contract/traits.h b/src/contract/traits.h new file mode 100644 index 00000000..1baa9ab0 --- /dev/null +++ b/src/contract/traits.h @@ -0,0 +1,78 @@ +#ifndef BDK_MESSAGES_TRAITS_H +#define BDK_MESSAGES_TRAITS_H + +#include +#include "concepts.h" + +namespace traits { + +template +struct Methods {}; + +template +struct Methods { + using Return = R; + using Class = C; + static constexpr bool IS_VIEW = false; +}; + +template +struct Methods { + using Return = R; + using Class = C; + static constexpr bool IS_VIEW = true; +}; + +template +using MethodReturn = Methods>::Return; + +template +using MethodClass = Methods>::Class; + +template +constexpr bool IsViewMethod = Methods>::IS_VIEW; + +template +struct MessageResultHelper; + +template + requires concepts::PackedMessage +struct MessageResultHelper { + using Type = MethodReturn().method())>; +}; + +template + requires concepts::EncodedMessage +struct MessageResultHelper { + using Type = Bytes; +}; + +template +struct MessageResultHelper { + using Type = Address; +}; + +template +using MessageResult = MessageResultHelper::Type; + +template +struct MessageContractHelper; + +template + requires (concepts::PackedMessage) +struct MessageContractHelper { + using Type = MethodClass().method())>; +}; + +template + requires (concepts::PackedMessage) +struct MessageContractHelper { + using Type = std::remove_cvref_t::ContractType; +}; + +template +using MessageContract = std::remove_cvref_t::Type>; + +} // namespace traits + +#endif // BDK_MESSAGES_TRAITS_H diff --git a/src/contract/variables/reentrancyguard.h b/src/contract/variables/reentrancyguard.h index 643d6aaa..3537e0a3 100644 --- a/src/contract/variables/reentrancyguard.h +++ b/src/contract/variables/reentrancyguard.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,29 +8,27 @@ See the LICENSE.txt file in the project root for more information. #ifndef REENTRANCY_GUARD_H #define REENTRANCY_GUARD_H -#include +#include "../../utils/dynamicexception.h" /** - * The ReentrancyGuard class is used to prevent reentrancy attacks. - * Similarly to std::unique_lock or std::shared_lock, ReentrancyGuard is a RAII object. + * RAII object used to prevent reentrancy attacks, similar to std::unique_lock or std::shared_lock. * It is meant to be used within the first line of the function you want to protect against reentrancy attacks. * The constructor of ReentrancyGuard will check the bool and set it to true. * If the bool is already true, the constructor will throw an exception. * The destructor of ReentrancyGuard will set the bool to false. */ - class ReentrancyGuard { private: - bool &lock_; ///< Reference to the mutex. + bool& lock_; ///< Reference to the mutex. public: /** * Constructor. * @param lock Reference to the mutex. - * @throw std::runtime_error if the mutex is already locked. + * @throw DynamicException if the mutex is already locked. */ - explicit ReentrancyGuard(bool &lock) : lock_(lock) { - if (lock_) throw std::runtime_error("ReentrancyGuard: reentrancy attack detected"); + explicit ReentrancyGuard(bool& lock) : lock_(lock) { + if (lock_) throw DynamicException("ReentrancyGuard: reentrancy attack detected"); lock_ = true; } diff --git a/src/contract/variables/safeaddress.h b/src/contract/variables/safeaddress.h index 37717e1f..771879d9 100644 --- a/src/contract/variables/safeaddress.h +++ b/src/contract/variables/safeaddress.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -9,23 +9,17 @@ See the LICENSE.txt file in the project root for more information. #define SAFEADDRESS_H #include "../../utils/strings.h" + #include "safebase.h" /** - * Safe wrapper for an Address variable. - * Used to safely store an Address within a contract. - * @see SafeBase - * @see Address + * Safe wrapper for an Address variable. Used to safely store an Address within a contract. + * @see SafeBase, Address */ class SafeAddress : public SafeBase { private: - Address address_; ///< Value. - mutable std::unique_ptr
addressPtr_; ///< Pointer to the value. check() requires this to be mutable. - - /// Check if the pointer is initialized (and initialize it if not). - inline void check() const override { - if (addressPtr_ == nullptr) addressPtr_ = std::make_unique
(address_); - }; + Address value_; ///< Current ("original") value. + Address copy_; ///< Previous ("temporary") value. public: /** @@ -33,62 +27,45 @@ class SafeAddress : public SafeBase { * @param owner The contract that owns the variable. * @param address The initial value. Defaults to an empty address. */ - SafeAddress(DynamicContract* owner, const Address& address = Address()) - : SafeBase(owner), address_(Address()), addressPtr_(std::make_unique
(address)) - {}; + explicit SafeAddress(DynamicContract* owner, const Address& address = Address()) + : SafeBase(owner), value_(address), copy_(address) {} /** * Empty constructor. * @param address The initial value. Defaults to an empty address. */ explicit SafeAddress(const Address& address = Address()) - : SafeBase(nullptr), address_(Address()), addressPtr_(std::make_unique
(address)) - {}; + : SafeBase(nullptr), value_(address), copy_(address) {} - /// Copy constructor. - SafeAddress(const SafeAddress& other) : SafeBase(nullptr) { - check(); - address_ = other.address_; - addressPtr_ = std::make_unique
(*other.addressPtr_); - } + /// Copy constructor. Only copies the CURRENT value. + SafeAddress(const SafeAddress& other) : SafeBase(nullptr), value_(other.value_), copy_(other.value_) {} - /// Getter for the value. Returns the value from the pointer. - inline const Address& get() const { check(); return *addressPtr_; }; + /// Getter for the CURRENT value. + inline const Address& get() const { return this->value_; } - /// Commit the value. Updates the value from the pointer and nullifies it. - inline void commit() override { - check(); - address_ = *addressPtr_; - addressPtr_ = nullptr; + ///@{ + /** Assignment operator. Assigns only the CURRENT value. */ + inline SafeAddress& operator=(const Address& address) { + markAsUsed(); this->value_ = address; return *this; }; - - /// Revert the value. Nullifies the pointer. - inline void revert() const override { addressPtr_ = nullptr; }; - - /// Assignment operator. - inline Address& operator=(const Address& address) { - check(); - markAsUsed(); - *addressPtr_ = address; - return *addressPtr_; + inline SafeAddress& operator=(const SafeAddress& other) { + markAsUsed(); this->value_ = other.value_; return *this; }; + ///@} - /// Assignment operator. - inline Address& operator=(const SafeAddress& other) { - check(); - markAsUsed(); - *addressPtr_ = other.get(); - return *addressPtr_; - }; + ///@{ + /** Equality operator. Checks only the CURRENT value. */ + inline bool operator==(const Address& other) const { return (this->value_ == other); } + inline bool operator==(const SafeAddress& other) const { return (this->value_ == other.get()); } + ///@} - /// Equality operator. - inline bool operator==(const Address& other) const { - check(); return (*addressPtr_ == other); - } + /// Commit the value. + inline void commit() override { this->copy_ = this->value_; this->registered_ = false; } - /// Equality operator. - inline bool operator==(const SafeAddress& other) const { - check(); return (*addressPtr_ == other.get()); + /// Revert the value. + inline void revert() override { + this->value_ = this->copy_; + this->registered_ = false; } }; diff --git a/src/contract/variables/safearray.h b/src/contract/variables/safearray.h index 0db693cb..574535c9 100644 --- a/src/contract/variables/safearray.h +++ b/src/contract/variables/safearray.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -9,156 +9,176 @@ See the LICENSE.txt file in the project root for more information. #define SAFEARRAY_H #include -#include +#include +#include +#include +#include + #include "safebase.h" /** * Safe wrapper for `std::array`. * Works the same as SafeVector, but with more constraints as a std::array is a fixed size container. * SafeArray is ALWAYS initialized with default values, differently from std::array. - * @see SafeVector - * @see SafeBase + * @see SafeVector, SafeBase */ template class SafeArray : public SafeBase { private: - std::array array_; ///< Original array. - mutable std::unique_ptr> tmp_; ///< Temporary array. - - /// Check if the temporary array is initialized (and initialize it if not). - inline void check() const { - if (this->tmp_ == nullptr) this->tmp_ = std::make_unique>(); - } - - /// Check a index and copy if necessary. /** - * Check if a specific index exists in the temporary array, copying it from the original if not. - * @param index The index to check. - * @throw std::out_of_range if index is bigger than the maximum index of the array. + * Enum for partial array modifying operations, used by the undo structure. + * Full operations are not included since doing any of them disables the + * use of the undo stack from that point until commit/revert. */ - inline void checkIndexAndCopy_(const uint64_t& index) { - this->check(); - if (index >= N) throw std::out_of_range("Index out of range"); - if (this->tmp_->contains(index)) return; - this->tmp_->emplace(index, this->array_[index]); + enum ArrayOp { AT, OPERATOR_BRACKETS, FRONT, BACK }; // Technically everything can be AT here, but discernment is important + + /// Helper alias for the undo operation structure (operation made, in which index, and the old value). + using UndoOp = std::tuple; + + std::array value_; ///< Current ("original") value. + std::unique_ptr> copy_; ///< Full copy of the current value. + std::unique_ptr>> undo_; ///< Undo stack of the current value. + + /// Undo all changes in the undo stack on top of the current value. + void processUndoStack() { + while (!this->undo_->empty()) { + const UndoOp& op = this->undo_->top(); + const auto& [operation, index, value] = op; + switch (operation) { + case AT: this->value_.at(index) = value; break; + case OPERATOR_BRACKETS: this->value_[index] = value; break; + case FRONT: this->value_.at(0) = value; break; + case BACK: this->value_.at(N-1) = value; break; + // at(0)/(N-1) are hardcoded on purpose - std::get<1>(op) is not really + // needed for FRONT and BACK, but it could be used as well + } + this->undo_->pop(); + } } public: /** * Default constructor. - * @param a (optional) An array of T with fixed size of N to use during construction. - * Defaults to an empty array. + * @param a (optional) An array of T with fixed size of N to use during construction. Defaults to an empty array. */ - SafeArray(const std::array& a = {}) : SafeBase(nullptr) { - check(); - uint64_t index = 0; - for (const auto& value : a) { - this->tmp_->emplace(index, value); - index++; - } - }; + explicit SafeArray(const std::array& a = {}) : SafeBase(nullptr), value_(a), copy_(nullptr), undo_(nullptr) {} /** * Constructor with owner, for contracts. * @param owner The owner of the variable. - * @param a (optional) An array of T with fixed size of N to use during construction. - * Defaults to an empty array. + * @param a (optional) An array of T with fixed size of N to use during construction. Defaults to an empty array. */ - SafeArray(DynamicContract* owner, const std::array& a = {}) : SafeBase(owner) { - check(); - uint64_t index = 0; - for (const auto& value : a) { - this->tmp_->emplace(index, value); - index++; - } - }; + explicit SafeArray(DynamicContract* owner, const std::array& a = {}) + : SafeBase(owner), value_(a), copy_(nullptr), undo_(nullptr) {} + ///@{ /** - * Access a specified element of the temporary array with bounds checking. + * Access a specified element of the array. * @param pos The position of the index to access. * @return The element at the given index. + * @throws std::out_of_range if pos is bigger than the array's size. */ inline T& at(std::size_t pos) { - checkIndexAndCopy_(pos); - markAsUsed(); - return this->tmp_->at(pos); - } - - /** - * Const overload of at(). - * @param pos The position of the index to access. - * @return The element at the given index. - */ - const T& at(std::size_t pos) const { - checkIndexAndCopy_(pos); - return this->tmp_->at(pos); + T& ret = this->value_.at(pos); + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(std::make_tuple(ArrayOp::AT, pos, this->value_.at(pos))); + } + markAsUsed(); return ret; } + inline const T& at(std::size_t pos) const { return this->value_.at(pos); } + ///@} + ///@{ /** - * Access a specified element of the temporary array without bounds checking. + * Access a specified element of the array (without bounds checking). * @param pos The position of the index to access. * @return The element at the given index. */ inline T& operator[](std::size_t pos) { - checkIndexAndCopy_(pos); - markAsUsed(); - return (*this->tmp_)[pos]; + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(std::make_tuple(ArrayOp::OPERATOR_BRACKETS, pos, this->value_[pos])); + } + markAsUsed(); return this->value_[pos]; } - - /** - * Const overload of operator[]. - * @param pos The position of the index to access. - * @return The element at the given index. - */ - inline const T& operator[](std::size_t pos) const { - checkIndexAndCopy_(pos); - return (*this->tmp_)[pos]; + inline const T& operator[](std::size_t pos) const { return this->value_[pos]; } + ///@} + + ///@{ + /** Access the first element of the array. */ + inline T& front() { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(std::make_tuple(ArrayOp::FRONT, 0, this->value_.at(0))); + } + markAsUsed(); return this->value_.front(); + } + inline const T& front() const { return this->value_.front(); } + ///@} + + ///@{ + /** Access the last element of the array. */ + inline T& back() { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(std::make_tuple(ArrayOp::BACK, N-1, this->value_.at(N-1))); + } + markAsUsed(); return this->value_.back(); } + inline const T& back() const { return this->value_.back(); } + ///@} + + /// Get a pointer to the underlying array serving as element storage. + inline const T* data() const { return this->value_.data(); } - /// Get an iterator to the beginning of the original array. - inline std::array::const_iterator cbegin() const { return this->array_.cbegin(); } + /// Get an iterator to the beginning of the array. + inline typename std::array::const_iterator cbegin() const { return this->value_.cbegin(); } - /// Get an iterator to the end of the original array. - inline std::array::const_iterator cend() const { return this->array_.cend(); } + /// Get an iterator to the end of the array. + inline typename std::array::const_iterator cend() const { return this->value_.cend(); } - /// Get a reverse iterator to the beginning of the original array. - inline std::array::const_reverse_iterator crbegin() const { return this->array_.crbegin(); } + /// Get a reverse iterator to the beginning of the array. + inline typename std::array::const_reverse_iterator crbegin() const { return this->value_.crbegin(); } - /// Get a reverse iterator to the end of the original array. - inline std::array::const_reverse_iterator crend() const { return this->array_.crend(); } + /// Get a reverse iterator to the end of the array. + inline typename std::array::const_reverse_iterator crend() const { return this->value_.crend(); } /** - * Check if the original array is empty (has no elements). + * Check if the array is empty (has no elements). * @return `true` if array is empty, `false` otherwise. */ - inline bool empty() const { return (N == 0); } + constexpr bool empty() const noexcept { return this->value_.empty(); } - /** - * Get the current size of the original array. - * @return The size of the array. - */ - inline std::size_t size() const { return N; } + /// Get the current size of the array. + constexpr std::size_t size() const noexcept { return this->value_.size(); } - /** - * Get the maximum possible size of the original array. - * @return The maximum size. - */ - inline std::size_t max_size() const { return N; } + /// Get the maximum possible size of the array. + constexpr std::size_t max_size() const noexcept { return this->value_.max_size(); } /** - * Fill the temporary array with a given value. + * Fill the array with a given value. * @param value The value to fill the array with. */ inline void fill(const T& value) { - for (uint64_t i = 0; i < N; i++) this->tmp_->insert_or_assign(i, value); + if (this->copy_ == nullptr) this->copy_ = std::make_unique>(this->value_); + markAsUsed(); this->value_.fill(value); } - /// Commit the value. Updates the original array from the temporary one and clears it. - void commit() override { - for (const auto& [index, value] : *this->tmp_) this->array_[index] = value; - } + ///@{ + /** Equality operator. Checks only the CURRENT value. */ + inline bool operator==(const std::array& other) const { return (this->value_ == other); } + inline bool operator==(const SafeArray& other) const { return (this->value_ == other.value_); } + ///@} + + /// Commit the value. + void commit() override { this->copy_ = nullptr; this->undo_ = nullptr; this->registered_ = false; } - /// Revert the value. Nullifies the temporary array. - void revert() const override { this->tmp_ = nullptr; } + /// Revert the value. + void revert() override { + if (this->copy_ != nullptr) this->value_ = *this->copy_; + if (this->undo_ != nullptr && !this->undo_->empty()) this->processUndoStack(); + this->copy_ = nullptr; this->undo_ = nullptr; this->registered_ = false; + } }; #endif // SAFEARRAY_H diff --git a/src/contract/variables/safebase.h b/src/contract/variables/safebase.h index 357a54ce..8eee029c 100644 --- a/src/contract/variables/safebase.h +++ b/src/contract/variables/safebase.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,7 +8,7 @@ See the LICENSE.txt file in the project root for more information. #ifndef SAFEBASE_H #define SAFEBASE_H -#include +#include // Forward declarations. class DynamicContract; @@ -17,20 +17,16 @@ void registerVariableUse(DynamicContract &contract, SafeBase &variable); /** * Base class for all safe variables. Used to safely store a variable within a contract. - * @see SafeAddress - * @see SafeBool - * @see SafeString - * @see SafeUnorderedMap + * @see SafeArray, SafeAddress, SafeBool, SafeInt_t, SafeUint_t, SafeString, SafeUnorderedMap, SafeTuple, SafeVector */ class SafeBase { private: /** - * Pointer to the contract that owns the variable. - * Why pointer and not reference? + * Pointer to the contract that owns the variable. Why pointer and not reference? * Certain operators return a new copy of the variable, and such copy is - * not stored within the contract, only within the function. Thus, the - * contract pointer is not available because we don't need to register a - * variable that will be destroyed, regardless of whether it reverts or not. + * not stored within the contract, only within the function. + * Thus, the contract pointer is not available because we don't need to register + * a variable that will be destroyed, regardless of whether it reverts or not. * Variables of the contract should be initialized during the constructor of * such contract. Passing the contract as a pointer allows us to register it * to the contract, and call commit() and/or revert() properly. @@ -38,17 +34,22 @@ class SafeBase { DynamicContract* owner_ = nullptr; protected: - /// Indicates whether the variable is already registered within the contract. - mutable bool registered_ = false; + mutable bool registered_ = false; ///< Indicates whether the variable is already registered within the contract. + bool shouldRegister_ = false; ///< Indicates whether the variable should be registered within the contract. - /// Getter for `owner`. - inline DynamicContract* getOwner() const { return owner_; } + inline DynamicContract* getOwner() const { return this->owner_; } ///< Getter for `owner`. + + /** + * Check if the variable is registered within the contract. + * @return `true` if the variable is registered, `false` otherwise. + */ + inline bool isRegistered() const { return this->registered_; } /// Register the use of the variable within the contract. - void markAsUsed() { - if (owner_ != nullptr && !registered_) { + void markAsUsed() { + if (this->owner_ != nullptr && !this->registered_ && this->shouldRegister_) { registerVariableUse(*owner_, *this); - registered_ = true; + this->registered_ = true; } } @@ -60,19 +61,12 @@ class SafeBase { throw std::runtime_error("Derived Class from SafeBase does not override check()"); }; - /** - * Check if the variable is registered within the contract. - * @return `true` if the variable is registered, `false` otherwise. - */ - inline bool isRegistered() const { return registered_; } - public: /// Empty constructor. Should be used only within local variables within functions. SafeBase() : owner_(nullptr) {}; /** - * Constructor. - * Only variables built with this constructor will be registered within the contract. + * Constructor with owner. Only variables built with this constructor will be registered within the contract. * @param owner A pointer to the owner contract. */ explicit SafeBase(DynamicContract* owner) : owner_(owner) {}; @@ -80,29 +74,27 @@ class SafeBase { /** * Constructor for variables that are not registered within the contract. * Should be used only within local variables within functions. - * @param other The variable to copy from. */ - SafeBase(const SafeBase& other) : owner_(nullptr) {}; + SafeBase(const SafeBase&) : owner_(nullptr) {}; + + void enableRegister() { this->shouldRegister_ = true; } ///< Enable variable registration. /** - * Commit a structure value to the contract. - * Should always be overridden by the child class. + * Commit a structure value. Should always be overridden by the child class. + * This function should simply discard the previous/temporary value, as the + * current/original value is always operated on (optimist approach). * Child class should always do `this->registered = false;` at the end of commit(). - * @throw std::runtime_error if not overridden by the child class. + * @throw DynamicException if not overridden by the child class. */ - inline virtual void commit() { - throw std::runtime_error("Derived Class from SafeBase does not override commit()"); - }; + inline virtual void commit() { assert(false); } /** - * Revert a structure value (nullify). - * Should always be overridden by the child class. + * Revert a structure value. Should always be overridden by the child class. + * This function should copy the previous/temporary value back to the current/original value. * Child class should always do `this->registered = false;` at the end of revert(). - * @throw std::runtime_error if not overridden by the child class. + * @throw DynamicException if not overridden by the child class. */ - inline virtual void revert() const { - throw std::runtime_error("Derived Class from SafeBase does not override revert()"); - }; + inline virtual void revert() { assert(false); } }; #endif // SAFEBASE_H diff --git a/src/contract/variables/safebool.h b/src/contract/variables/safebool.h index 885654c5..9ec54cd7 100644 --- a/src/contract/variables/safebool.h +++ b/src/contract/variables/safebool.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,24 +8,16 @@ See the LICENSE.txt file in the project root for more information. #ifndef SAFEBOOL_H #define SAFEBOOL_H -#include - #include "safebase.h" /** - * Safe wrapper for a bool variable. - * Used to safely store a bool within a contract. + * Safe wrapper for a bool variable. Used to safely store a bool within a contract. * @see SafeBase */ class SafeBool : public SafeBase { private: - bool value_; ///< Value. - mutable std::unique_ptr valuePtr_; ///< Pointer to the value. check() requires this to be mutable. - - /// Check if the pointer is initialized (and initialize it if not). - inline void check() const override { - if (valuePtr_ == nullptr) { valuePtr_ = std::make_unique(value_); } - }; + bool value_; ///< Current ("original") value. + bool copy_; ///< Previous ("temporary") value. Not a pointer because bool is trivial and only takes 1 byte, while a pointer takes 8 bytes. public: /** @@ -33,63 +25,40 @@ class SafeBool : public SafeBase { * @param owner The contract that owns the variable. * @param value The initial value. Defaults to `false`. */ - SafeBool(DynamicContract* owner, bool value = false) - : SafeBase(owner), value_(false), valuePtr_(std::make_unique(value)) - {}; + explicit SafeBool(DynamicContract* owner, bool value = false) : SafeBase(owner), value_(value), copy_(value) {} /** * Empty constructor. * @param value The initial value. Defaults to `false`. */ - SafeBool(bool value = false) - : SafeBase(nullptr), value_(false), valuePtr_(std::make_unique(value)) - {}; + explicit SafeBool(bool value = false) : SafeBase(nullptr), value_(value), copy_(value) {} /// Copy constructor. - SafeBool(const SafeBool& other) : SafeBase(nullptr) { - other.check(); - value_ = other.value_; - valuePtr_ = std::make_unique(*other.valuePtr_); - } + SafeBool(const SafeBool& other) : SafeBase(nullptr), value_(other.value_), copy_(other.copy_) {} - /// Getter for the value. Returns the value from the pointer. - inline const bool& get() const { check(); return *valuePtr_; }; + /// Getter for the CURRENT value. + inline const bool& get() const { return this->value_; } - /// Explicit conversion operator used to get the value. - explicit operator bool() const { check(); return *valuePtr_; } + /// Explicit conversion operator. + explicit operator bool() const { return this->value_; } - /** - * Commit the value. Updates the value from the pointer, nullifies it and - * unregisters the variable. - */ - inline void commit() override { - check(); - value_ = *valuePtr_; - valuePtr_ = nullptr; - registered_ = false; - }; + ///@{ + /** Assignment operator. Assigns only the CURRENT value. */ + inline SafeBool& operator=(bool value) { markAsUsed(); this->value_ = value; return *this; } + inline SafeBool& operator=(const SafeBool& other) { markAsUsed(); this->value_ = other.get(); return *this; } + ///@} - /// Revert the value. Nullifies the pointer and unregisters the variable. - inline void revert() const override { - valuePtr_ = nullptr; - registered_ = false; - }; + ///@{ + /** Equality operator. Checks only the CURRENT value. */ + inline bool operator==(const bool& other) const { return (this->value_ == other); } + inline bool operator==(const SafeBool& other) const { return (this->value_ == other.get()); } + ///@} - /// Assignment operator. - inline SafeBool& operator=(bool value) { - check(); - markAsUsed(); - *valuePtr_ = value; - return *this; - } + /// Commit the value. + inline void commit() override { this->copy_ = this->value_; this->registered_ = false; } - /// Assignment operator. - inline SafeBool& operator=(const SafeBool& other) { - check(); - markAsUsed(); - *valuePtr_ = other.get(); - return *this; - } + /// Revert the value. + inline void revert() override { this->value_ = this->copy_; this->registered_ = false; } }; #endif // SAFEBOOL_H diff --git a/src/contract/variables/safebytes.h b/src/contract/variables/safebytes.h new file mode 100644 index 00000000..77636dab --- /dev/null +++ b/src/contract/variables/safebytes.h @@ -0,0 +1,513 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef SAFEBYTES_H +#define SAFEBYTES_H + +#include +#include +#include +#include + +#include "safebase.h" + +/** + * Safe wrapper for a raw bytes container. Derived from SafeVector. + * @see SafeVector + */ +class SafeBytes : public SafeBase { + private: + /** + * Enum for partial bytes modifying operations, used by the undo structure. + * Full operations are not included since doing any of them disables the + * use of the undo stack from that point until commit/revert. + * NOTE: RESIZE can be either partial or total - resize(0) = clear(), every other size (for now) is considered partial + */ + enum BytesOp { + AT, OPERATOR_BRACKETS, FRONT, BACK, INSERT, EMPLACE, ERASE, INSERT_BULK, ERASE_BULK, + PUSH_BACK, EMPLACE_BACK, POP_BACK, RESIZE_MORE, RESIZE_LESS + }; + + /// Helper alias for the undo operation structure (operation made, in which index, optionally which quantity, and one or more old values). + using UndoOp = std::tuple>; + + std::vector value_; ///< Current ("original") value. + std::unique_ptr> copy_; ///< Full copy of the current value. + std::unique_ptr>> undo_; ///< Undo stack of the current value. + + /// Undo all changes in the undo stack on top of the current value. + void processUndoStack() { + while (!this->undo_->empty()) { + const auto& op = this->undo_->top(); + const auto& [opType, index, quantity, oldVals] = op; + switch (opType) { + case AT: this->value_.at(index) = oldVals[0]; break; + case OPERATOR_BRACKETS: this->value_[index] = oldVals[0]; break; + case FRONT: this->value_.at(0) = oldVals[0]; break; + case BACK: this->value_.at(this->value_.size() - 1) = oldVals[0]; break; + case INSERT: + case EMPLACE: this->value_.erase(this->value_.begin() + index); break; + case ERASE: this->value_.insert(this->value_.begin() + index, oldVals[0]); break; + case INSERT_BULK: + for (std::size_t i = 0; i < quantity; i++) { + this->value_.erase(this->value_.begin() + index); + } + break; + case ERASE_BULK: + for (std::size_t i = 0; i < quantity; i++) { + this->value_.insert(this->value_.begin() + index + i, oldVals[i]); + } + break; + case PUSH_BACK: + case EMPLACE_BACK: this->value_.pop_back(); break; + case POP_BACK: this->value_.push_back(oldVals[0]); break; + // For resize(), treat index as quantity + case RESIZE_MORE: + for (std::size_t i = 0; i < index; i++) this->value_.pop_back(); break; + case RESIZE_LESS: + for (std::size_t i = 0; i < index; i++) this->value_.push_back(oldVals[i]); break; + break; + } + this->undo_->pop(); + } + } + + public: + /** + * Constructor with owner. + * @param owner The owner of the variable. + * @param bytes (optional) A vector of bytes to use during construction. Defaults to an empty vector. + */ + explicit SafeBytes(DynamicContract* owner, const std::vector& bytes = {}) + : SafeBase(owner), value_(bytes), copy_(nullptr), undo_(nullptr) {} + + /** + * Empty constructor. + * @param bytes (optional) A vector of bytes to use during construction. Defaults to an empty vector. + */ + explicit SafeBytes(const std::vector& bytes = {}) : SafeBase(nullptr), value_(bytes), copy_(nullptr), undo_(nullptr) {} + + /** + * Constructor with repeating value. + * @param count The number of copies to make. + * @param value The value to copy. + */ + SafeBytes(std::size_t count, const uint8_t& value) : + SafeBase(nullptr), value_(count, value), copy_(nullptr), undo_(nullptr) {} + + /** + * Constructor with empty repeating value. + * @param count The number of empty values to make. + */ + explicit SafeBytes(std::size_t count) : SafeBase(nullptr), value_(count), copy_(nullptr), undo_(nullptr) {} + + /** + * Constructor with iterators. + * @tparam InputIt Any iterator type. + * @param first An iterator to the first value. + * @param last An iterator to the last value. + */ + template SafeBytes(InputIt first, InputIt last) + : SafeBase(nullptr), value_(first, last), copy_(nullptr), undo_(nullptr) {} + + /** + * Constructor with initializer list. + * @param init The initializer list to use. + */ + SafeBytes(std::initializer_list init) + : SafeBase(nullptr), value_(init), copy_(nullptr), undo_(nullptr) {} + + /// Copy constructor. Only copies the CURRENT value. + SafeBytes(const SafeBytes& other) : SafeBase(nullptr), value_(other.value_), copy_(nullptr), undo_(nullptr) {} + + /// Get the inner vector (for const functions). + inline const std::vector& get() const { return this->value_; } + + /** + * Replace the contents of the bytes with copies of a value. + * @param count The number of copies to make. + * @param value The value to copy. + */ + inline void assign(std::size_t count, const uint8_t& value) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique>(this->value_); + markAsUsed(); this->value_.assign(count, value); + } + + /** + * Replace the contents of the bytes with elements from the input range [first, last). + * @tparam InputIt A type of iterator to the element. + * @param first An iterator to the first element. + * @param last An iterator to the last element. + */ + template inline void assign(InputIt first, InputIt last) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique>(this->value_); + markAsUsed(); this->value_.assign(first, last); + } + + /** + * Replace the contents with elements from an initializer list. + * @param ilist The initializer list to use. + */ + inline void assign(const std::initializer_list& ilist) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique>(this->value_); + markAsUsed(); this->value_.assign(ilist); + } + + ///@{ + /** + * Access a specified element of the bytes. + * @param pos The position of the index to access. + * @return The element at the given index. + * @throws std::out_of_range if pos is bigger than the bytes' size. + */ + inline uint8_t& at(std::size_t pos) { + uint8_t& ret = this->value_.at(pos); + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(BytesOp::AT, pos, 1, std::vector{this->value_.at(pos)}); + } + markAsUsed(); return ret; + } + inline const uint8_t& at(std::size_t pos) const { return this->value_.at(pos); } + ///@} + + ///@{ + /** + * Access a specified element of the bytes (without bounds checking). + * @param pos The position of the index to access. + * @return The element at the given index. + */ + inline uint8_t& operator[](std::size_t pos) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(BytesOp::OPERATOR_BRACKETS, pos, 1, std::vector{this->value_[pos]}); + } + markAsUsed(); return this->value_[pos]; + } + inline const uint8_t& operator[](std::size_t pos) const { return this->value_[pos]; } + ///@} + + ///@{ + /** Access the first element of the vector. */ + inline uint8_t& front() { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(BytesOp::FRONT, 0, 1, std::vector{this->value_.at(0)}); + } + markAsUsed(); return this->value_.front(); + } + inline const uint8_t& front() const { return this->value_.front(); } + ///@} + + ///@{ + /** Access the last element of the vector. */ + inline uint8_t& back() { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(BytesOp::BACK, this->value_.size() - 1, 1, std::vector{this->value_.at(this->value_.size() - 1)}); + } + markAsUsed(); return this->value_.back(); + } + inline const uint8_t& back() const { return this->value_.back(); } + ///@} + + /// Get a pointer to the underlying array serving as element storage. + inline const uint8_t* data() const { return this->value_.data(); } + + /// Get an iterator to the beginning of the bytes. + inline typename std::vector::const_iterator cbegin() const { return this->value_.cbegin(); } + + /// Get an iterator to the end of the bytes. + inline typename std::vector::const_iterator cend() const { return this->value_.cend(); } + + /// Get a reverse iterator to the beginning of the bytes. + inline typename std::vector::const_reverse_iterator crbegin() const { return this->value_.crbegin(); } + + /// Get a reverse iterator to the end of the bytes. + inline typename std::vector::const_reverse_iterator crend() const { return this->value_.crend(); } + + /// Check if the bytes is empty. + inline bool empty() const { return this->value_.empty(); } + + /// Get the bytes' current size. + inline std::size_t size() const { return this->value_.size(); } + + /// Get the bytes' maximum size. + inline std::size_t max_size() const { return this->value_.max_size(); } + + /** + * Reserve space for a new cap of items in the bytes, if the new cap is + * greater than current capacity. + * Does NOT change the bytes' size or contents, therefore we don't + * consider it for a copy or undo operation. + * @param new_cap The new cap for the bytes. + */ + inline void reserve(std::size_t new_cap) { markAsUsed(); this->value_.reserve(new_cap); } + + /// Get the number of items the bytes has currently allocated space for. + inline std::size_t capacity() const { return this->value_.capacity(); } + + /** + * Reduce unused capacity on the bytes to fit the current size. + * Does NOT change the bytes's size or contents, therefore we don't + * consider it for a copy or undo operation. + */ + inline void shrink_to_fit() { markAsUsed(); this->value_.shrink_to_fit(); } + + /// Clear the bytes. + inline void clear() { + if (this->copy_ == nullptr) this->copy_ = std::make_unique>(this->value_); + markAsUsed(); this->value_.clear(); + } + + /** + * Insert an element into the bytes. + * @param pos The position to insert. + * @param value The element to insert. + * @return An iterator to the element that was inserted. + */ + typename std::vector::const_iterator insert(typename std::vector::const_iterator pos, const uint8_t& value) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + std::size_t index = std::distance(this->value_.cbegin(), pos); + this->undo_->emplace(BytesOp::INSERT, index, 1, std::vector()); + } + markAsUsed(); return this->value_.insert(pos, value); + } + + /** + * Insert an element into the bytes, using move. + * @param pos The position to insert. + * @param value The element to insert. + * @return An iterator to the element that was inserted. + */ + typename std::vector::const_iterator insert(typename std::vector::const_iterator pos, uint8_t&& value) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + std::size_t index = std::distance(this->value_.cbegin(), pos); + this->undo_->emplace(BytesOp::INSERT, index, 1, std::vector()); + } + markAsUsed(); return this->value_.insert(pos, value); + } + + /** + * Insert a repeated number of the same element into the bytes. + * @param pos The position to insert. + * @param count The number of times to insert. + * @param value The element to insert. + * @return An iterator to the first element that was inserted. + */ + typename std::vector::const_iterator insert(typename std::vector::const_iterator pos, std::size_t count, const uint8_t& value) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + std::size_t index = std::distance(this->value_.cbegin(), pos); + this->undo_->emplace(BytesOp::INSERT_BULK, index, count, std::vector()); + } + markAsUsed(); return this->value_.insert(pos, count, value); + } + + /** + * Insert a range of elements into the bytes using iterators. + * @param pos The position to insert. + * @param first An iterator to the first value. + * @param last An iterator to the last value. + * @return An iterator to the first element that was inserted. + */ + template requires std::input_iterator typename std::vector::const_iterator insert( + typename std::vector::const_iterator pos, InputIt first, InputIt last + ) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + std::size_t index = std::distance(this->value_.cbegin(), pos); + std::size_t diff = std::distance(first, last); + this->undo_->emplace(BytesOp::INSERT_BULK, index, diff, std::vector()); + } + markAsUsed(); return this->value_.insert(pos, first, last); + } + + /** + * Insert a list of elements into the bytes. + * @param pos The position to insert. + * @param ilist The list of elements to insert. + * @return An iterator to the first element that was inserted. + */ + typename std::vector::const_iterator insert(typename std::vector::const_iterator pos, std::initializer_list ilist) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + std::size_t index = std::distance(this->value_.cbegin(), pos); + this->undo_->emplace(BytesOp::INSERT_BULK, index, ilist.size(), std::vector()); + } + markAsUsed(); return this->value_.insert(pos, ilist); + } + + /** + * Emplace (construct in-place) an element into the bytes. + * @param pos The position to emplace. + * @param args The element to emplace. + * @return An iterator to the element that was emplaced. + */ + template typename std::vector::const_iterator emplace(typename std::vector::const_iterator pos, Args&&... args) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(BytesOp::EMPLACE, std::distance(this->value_.cbegin(), pos), 1, std::vector()); + } + markAsUsed(); return this->value_.emplace(pos, args...); + } + + /** + * Erase an element from the bytes. + * @param pos The index of the element to erase. + * @return An iterator to the element after the removed one. + */ + typename std::vector::const_iterator erase(typename std::vector::const_iterator pos) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + std::size_t index = std::distance(this->value_.cbegin(), pos); + this->undo_->emplace(BytesOp::ERASE, index, 1, std::vector{this->value_.at(index)}); + } + markAsUsed(); return this->value_.erase(pos); + } + + /** + * Erase a range of elements from the bytes. + * @param first An iterator to the first value. + * @param last An iterator to the last value. + * @return An iterator to the element after the last removed one. + */ + typename std::vector::const_iterator erase( + typename std::vector::const_iterator first, typename std::vector::const_iterator last + ) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + std::size_t index = std::distance(this->value_.cbegin(), first); + std::size_t diff = std::distance(first, last); + auto oldVals = std::vector(first, last); + this->undo_->emplace(BytesOp::ERASE_BULK, index, diff, oldVals); + } + markAsUsed(); return this->value_.erase(first, last); + } + + /** + * Append an element to the end of the bytes. + * @param value The value to append. + */ + void push_back(const uint8_t& value) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(BytesOp::PUSH_BACK, 0, 0, std::vector()); + } + markAsUsed(); this->value_.push_back(value); + } + + /** + * Append an element to the end of the bytes, using move. + * @param value The value to append. + */ + void push_back(uint8_t&& value) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(BytesOp::PUSH_BACK, 0, 0, std::vector()); + } + markAsUsed(); this->value_.push_back(value); + } + + /** + * Emplace an element at the end of the bytes. + * @param value The value to emplace. + */ + uint8_t& emplace_back(uint8_t&& value) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(BytesOp::EMPLACE_BACK, 0, 0, std::vector()); + } + markAsUsed(); return this->value_.emplace_back(value); + } + + /// Erase the element at the end of the bytes. + void pop_back() { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(BytesOp::POP_BACK, 0, 0, std::vector{this->value_.back()}); + } + markAsUsed(); this->value_.pop_back(); + } + + /** + * Resize the bytes to hold a given number of elements. + * Default-constructed elements are appended. + * @param count The number of items for the new size. + */ + void resize(std::size_t count) { + if (this->copy_ == nullptr) { + if (count == 0) { // Treat as full operation if resize(0), otherwise treat as partial + this->copy_ = std::make_unique>(this->value_); + } else if (count != this->value_.size()) { // Only consider undo if size will actually change + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + BytesOp bytesOp; // RESIZE_MORE (will be bigger) or RESIZE_LESS (will be smaller) + std::size_t diff = 0; // Size difference between old and new vector + std::vector vals = {}; // Old values from before the operation + if (count > this->value_.size()) { + bytesOp = BytesOp::RESIZE_MORE; + diff = count - this->value_.size(); + } else if (count < this->value_.size()) { + bytesOp = BytesOp::RESIZE_LESS; + diff = this->value_.size() - count; + vals = std::vector(this->value_.end() - diff, this->value_.end()); + } + this->undo_->emplace(bytesOp, diff, 0, vals); + } + } + markAsUsed(); this->value_.resize(count); + } + + /** + * Resize the bytes to hold a given number of elements. + * If new size is bigger, new elements are appended and initialized. + * @param count The number of items for the new size. + * @param value The value to append and initialize. + */ + void resize(std::size_t count, const uint8_t& value) { + if (this->copy_ == nullptr) { + if (count == 0) { // Treat as full operation if resize(0), otherwise treat as partial + this->copy_ = std::make_unique>(this->value_); + } else if (count != this->value_.size()) { // Only consider undo if size will actually change + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + BytesOp bytesOp; // RESIZE_MORE (will be bigger) or RESIZE_LESS (will be smaller) + std::size_t diff = 0; // Size difference between old and new vector + std::vector vals = {}; // Old values from before the operation + if (count > this->value_.size()) { + bytesOp = BytesOp::RESIZE_MORE; + diff = count - this->value_.size(); + } else if (count < this->value_.size()) { + bytesOp = BytesOp::RESIZE_LESS; + diff = this->value_.size() - count; + vals = std::vector(this->value_.end() - diff, this->value_.end()); + } + this->undo_->emplace(bytesOp, diff, 0, vals); + } + } + markAsUsed(); this->value_.resize(count, value); + } + + ///@{ + /** Equality operator. Checks only the CURRENT value. */ + inline bool operator==(const std::vector& other) const { return (this->value_ == other); } + inline bool operator==(const SafeBytes& other) const { return (this->value_ == other.get()); } + ///@} + + /// Commit the value. + void commit() override { this->copy_ = nullptr; this->undo_ = nullptr; this->registered_ = false; } + + /// Revert the value. + void revert() override { + if (this->copy_ != nullptr) this->value_ = *this->copy_; + if (this->undo_ != nullptr && !this->undo_->empty()) this->processUndoStack(); + this->copy_ = nullptr; this->undo_ = nullptr; this->registered_ = false; + } +}; + +#endif // SAFEBYTES_H diff --git a/src/contract/variables/safeint.cpp b/src/contract/variables/safeint.cpp new file mode 100644 index 00000000..9fbf2466 --- /dev/null +++ b/src/contract/variables/safeint.cpp @@ -0,0 +1,409 @@ +#include "safeint.h" + +// Explicit instantiations + +template class SafeInt_t<8>; +template class SafeInt_t<16>; +template class SafeInt_t<32>; +template class SafeInt_t<64>; + +template class SafeInt_t<24>; +template class SafeInt_t<40>; +template class SafeInt_t<48>; +template class SafeInt_t<56>; +template class SafeInt_t<72>; +template class SafeInt_t<80>; +template class SafeInt_t<88>; +template class SafeInt_t<96>; +template class SafeInt_t<104>; +template class SafeInt_t<112>; +template class SafeInt_t<120>; +template class SafeInt_t<128>; +template class SafeInt_t<136>; +template class SafeInt_t<144>; +template class SafeInt_t<152>; +template class SafeInt_t<160>; +template class SafeInt_t<168>; +template class SafeInt_t<176>; +template class SafeInt_t<184>; +template class SafeInt_t<192>; +template class SafeInt_t<200>; +template class SafeInt_t<208>; +template class SafeInt_t<216>; +template class SafeInt_t<224>; +template class SafeInt_t<232>; +template class SafeInt_t<240>; +template class SafeInt_t<248>; +template class SafeInt_t<256>; + +// Class impl starts here + +template requires IntInterval +SafeInt_t SafeInt_t::operator+(const int_t& other) const { + if ((other > 0) && (this->value_ > std::numeric_limits::max() - other)) { + throw std::overflow_error("Overflow in addition operation."); + } + if ((other < 0) && (this->value_ < std::numeric_limits::min() - other)) { + throw std::underflow_error("Underflow in addition operation."); + } + return SafeInt_t(this->value_ + other); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator+(const SafeInt_t& other) const { + if ((other.get() > 0) && (this->value_ > std::numeric_limits::max() - other.get())) { + throw std::overflow_error("Overflow in addition operation."); + } + if ((other.get() < 0) && (this->value_ < std::numeric_limits::min() - other.get())) { + throw std::underflow_error("Underflow in addition operation."); + } + return SafeInt_t(this->value_ + other.get()); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator-(const int_t& other) const { + if ((other < 0) && (this->value_ > std::numeric_limits::max() + other)) { + throw std::overflow_error("Overflow in subtraction operation."); + } + if ((other > 0) && (this->value_ < std::numeric_limits::min() + other)) { + throw std::underflow_error("Underflow in subtraction operation."); + } + return SafeInt_t(this->value_ - other); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator-(const SafeInt_t& other) const { + if ((other.get() < 0) && (this->value_ > std::numeric_limits::max() + other.get())) { + throw std::overflow_error("Overflow in subtraction operation."); + } + if ((other.get() > 0) && (this->value_ < std::numeric_limits::min() + other.get())) { + throw std::underflow_error("Underflow in subtraction operation."); + } + return SafeInt_t(this->value_ - other.get()); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator*(const int_t& other) const { + if (this->value_ == 0 || other == 0) { + throw std::domain_error("Multiplication by zero."); + } + if (this->value_ > std::numeric_limits::max() / other) { + throw std::overflow_error("Overflow in multiplication operation."); + } + if (this->value_ < std::numeric_limits::min() / other) { + throw std::underflow_error("Underflow in multiplication operation."); + } + return SafeInt_t(this->value_ * other); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator*(const SafeInt_t& other) const { + if (this->value_ == 0 || other.get() == 0) { + throw std::domain_error("Multiplication by zero."); + } + if (this->value_ > std::numeric_limits::max() / other.get()) { + throw std::overflow_error("Overflow in multiplication operation."); + } + if (this->value_ < std::numeric_limits::min() / other.get()) { + throw std::underflow_error("Underflow in multiplication operation."); + } + return SafeInt_t(this->value_ * other.get()); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator/(const int_t& other) const { + if (other == 0) throw std::domain_error("Division by zero"); + // Edge case - dividing the smallest negative number by -1 causes overflow + if (this->value_ == std::numeric_limits::min() && other == -1) { + throw std::overflow_error("Overflow in division operation."); + } + return SafeInt_t(this->value_ / other); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator/(const SafeInt_t& other) const { + if (other.get() == 0) throw std::domain_error("Division by zero"); + // Edge case - dividing the smallest negative number by -1 causes overflow + if (this->value_ == std::numeric_limits::min() && other.get() == -1) { + throw std::overflow_error("Overflow in division operation."); + } + return SafeInt_t(this->value_ / other.get()); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator%(const int_t& other) const { + if (other == 0) throw std::domain_error("Modulus by zero"); + return SafeInt_t(this->value_ % other); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator%(const SafeInt_t& other) const { + if (other.get() == 0) throw std::domain_error("Modulus by zero"); + return SafeInt_t(this->value_ % other.get()); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator&(const int_t& other) const { + return SafeInt_t(this->value_ & other); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator&(const SafeInt_t& other) const { + return SafeInt_t(this->value_ & other.get()); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator|(const int_t& other) const { + return SafeInt_t(this->value_ | other); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator|(const SafeInt_t& other) const { + return SafeInt_t(this->value_ | other.get()); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator^(const int_t& other) const { + return SafeInt_t(this->value_ ^ other); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator^(const SafeInt_t& other) const { + return SafeInt_t(this->value_ ^ other.get()); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator<<(const uint8_t& other) const { + return SafeInt_t(this->value_ << other); +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator>>(const uint8_t& other) const { + return SafeInt_t(this->value_ >> other); +} + +template requires IntInterval +SafeInt_t::operator bool() const { return bool(this->value_); } + +template requires IntInterval +bool SafeInt_t::operator==(const int_t& other) const { return (this->value_ == other); } + +template requires IntInterval +bool SafeInt_t::operator==(const SafeInt_t& other) const { return (this->value_ == other.get()); } + +template requires IntInterval +bool SafeInt_t::operator!=(const int_t& other) const { return (this->value_ != other); } + +template requires IntInterval +bool SafeInt_t::operator!=(const SafeInt_t& other) const { return (this->value_ != other.get()); } + +template requires IntInterval +bool SafeInt_t::operator<(const int_t& other) const { return (this->value_ < other); } + +template requires IntInterval +bool SafeInt_t::operator<(const SafeInt_t& other) const { return (this->value_ < other.get()); } + +template requires IntInterval +bool SafeInt_t::operator<=(const int_t& other) const { return (this->value_ <= other); } + +template requires IntInterval +bool SafeInt_t::operator<=(const SafeInt_t& other) const { return (this->value_ <= other.get()); } + +template requires IntInterval +bool SafeInt_t::operator>(const int_t& other) const { return (this->value_ > other); } + +template requires IntInterval +bool SafeInt_t::operator>(const SafeInt_t& other) const { return (this->value_ > other.get()); } + +template requires IntInterval +bool SafeInt_t::operator>=(const int_t& other) const { return (this->value_ >= other); } + +template requires IntInterval +bool SafeInt_t::operator>=(const SafeInt_t& other) const { return (this->value_ >= other.get()); } + +template requires IntInterval +SafeInt_t& SafeInt_t::operator=(const int_t& other) { + markAsUsed(); this->value_ = other; return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator=(const SafeInt_t& other) { + markAsUsed(); this->value_ = other.get(); return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator+=(const int_t& other) { + if ((other > 0) && (this->value_ > std::numeric_limits::max() - other)) { + throw std::overflow_error("Overflow in addition assignment operation."); + } + if ((other < 0) && (this->value_ < std::numeric_limits::min() - other)) { + throw std::underflow_error("Underflow in addition assignment operation."); + } + markAsUsed(); this->value_ += other; return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator+=(const SafeInt_t& other) { + if ((other.get() > 0) && (this->value_ > std::numeric_limits::max() - other.get())) { + throw std::overflow_error("Overflow in addition assignment operation."); + } + if ((other.get() < 0) && (this->value_ < std::numeric_limits::min() - other.get())) { + throw std::underflow_error("Underflow in addition assignment operation."); + } + markAsUsed(); this->value_ += other.get(); return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator-=(const int_t& other) { + if ((other < 0) && (this->value_ > std::numeric_limits::max() + other)) { + throw std::overflow_error("Overflow in subtraction assignment operation."); + } + if ((other > 0) && (this->value_ < std::numeric_limits::min() + other)) { + throw std::underflow_error("Underflow in subtraction assignment operation."); + } + markAsUsed(); this->value_ -= other; return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator-=(const SafeInt_t& other) { + if ((other.get() < 0) && (this->value_ > std::numeric_limits::max() + other.get())) { + throw std::overflow_error("Overflow in subtraction assignment operation."); + } + if ((other.get() > 0) && (this->value_ < std::numeric_limits::min() + other.get())) { + throw std::underflow_error("Underflow in subtraction assignment operation."); + } + markAsUsed(); this->value_ -= other.get(); return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator*=(const int_t& other) { + if (this->value_ == 0 || other == 0) { + throw std::domain_error("Multiplication by zero."); + } + if (this->value_ > std::numeric_limits::max() / other) { + throw std::overflow_error("Overflow in multiplication assignment operation."); + } + if (this->value_ < std::numeric_limits::min() / other) { + throw std::underflow_error("Underflow in multiplication assignment operation."); + } + markAsUsed(); this->value_ *= other; return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator*=(const SafeInt_t& other) { + if (this->value_ == 0 || other.get() == 0) { + throw std::domain_error("Multiplication by zero."); + } + if (this->value_ > std::numeric_limits::max() / other.get()) { + throw std::overflow_error("Overflow in multiplication assignment operation."); + } + if (this->value_ < std::numeric_limits::min() / other.get()) { + throw std::underflow_error("Underflow in multiplication assignment operation."); + } + markAsUsed(); this->value_ *= other.get(); return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator/=(const int_t& other) { + if (other == 0) throw std::domain_error("Division assignment by zero."); + // Handling the edge case where dividing the smallest negative number by -1 causes overflow + if (this->value_ == std::numeric_limits::min() && other == -1) { + throw std::overflow_error("Overflow in division assignment operation."); + } + markAsUsed(); this->value_ /= other; return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator/=(const SafeInt_t& other) { + if (other.get() == 0) throw std::domain_error("Division assignment by zero."); + // Handling the edge case where dividing the smallest negative number by -1 causes overflow + if (this->value_ == std::numeric_limits::min() && other.get() == -1) { + throw std::overflow_error("Overflow in division assignment operation."); + } + markAsUsed(); this->value_ /= other.get(); return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator%=(const int_t& other) { + if (other == 0) throw std::domain_error("Modulus assignment by zero."); + markAsUsed(); this->value_ %= other; return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator%=(const SafeInt_t& other) { + if (other.get() == 0) throw std::domain_error("Modulus assignment by zero."); + markAsUsed(); this->value_ %= other.get(); return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator&=(const int_t& other) { + markAsUsed(); this->value_ &= other; return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator&=(const SafeInt_t& other) { + markAsUsed(); this->value_ &= other.get(); return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator|=(const int_t& other) { + markAsUsed(); this->value_ |= other; return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator|=(const SafeInt_t& other) { + markAsUsed(); this->value_ |= other.get(); return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator^=(const int_t& other) { + markAsUsed(); this->value_ ^= other; return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator^=(const SafeInt_t& other) { + markAsUsed(); this->value_ ^= other.get(); return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator<<=(const uint8_t& other) { + markAsUsed(); this->value_ <<= other; return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator>>=(const uint8_t& other) { + markAsUsed(); this->value_ >>= other; return *this; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator++() { + if (this->value_ == std::numeric_limits::max()) { + throw std::overflow_error("Overflow in prefix increment operation."); + } + markAsUsed(); ++(this->value_); return *this; +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator++(int) { + if (this->value_ == std::numeric_limits::max()) { + throw std::overflow_error("Overflow in postfix increment operation."); + } + markAsUsed(); SafeInt_t temp(this->value_); ++(this->value_); return temp; +} + +template requires IntInterval +SafeInt_t& SafeInt_t::operator--() { + if (this->value_ == std::numeric_limits::min()) { + throw std::underflow_error("Underflow in prefix decrement operation."); + } + markAsUsed(); --(this->value_); return *this; +} + +template requires IntInterval +SafeInt_t SafeInt_t::operator--(int) { + if (this->value_ == std::numeric_limits::min()) { + throw std::underflow_error("Underflow in postfix decrement operation."); + } + markAsUsed(); SafeInt_t temp(this->value_); --(this->value_); return temp; +} + diff --git a/src/contract/variables/safeint.h b/src/contract/variables/safeint.h index c6fd7ff1..3639a414 100644 --- a/src/contract/variables/safeint.h +++ b/src/contract/variables/safeint.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,7 +8,6 @@ See the LICENSE.txt file in the project root for more information. #ifndef SAFEINT_T_H #define SAFEINT_T_H -#include #include #include "safebase.h" @@ -44,800 +43,498 @@ template <> struct IntType<64> { }; /** - * SafeInt_t class template. + * Safe wrapper for an int_t variable. * @tparam Size The size of the int. */ template class SafeInt_t : public SafeBase { private: using int_t = typename IntType::type; ///< The type of the int. - int_t value_; ///< The value of the int. - mutable std::unique_ptr valuePtr_; ///< The pointer to the value of the int. - - /// Check if the value is registered_ and if not, register it. - inline void check() const override { - if (valuePtr_ == nullptr) valuePtr_ = std::make_unique(value_); - }; + int_t value_; ///< Current ("original") value. + int_t copy_; ///< Previous ("temporary") value. public: static_assert(Size >= 8 && Size <= 256 && Size % 8 == 0, "Size must be between 8 and 256 and a multiple of 8."); /** * Constructor. - * @param owner The DynamicContract that owns this variable. * @param value The initial value of the variable. Defaults to 0. */ - SafeInt_t(DynamicContract* owner, const int_t& value = 0) - : SafeBase(owner), value_(0), valuePtr_(std::make_unique(value)) - {}; + explicit SafeInt_t(const int_t& value = 0) : SafeBase(nullptr), value_(value), copy_(value) {} /** - * Constructor. + * Constructor with owner. + * @param owner The DynamicContract that owns this variable. * @param value The initial value of the variable. Defaults to 0. */ - explicit SafeInt_t(const int_t& value = 0) - : SafeBase(nullptr), value_(0), valuePtr_(std::make_unique(value)) - {}; + explicit SafeInt_t(DynamicContract* owner, const int_t& value = 0) : SafeBase(owner), value_(value), copy_(value) {} - /** - * Copy constructor. - * @param other The SafeInt_t to copy. - */ - SafeInt_t(const SafeInt_t& other) : SafeBase(nullptr) { - other.check(); value_ = 0; valuePtr_ = std::make_unique(*other.valuePtr_); - }; - - /// Getter for the temporary value. - inline int_t get() const { check(); return *valuePtr_; }; + /// Copy constructor. Only copies the CURRENT value. + SafeInt_t(const SafeInt_t& other) : SafeBase(nullptr), value_(other.value_), copy_(other.value_) {} - /// Commit the value. - inline void commit() override { check(); value_ = *valuePtr_; valuePtr_ = nullptr; registered_ = false; }; - - /// Revert the value. - inline void revert() const override { valuePtr_ = nullptr; registered_ = false; }; + /// Getter for the value. + inline const int_t& get() const { return this->value_; } + ///@{ /** * Addition operator. - * @param other The SafeInt_t to add. + * @param other The integer to add. * @throw std::overflow_error if an overflow happens. * @throw std::underflow_error if an underflow happens. * @return A new SafeInt_t with the result of the addition. */ - inline SafeInt_t operator+(const SafeInt_t& other) const { - check(); - if ((other.get() > 0) && (*valuePtr_ > std::numeric_limits::max() - other.get())) { + inline SafeInt_t operator+(const int_t& other) const { + if ((other > 0) && (this->value_ > std::numeric_limits::max() - other)) { throw std::overflow_error("Overflow in addition operation."); } - if ((other.get() < 0) && (*valuePtr_ < std::numeric_limits::min() - other.get())) { + if ((other < 0) && (this->value_ < std::numeric_limits::min() - other)) { throw std::underflow_error("Underflow in addition operation."); } - return SafeInt_t(*valuePtr_ + other.get()); + return SafeInt_t(this->value_ + other); } - - /** - * Addition operator. - * @param other The int_t to add. - * @throw std::overflow_error if an overflow happens. - * @throw std::underflow_error if an underflow happens. - * @return A new SafeInt_t with the result of the addition. - */ - inline SafeInt_t operator+(const int_t& other) const { - check(); - if ((other > 0) && (*valuePtr_ > std::numeric_limits::max() - other)) { + inline SafeInt_t operator+(const SafeInt_t& other) const { + if ((other.get() > 0) && (this->value_ > std::numeric_limits::max() - other.get())) { throw std::overflow_error("Overflow in addition operation."); } - if ((other < 0) && (*valuePtr_ < std::numeric_limits::min() - other)) { + if ((other.get() < 0) && (this->value_ < std::numeric_limits::min() - other.get())) { throw std::underflow_error("Underflow in addition operation."); } - return SafeInt_t(*valuePtr_ + other); + return SafeInt_t(this->value_ + other.get()); } + ///@} + ///@{ /** * Subtraction operator. - * @param other The SafeInt_t to subtract. + * @param other The integer to subtract. * @throw std::overflow_error if an overflow happens. * @throw std::underflow_error if an underflow happens. * @return A new SafeInt_t with the result of the subtraction. */ - inline SafeInt_t operator-(const SafeInt_t& other) const { - check(); - if ((other.get() < 0) && (*valuePtr_ > std::numeric_limits::max() + other.get())) { + inline SafeInt_t operator-(const int_t& other) const { + if ((other < 0) && (this->value_ > std::numeric_limits::max() + other)) { throw std::overflow_error("Overflow in subtraction operation."); } - if ((other.get() > 0) && (*valuePtr_ < std::numeric_limits::min() + other.get())) { + if ((other > 0) && (this->value_ < std::numeric_limits::min() + other)) { throw std::underflow_error("Underflow in subtraction operation."); } - return SafeInt_t(*valuePtr_ - other.get()); + return SafeInt_t(this->value_ - other); } - - /** - * Subtraction operator. - * @param other The int_t to subtract. - * @throw std::overflow_error if an overflow happens. - * @throw std::underflow_error if an underflow happens. - * @return A new SafeInt_t with the result of the subtraction. - */ - inline SafeInt_t operator-(const int_t& other) const { - check(); - if ((other < 0) && (*valuePtr_ > std::numeric_limits::max() + other)) { + inline SafeInt_t operator-(const SafeInt_t& other) const { + if ((other.get() < 0) && (this->value_ > std::numeric_limits::max() + other.get())) { throw std::overflow_error("Overflow in subtraction operation."); } - if ((other > 0) && (*valuePtr_ < std::numeric_limits::min() + other)) { + if ((other.get() > 0) && (this->value_ < std::numeric_limits::min() + other.get())) { throw std::underflow_error("Underflow in subtraction operation."); } - return SafeInt_t(*valuePtr_ - other); + return SafeInt_t(this->value_ - other.get()); } + ///@} + ///@{ /** * Multiplication operator. - * @param other The SafeInt_t to multiply. + * @param other The integer to multiply. * @throw std::overflow_error if an overflow happens. * @throw std::underflow_error if an underflow happens. * @throw std::domain_error if multiplying by 0. * @return A new SafeInt_t with the result of the multiplication. */ - inline SafeInt_t operator*(const SafeInt_t& other) const { - check(); - if (*valuePtr_ == 0 || other.get() == 0) { + inline SafeInt_t operator*(const int_t& other) const { + if (this->value_ == 0 || other == 0) { throw std::domain_error("Multiplication by zero."); } - if (*valuePtr_ > std::numeric_limits::max() / other.get()) { + if (this->value_ > std::numeric_limits::max() / other) { throw std::overflow_error("Overflow in multiplication operation."); } - if (*valuePtr_ < std::numeric_limits::min() / other.get()) { + if (this->value_ < std::numeric_limits::min() / other) { throw std::underflow_error("Underflow in multiplication operation."); } - return SafeInt_t(*valuePtr_ * other.get()); + return SafeInt_t(this->value_ * other); } - - /** - * Multiplication operator. - * @param other The int_t to multiply. - * @throw std::overflow_error if an overflow happens. - * @throw std::underflow_error if an underflow happens. - * @throw std::domain_error if multiplying by 0. - * @return A new SafeInt_t with the result of the multiplication. - */ - inline SafeInt_t operator*(const int_t& other) const { - check(); - if (*valuePtr_ == 0 || other == 0) { + inline SafeInt_t operator*(const SafeInt_t& other) const { + if (this->value_ == 0 || other.get() == 0) { throw std::domain_error("Multiplication by zero."); } - if (*valuePtr_ > std::numeric_limits::max() / other) { + if (this->value_ > std::numeric_limits::max() / other.get()) { throw std::overflow_error("Overflow in multiplication operation."); } - if (*valuePtr_ < std::numeric_limits::min() / other) { + if (this->value_ < std::numeric_limits::min() / other.get()) { throw std::underflow_error("Underflow in multiplication operation."); } - return SafeInt_t(*valuePtr_ * other); + return SafeInt_t(this->value_ * other.get()); } + ///@} + ///@{ /** * Division operator. - * @param other The SafeInt_t to divide. - * @throw std::domain_error if the other value is zero. - * @throw std::overflow_error if division results in overflow. - * @return A new SafeInt_t with the result of the division. - */ - inline SafeInt_t operator/(const SafeInt_t& other) const { - check(); - if (other.get() == 0) throw std::domain_error("Division by zero"); - - // Handling the edge case where dividing the smallest negative number by -1 causes overflow - if (*valuePtr_ == std::numeric_limits::min() && other.get() == -1) { - throw std::overflow_error("Overflow in division operation."); - } - - return SafeInt_t(*valuePtr_ / other.get()); - } - - /** - * Division operator. - * @param other The int_t to divide. + * @param other The integer to divide. * @throw std::domain_error if the other value is zero. * @throw std::overflow_error if division results in overflow. * @return A new SafeInt_t with the result of the division. */ inline SafeInt_t operator/(const int_t& other) const { - check(); if (other == 0) throw std::domain_error("Division by zero"); - - // Handling the edge case where dividing the smallest negative number by -1 causes overflow - if (*valuePtr_ == std::numeric_limits::min() && other == -1) { + // Edge case - dividing the smallest negative number by -1 causes overflow + if (this->value_ == std::numeric_limits::min() && other == -1) { throw std::overflow_error("Overflow in division operation."); } - - return SafeInt_t(*valuePtr_ / other); + return SafeInt_t(this->value_ / other); } - - /** - * Modulus operator. - * @param other The SafeInt_t to take the modulus by. - * @throw std::domain_error if the other value is zero. - * @return A new SafeInt_t with the result of the modulus. - */ - inline SafeInt_t operator%(const SafeInt_t& other) const { - check(); - if (other.get() == 0) throw std::domain_error("Modulus by zero"); - - return SafeInt_t(*valuePtr_ % other.get()); + inline SafeInt_t operator/(const SafeInt_t& other) const { + if (other.get() == 0) throw std::domain_error("Division by zero"); + // Edge case - dividing the smallest negative number by -1 causes overflow + if (this->value_ == std::numeric_limits::min() && other.get() == -1) { + throw std::overflow_error("Overflow in division operation."); + } + return SafeInt_t(this->value_ / other.get()); } + ///@} + ///@{ /** * Modulus operator. - * @param other The int_t to take the modulus by. + * @param other The integer to take the modulus of. * @throw std::domain_error if the other value is zero. * @return A new SafeInt_t with the result of the modulus. */ inline SafeInt_t operator%(const int_t& other) const { - check(); if (other == 0) throw std::domain_error("Modulus by zero"); - - return SafeInt_t(*valuePtr_ % other); + return SafeInt_t(this->value_ % other); } - - /** - * Bitwise AND operator. - * @param other The SafeInt_t to AND. - * @return A new SafeInt_t with the result of the AND. - */ - inline SafeInt_t operator&(const SafeInt_t& other) const { - check(); - return SafeInt_t(*valuePtr_ & other.get()); + inline SafeInt_t operator%(const SafeInt_t& other) const { + if (other.get() == 0) throw std::domain_error("Modulus by zero"); + return SafeInt_t(this->value_ % other.get()); } + ///@} + ///@{ /** * Bitwise AND operator. - * @param other The int_t to AND. + * @param other The integer to apply AND. * @return A new SafeInt_t with the result of the AND. */ inline SafeInt_t operator&(const int_t& other) const { - check(); - return SafeInt_t(*valuePtr_ & other); + return SafeInt_t(this->value_ & other); } - - /** - * Bitwise OR operator. - * @param other The SafeInt_t to OR. - * @return A new SafeInt_t with the result of the OR. - */ - inline SafeInt_t operator|(const SafeInt_t& other) const { - check(); - return SafeInt_t(*valuePtr_ | other.get()); + inline SafeInt_t operator&(const SafeInt_t& other) const { + return SafeInt_t(this->value_ & other.get()); } + ///@} + ///@{ /** * Bitwise OR operator. - * @param other The int_t to OR. + * @param other The integer to apply OR. * @return A new SafeInt_t with the result of the OR. */ inline SafeInt_t operator|(const int_t& other) const { - check(); - return SafeInt_t(*valuePtr_ | other); + return SafeInt_t(this->value_ | other); } - - /** - * Bitwise XOR operator. - * @param other The SafeInt_t to XOR. - * @return A new SafeInt_t with the result of the XOR. - */ - inline SafeInt_t operator^(const SafeInt_t& other) const { - check(); - return SafeInt_t(*valuePtr_ ^ other.get()); + inline SafeInt_t operator|(const SafeInt_t& other) const { + return SafeInt_t(this->value_ | other.get()); } + ///@} + ///@{ /** * Bitwise XOR operator. - * @param other The int_t to XOR. + * @param other The integer to apply XOR. * @return A new SafeInt_t with the result of the XOR. */ inline SafeInt_t operator^(const int_t& other) const { - check(); - return SafeInt_t(*valuePtr_ ^ other); + return SafeInt_t(this->value_ ^ other); } - - /** - * Left shift operator. - * @param other The SafeInt_t indicating the number of positions to shift. - * @return A new SafeInt_t with the result of the shift. - */ - inline SafeInt_t operator<<(const SafeInt_t& other) const { - check(); - return SafeInt_t(*valuePtr_ << other.get()); + inline SafeInt_t operator^(const SafeInt_t& other) const { + return SafeInt_t(this->value_ ^ other.get()); } + ///@} - /** - * Left shift operator. - * @param other The int_t indicating the number of positions to shift. - * @return A new SafeInt_t with the result of the shift. - */ - inline SafeInt_t operator<<(const int_t& other) const { - check(); - return SafeInt_t(*valuePtr_ << other); - } + // NOTE: Boost types (anything that's not 8, 16, 32 or 64) do not support + // bit shifting with their own types (e.g. `i >> int256_t(2)`). + // Because of that, uint8_t is forcibly used instead for all types, given + // anything bigger than `i >> 31` yields a compiler warning (= "undefined behaviour"). /** - * Right shift operator. - * @param other The SafeInt_t indicating the number of positions to shift. + * Left shift operator. + * @param other The integer indicating the number of positions to shift. * @return A new SafeInt_t with the result of the shift. */ - inline SafeInt_t operator>>(const SafeInt_t& other) const { - check(); - return SafeInt_t(*valuePtr_ >> other.get()); + inline SafeInt_t operator<<(const uint8_t& other) const { + return SafeInt_t(this->value_ << other); } /** * Right shift operator. - * @param other The int_t indicating the number of positions to shift. + * @param other The integer indicating the number of positions to shift. * @return A new SafeInt_t with the result of the shift. */ - inline SafeInt_t operator>>(const int_t& other) const { - check(); - return SafeInt_t(*valuePtr_ >> other); - } - - /** - * Logical NOT operator. - * @return True if the value is zero, false otherwise. - */ - inline bool operator!() const { - check(); - return (!*valuePtr_); - } - - /** - * Logical AND operator. - * @param other The SafeInt_t to AND. - * @return True if both values are non-zero, false otherwise. - */ - inline bool operator&&(const SafeInt_t& other) const { - check(); - return (*valuePtr_ && other.get()); - } - - /** - * Logical AND operator. - * @param other The int_t to AND. - * @return True if both values are non-zero, false otherwise. - */ - inline bool operator&&(const int_t& other) const { - check(); - return (*valuePtr_ && other); - } - - /** - * Logical OR operator. - * @param other The SafeInt_t to OR. - * @return True if either value is non-zero, false otherwise. - */ - inline bool operator||(const SafeInt_t& other) const { - check(); - return (*valuePtr_ || other.get()); - } - - /** - * Logical OR operator. - * @param other The int_t to OR. - * @return True if either value is non-zero, false otherwise. - */ - inline bool operator||(const int_t& other) const { - check(); - return (*valuePtr_ || other); + inline SafeInt_t operator>>(const uint8_t& other) const { + return SafeInt_t(this->value_ >> other); } /** - * Equality operator. - * @param other The SafeInt_t to compare to. - * @return True if the values are equal, false otherwise. + * Boolean operator + * @return `true` if the value is non-zero, `false` otherwise. */ - inline bool operator==(const SafeInt_t& other) const { - check(); - return (*valuePtr_ == other.get()); - } + inline explicit operator bool() const { return bool(this->value_); } + ///@{ /** * Equality operator. - * @param other The int_t to compare to. - * @return True if the values are equal, false otherwise. + * @param other The integer to compare. + * @return `true` if the values are equal, `false` otherwise. */ - inline bool operator==(const int_t& other) const { - check(); - return (*valuePtr_ == other); - } + inline bool operator==(const int_t& other) const { return (this->value_ == other); } + inline bool operator==(const SafeInt_t& other) const { return (this->value_ == other.get()); } + ///@} + ///@{ /** - * Less than operator. - * @param other The SafeInt_t to compare to. - * @return True if the value is less than the other value, false otherwise. + * Inequality operator. + * @param other The integer to compare. + * @return `true` if the values are not equal, `false` otherwise. */ - inline bool operator<(const SafeInt_t& other) const { - check(); - return (*valuePtr_ < other.get()); - } + inline bool operator!=(const int_t& other) const { return (this->value_ != other); } + inline bool operator!=(const SafeInt_t& other) const { return (this->value_ != other.get()); } + ///@} + ///@{ /** * Less than operator. - * @param other The int_t to compare to. - * @return True if the value is less than the other value, false otherwise. + * @param other The integer to compare. + * @return `true` if the value is less than the other value, `false` otherwise. */ - inline bool operator<(const int_t& other) const { - check(); - return (*valuePtr_ < other); - } + inline bool operator<(const int_t& other) const { return (this->value_ < other); } + inline bool operator<(const SafeInt_t& other) const { return (this->value_ < other.get()); } + ///@} + ///@{ /** * Less than or equal to operator. - * @param other The SafeInt_t to compare to. - * @return True if the value is less than or equal to the other value, false otherwise. + * @param other The integer to compare. + * @return `true` if the value is less than or equal to the other value, `false` otherwise. */ - inline bool operator<=(const SafeInt_t& other) const { - check(); - return (*valuePtr_ <= other.get()); - } - - /** - * Less than or equal to operator. - * @param other The int_t to compare to. - * @return True if the value is less than or equal to the other value, false otherwise. - */ - inline bool operator<=(const int_t& other) const { - check(); - return (*valuePtr_ <= other); - } + inline bool operator<=(const int_t& other) const { return (this->value_ <= other); } + inline bool operator<=(const SafeInt_t& other) const { return (this->value_ <= other.get()); } + ///@} + ///@{ /** * Greater than operator. - * @param other The SafeInt_t to compare to. - * @return True if the value is greater than the other value, false otherwise. + * @param other The integer to compare. + * @return `true` if the value is greater than the other value, `false` otherwise. */ - inline bool operator>(const SafeInt_t& other) const { - check(); - return (*valuePtr_ > other.get()); - } - - /** - * Greater than operator. - * @param other The int_t to compare to. - * @return True if the value is greater than the other value, false otherwise. - */ - inline bool operator>(const int_t& other) const { - check(); - return (*valuePtr_ > other); - } - - /** - * Greater than or equal to operator. - * @param other The SafeInt_t to compare to. - * @return True if the value is greater than or equal to the other value, false otherwise. - */ - inline bool operator>=(const SafeInt_t& other) const { - check(); - return (*valuePtr_ >= other.get()); - } + inline bool operator>(const int_t& other) const { return (this->value_ > other); } + inline bool operator>(const SafeInt_t& other) const { return (this->value_ > other.get()); } + ///@} + ///@{ /** * Greater than or equal to operator. - * @param other The int_t to compare to. - * @return True if the value is greater than or equal to the other value, false otherwise. - */ - inline bool operator>=(const int_t& other) const { - check(); - return (*valuePtr_ >= other); - } - - /** - * Assignment operator. - * @param other The SafeInt_t to assign. - * @return A reference to this SafeInt_t. + * @param other The integer to compare. + * @return `true` if the value is greater than or equal to the other value, `false` otherwise. */ - inline SafeInt_t& operator=(const SafeInt_t& other) { - check(); - markAsUsed(); - *valuePtr_ = other.get(); - return *this; - } + inline bool operator>=(const int_t& other) const { return (this->value_ >= other); } + inline bool operator>=(const SafeInt_t& other) const { return (this->value_ >= other.get()); } + ///@} + ///@{ /** * Assignment operator. - * @param other The int_t to assign. + * @param other The integer to assign. * @return A reference to this SafeInt_t. */ inline SafeInt_t& operator=(const int_t& other) { - check(); - markAsUsed(); - *valuePtr_ = other; - return *this; + markAsUsed(); this->value_ = other; return *this; + } + inline SafeInt_t& operator=(const SafeInt_t& other) { + markAsUsed(); this->value_ = other.get(); return *this; } + ///@} + ///@{ /** * Addition assignment operator. - * @param other The SafeInt_t to add. + * @param other The integer to add. * @return A reference to this SafeInt_t. */ - inline SafeInt_t& operator+=(const SafeInt_t& other) { - check(); - if ((other.get() > 0) && (*valuePtr_ > std::numeric_limits::max() - other.get())) { + inline SafeInt_t& operator+=(const int_t& other) { + if ((other > 0) && (this->value_ > std::numeric_limits::max() - other)) { throw std::overflow_error("Overflow in addition assignment operation."); } - if ((other.get() < 0) && (*valuePtr_ < std::numeric_limits::min() - other.get())) { + if ((other < 0) && (this->value_ < std::numeric_limits::min() - other)) { throw std::underflow_error("Underflow in addition assignment operation."); } - markAsUsed(); - *valuePtr_ += other.get(); - return *this; + markAsUsed(); this->value_ += other; return *this; } - - /** - * Addition assignment operator. - * @param other The int_t to add. - * @return A reference to this SafeInt_t. - */ - inline SafeInt_t& operator+=(const int_t& other) { - check(); - if ((other > 0) && (*valuePtr_ > std::numeric_limits::max() - other)) { + inline SafeInt_t& operator+=(const SafeInt_t& other) { + if ((other.get() > 0) && (this->value_ > std::numeric_limits::max() - other.get())) { throw std::overflow_error("Overflow in addition assignment operation."); } - if ((other < 0) && (*valuePtr_ < std::numeric_limits::min() - other)) { + if ((other.get() < 0) && (this->value_ < std::numeric_limits::min() - other.get())) { throw std::underflow_error("Underflow in addition assignment operation."); } - markAsUsed(); - *valuePtr_ += other; - return *this; + markAsUsed(); this->value_ += other.get(); return *this; } + ///@} + ///@{ /** * Subtraction assignment operator. - * @param other The SafeInt_t to subtract. + * @param other The integer to subtract. * @return A reference to this SafeInt_t. */ - inline SafeInt_t& operator-=(const SafeInt_t& other) { - check(); - if ((other.get() < 0) && (*valuePtr_ > std::numeric_limits::max() + other.get())) { + inline SafeInt_t& operator-=(const int_t& other) { + if ((other < 0) && (this->value_ > std::numeric_limits::max() + other)) { throw std::overflow_error("Overflow in subtraction assignment operation."); } - if ((other.get() > 0) && (*valuePtr_ < std::numeric_limits::min() + other.get())) { + if ((other > 0) && (this->value_ < std::numeric_limits::min() + other)) { throw std::underflow_error("Underflow in subtraction assignment operation."); } - markAsUsed(); - *valuePtr_ -= other.get(); - return *this; + markAsUsed(); this->value_ -= other; return *this; } - - /** - * Subtraction assignment operator. - * @param other The int_t to subtract. - * @return A reference to this SafeInt_t. - */ - inline SafeInt_t& operator-=(const int_t& other) { - check(); - if ((other < 0) && (*valuePtr_ > std::numeric_limits::max() + other)) { + inline SafeInt_t& operator-=(const SafeInt_t& other) { + if ((other.get() < 0) && (this->value_ > std::numeric_limits::max() + other.get())) { throw std::overflow_error("Overflow in subtraction assignment operation."); } - if ((other > 0) && (*valuePtr_ < std::numeric_limits::min() + other)) { + if ((other.get() > 0) && (this->value_ < std::numeric_limits::min() + other.get())) { throw std::underflow_error("Underflow in subtraction assignment operation."); } - markAsUsed(); - *valuePtr_ -= other; - return *this; + markAsUsed(); this->value_ -= other.get(); return *this; } + ///@} + ///@{ /** * Multiplication assignment operator. - * @param other The SafeInt_t to multiply. + * @param other The integer to multiply. * @return A reference to this SafeInt_t. */ - inline SafeInt_t& operator*=(const SafeInt_t& other) { - check(); - if (*valuePtr_ > std::numeric_limits::max() / other.get()) { + inline SafeInt_t& operator*=(const int_t& other) { + if (this->value_ == 0 || other == 0) { + throw std::domain_error("Multiplication by zero."); + } + if (this->value_ > std::numeric_limits::max() / other) { throw std::overflow_error("Overflow in multiplication assignment operation."); } - if (*valuePtr_ < std::numeric_limits::min() / other.get()) { + if (this->value_ < std::numeric_limits::min() / other) { throw std::underflow_error("Underflow in multiplication assignment operation."); } - markAsUsed(); - *valuePtr_ *= other.get(); - return *this; + markAsUsed(); this->value_ *= other; return *this; } - - /** - * Multiplication assignment operator. - * @param other The int_t to multiply. - * @return A reference to this SafeInt_t. - */ - inline SafeInt_t& operator*=(const int_t& other) { - check(); - if (*valuePtr_ > std::numeric_limits::max() / other) { + inline SafeInt_t& operator*=(const SafeInt_t& other) { + if (this->value_ == 0 || other.get() == 0) { + throw std::domain_error("Multiplication by zero."); + } + if (this->value_ > std::numeric_limits::max() / other.get()) { throw std::overflow_error("Overflow in multiplication assignment operation."); } - if (*valuePtr_ < std::numeric_limits::min() / other) { + if (this->value_ < std::numeric_limits::min() / other.get()) { throw std::underflow_error("Underflow in multiplication assignment operation."); } - markAsUsed(); - *valuePtr_ *= other; - return *this; + markAsUsed(); this->value_ *= other.get(); return *this; } + ///@} + ///@{ /** * Division assignment operator. - * @param other The SafeInt_t to divide. + * @param other The integer to divide. * @return A reference to this SafeInt_t. */ - inline SafeInt_t& operator/=(const SafeInt_t& other) { - check(); - if (other.get() == 0) throw std::domain_error("Division assignment by zero."); - + inline SafeInt_t& operator/=(const int_t& other) { + if (other == 0) throw std::domain_error("Division assignment by zero."); // Handling the edge case where dividing the smallest negative number by -1 causes overflow - if (*valuePtr_ == std::numeric_limits::min() && other.get() == -1) { + if (this->value_ == std::numeric_limits::min() && other == -1) { throw std::overflow_error("Overflow in division assignment operation."); } - - markAsUsed(); - *valuePtr_ /= other.get(); - return *this; + markAsUsed(); this->value_ /= other; return *this; } - - /** - * Division assignment operator. - * @param other The int_t to divide. - * @return A reference to this SafeInt_t. - */ - inline SafeInt_t& operator/=(const int_t& other) { - check(); - if (other == 0) throw std::domain_error("Division assignment by zero."); - + inline SafeInt_t& operator/=(const SafeInt_t& other) { + if (other.get() == 0) throw std::domain_error("Division assignment by zero."); // Handling the edge case where dividing the smallest negative number by -1 causes overflow - if (*valuePtr_ == std::numeric_limits::min() && other == -1) { + if (this->value_ == std::numeric_limits::min() && other.get() == -1) { throw std::overflow_error("Overflow in division assignment operation."); } - - markAsUsed(); - *valuePtr_ /= other; - return *this; + markAsUsed(); this->value_ /= other.get(); return *this; } + ///@} + ///@{ /** * Modulus assignment operator. * @param other The SafeInt_t to take the modulus by. * @return A reference to this SafeInt_t. */ - inline SafeInt_t& operator%=(const SafeInt_t& other) { - check(); - if (other.get() == 0) throw std::domain_error("Modulus assignment by zero."); - markAsUsed(); - *valuePtr_ %= other.get(); - return *this; - } - - /** - * Modulus assignment operator. - * @param other The int_t to take the modulus by. - * @return A reference to this SafeInt_t. - */ inline SafeInt_t& operator%=(const int_t& other) { - check(); if (other == 0) throw std::domain_error("Modulus assignment by zero."); - markAsUsed(); - *valuePtr_ %= other; - return *this; + markAsUsed(); this->value_ %= other; return *this; } - - /** - * Bitwise AND assignment operator. - * @param other The SafeInt_t to AND. - * @return A reference to this SafeInt_t. - */ - inline SafeInt_t& operator&=(const SafeInt_t& other) { - check(); - markAsUsed(); - *valuePtr_ &= other.get(); - return *this; + inline SafeInt_t& operator%=(const SafeInt_t& other) { + if (other.get() == 0) throw std::domain_error("Modulus assignment by zero."); + markAsUsed(); this->value_ %= other.get(); return *this; } + ///@} + ///@{ /** * Bitwise AND assignment operator. - * @param other The int_t to AND. + * @param other The integer to apply AND. * @return A reference to this SafeInt_t. */ inline SafeInt_t& operator&=(const int_t& other) { - check(); - markAsUsed(); - *valuePtr_ &= other; - return *this; + markAsUsed(); this->value_ &= other; return *this; } - - /** - * Bitwise OR assignment operator. - * @param other The SafeInt_t to OR. - * @return A reference to this SafeInt_t. - */ - inline SafeInt_t& operator|=(const SafeInt_t& other) { - check(); - markAsUsed(); - *valuePtr_ |= other.get(); - return *this; + inline SafeInt_t& operator&=(const SafeInt_t& other) { + markAsUsed(); this->value_ &= other.get(); return *this; } + ///@} + ///@{ /** * Bitwise OR assignment operator. - * @param other The int_t to OR. + * @param other The integer to apply OR. * @return A reference to this SafeInt_t. */ inline SafeInt_t& operator|=(const int_t& other) { - check(); - markAsUsed(); - *valuePtr_ |= other; - return *this; + markAsUsed(); this->value_ |= other; return *this; } - - /** - * Bitwise XOR assignment operator. - * @param other The SafeInt_t to XOR. - * @return A reference to this SafeInt_t. - */ - inline SafeInt_t& operator^=(const SafeInt_t& other) { - check(); - markAsUsed(); - *valuePtr_ ^= other.get(); - return *this; + inline SafeInt_t& operator|=(const SafeInt_t& other) { + markAsUsed(); this->value_ |= other.get(); return *this; } + ///@} + ///@{ /** * Bitwise XOR assignment operator. - * @param other The int_t to XOR. + * @param other The integer to apply XOR. * @return A reference to this SafeInt_t. */ inline SafeInt_t& operator^=(const int_t& other) { - check(); - markAsUsed(); - *valuePtr_ ^= other; - return *this; + markAsUsed(); this->value_ ^= other; return *this; } - - /** - * Left shift assignment operator. - * @param other The SafeInt_t indicating the number of positions to shift. - * @return A reference to this SafeInt_t. - */ - inline SafeInt_t& operator<<=(const SafeInt_t& other) { - check(); - markAsUsed(); - *valuePtr_ <<= other.get(); - return *this; + inline SafeInt_t& operator^=(const SafeInt_t& other) { + markAsUsed(); this->value_ ^= other.get(); return *this; } + ///@} /** * Left shift assignment operator. - * @param other The int_t indicating the number of positions to shift. - * @return A reference to this SafeInt_t. - */ - inline SafeInt_t& operator<<=(const int_t& other) { - check(); - markAsUsed(); - *valuePtr_ <<= other; - return *this; - } - - /** - * Right shift assignment operator. - * @param other The SafeInt_t indicating the number of positions to shift. + * @param other The integer indicating the number of positions to shift. * @return A reference to this SafeInt_t. */ - inline SafeInt_t& operator>>=(const SafeInt_t& other) { - check(); - markAsUsed(); - *valuePtr_ >>= other.get(); - return *this; + inline SafeInt_t& operator<<=(const uint8_t& other) { + markAsUsed(); this->value_ <<= other; return *this; } /** * Right shift assignment operator. - * @param other The int_t indicating the number of positions to shift. + * @param other The integer indicating the number of positions to shift. * @return A reference to this SafeInt_t. */ - inline SafeInt_t& operator>>=(const int_t& other) { - check(); - markAsUsed(); - *valuePtr_ >>= other; - return *this; + inline SafeInt_t& operator>>=(const uint8_t& other) { + markAsUsed(); this->value_ >>= other; return *this; } /** @@ -845,13 +542,10 @@ template class SafeInt_t : public SafeBase { * @return A reference to this SafeInt_t. */ inline SafeInt_t& operator++() { - check(); - if (*valuePtr_ == std::numeric_limits::max()) { + if (this->value_ == std::numeric_limits::max()) { throw std::overflow_error("Overflow in prefix increment operation."); } - markAsUsed(); - ++(*valuePtr_); - return *this; + markAsUsed(); ++(this->value_); return *this; } /** @@ -859,14 +553,10 @@ template class SafeInt_t : public SafeBase { * @return A new SafeInt_t with the value of this SafeInt_t before the increment. */ inline SafeInt_t operator++(int) { - check(); - if (*valuePtr_ == std::numeric_limits::max()) { + if (this->value_ == std::numeric_limits::max()) { throw std::overflow_error("Overflow in postfix increment operation."); } - markAsUsed(); - SafeInt_t temp(*valuePtr_); - ++(*valuePtr_); - return temp; + markAsUsed(); SafeInt_t temp(this->value_); ++(this->value_); return temp; } /** @@ -874,13 +564,10 @@ template class SafeInt_t : public SafeBase { * @return A reference to this SafeInt_t. */ inline SafeInt_t& operator--() { - check(); - if (*valuePtr_ == std::numeric_limits::min()) { + if (this->value_ == std::numeric_limits::min()) { throw std::underflow_error("Underflow in prefix decrement operation."); } - markAsUsed(); - --(*valuePtr_); - return *this; + markAsUsed(); --(this->value_); return *this; } /** @@ -888,15 +575,54 @@ template class SafeInt_t : public SafeBase { * @return A new SafeInt_t with the value of this SafeInt_t before the decrement. */ inline SafeInt_t operator--(int) { - check(); - if (*valuePtr_ == std::numeric_limits::min()) { + if (this->value_ == std::numeric_limits::min()) { throw std::underflow_error("Underflow in postfix decrement operation."); } - markAsUsed(); - SafeInt_t temp(*valuePtr_); - --(*valuePtr_); - return temp; + markAsUsed(); SafeInt_t temp(this->value_); --(this->value_); return temp; + } + + /// Commit the value. + inline void commit() override { this->copy_ = this->value_; this->registered_ = false; } + + /// Revert the value. + inline void revert() override { + this->value_ = this->copy_; + this->registered_ = false; } -}; // class SafeInt_t +}; + +// Aliases for SafeInt +using SafeInt8_t = SafeInt_t<8>; +using SafeInt16_t = SafeInt_t<16>; +using SafeInt24_t = SafeInt_t<24>; +using SafeInt32_t = SafeInt_t<32>; +using SafeInt40_t = SafeInt_t<40>; +using SafeInt48_t = SafeInt_t<48>; +using SafeInt56_t = SafeInt_t<56>; +using SafeInt64_t = SafeInt_t<64>; +using SafeInt72_t = SafeInt_t<72>; +using SafeInt80_t = SafeInt_t<80>; +using SafeInt88_t = SafeInt_t<88>; +using SafeInt96_t = SafeInt_t<96>; +using SafeInt104_t = SafeInt_t<104>; +using SafeInt112_t = SafeInt_t<112>; +using SafeInt120_t = SafeInt_t<120>; +using SafeInt128_t = SafeInt_t<128>; +using SafeInt136_t = SafeInt_t<136>; +using SafeInt144_t = SafeInt_t<144>; +using SafeInt152_t = SafeInt_t<152>; +using SafeInt160_t = SafeInt_t<160>; +using SafeInt168_t = SafeInt_t<168>; +using SafeInt176_t = SafeInt_t<176>; +using SafeInt184_t = SafeInt_t<184>; +using SafeInt192_t = SafeInt_t<192>; +using SafeInt200_t = SafeInt_t<200>; +using SafeInt208_t = SafeInt_t<208>; +using SafeInt216_t = SafeInt_t<216>; +using SafeInt224_t = SafeInt_t<224>; +using SafeInt232_t = SafeInt_t<232>; +using SafeInt240_t = SafeInt_t<240>; +using SafeInt248_t = SafeInt_t<248>; +using SafeInt256_t = SafeInt_t<256>; #endif // SAFEINT_T_H diff --git a/src/contract/variables/safemultiset.h b/src/contract/variables/safemultiset.h new file mode 100644 index 00000000..1299b773 --- /dev/null +++ b/src/contract/variables/safemultiset.h @@ -0,0 +1,300 @@ +/* +Copyright (c) [2023] [Sparq Network] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef SAFEMULTISET_H +#define SAFEMULTISET_H + +#include +#include +#include +#include "safebase.h" + +// TODO: missing functions (not sure if necessary): +// merge, equal_range, operator==, operator<=> + +/** + * Safe wrapper for std::multiset. + * @tparam Key Defines the type of the set key element. + * @tparam Compare Defines the function object for performing comparisons. + * Defaults to std::less (elements ordered from lower to higher). + * @see SafeBase + */ +template > // TODO: Allocator? Do we need it? +class SafeMultiSet : public SafeBase { + private: + std::multiset value_; ///< Current ("original") value. + std::multiset copy_; ///< Previous ("temporary") value. + public: + /// Default Constructor. + SafeMultiSet() : SafeBase(nullptr) {}; + + /** + * Constructor with owner. + * @param owner The owner of the variable. + */ + SafeMultiSet(DynamicContract* owner) : SafeBase(owner) {}; + + /** + * Constructor with iterators. + * @param first Iterator to the start of the data. + * @param last Iterator to the end of the data. + */ + template SafeMultiSet(InputIt first, InputIt last) { + this->value_.insert(first, last); + this->copy_.insert(first, last); + } + + /** + * Constructor with initializer list. + * @param init The initializer list. + */ + SafeMultiSet(std::initializer_list init) { + for (const auto& val : init) { this->value_.emplace(val); this->copy_.emplace(val); } + } + + /// Copy constructor. only copies CURRENT value. + SafeMultiSet(const SafeMultiSet& other) : value_(other.value_), copy_(other.value_) { + } + /// Return the value set const begin(). + inline typename std::multiset::iterator begin() { this->markAsUsed(); return this->value_.begin(); }; + + /// Return the value set const end(). + inline typename std::multiset::iterator end() { this->markAsUsed(); return this->value_.end(); }; + + /// Return the value set const crbegin(). + inline typename std::multiset::reverse_iterator rbegin() { this->markAsUsed(); return this->value_.rbegin(); }; + + /// Return the value set const crend(). + inline typename std::multiset::reverse_iterator rend() { this->markAsUsed(); return this->value_.rend(); }; + + /// Return the value set const begin(). + inline typename std::multiset::const_iterator cbegin() const { return this->value_.cbegin(); } + + /// Return the value set const end(). + inline typename std::multiset::const_iterator cend() const { return this->value_.cend(); } + + /// Return the value set const crbegin(). + inline typename std::multiset::const_reverse_iterator crbegin() const { return this->value_.crbegin(); } + + /// Return the value set const crend(). + inline typename std::multiset::const_reverse_iterator crend() const { return this->value_.crend(); } + + /// Check if value is empty. + inline bool empty() const { return this->value_.empty(); } + + /// Get temporary set size. + inline std::size_t size() const { return this->value_.size(); } + + /// Get temporary set max_size. + inline std::size_t max_size() const { return this->value_.max_size(); } + + /// Clear sets. + inline void clear() { this->markAsUsed(); this->value_.clear(); }; + + /** + * Insert an element into the set. + * @param value The value to insert + * @return An iterator to the inserted position. + */ + typename std::multiset::iterator insert(const Key& value) { + this->markAsUsed(); return this->value_.insert(value); + } + + /// Move overload for insert(). + typename std::multiset::iterator insert(Key&& value) { + this->markAsUsed(); return this->value_.insert(std::move(value)); + } + + /// Iterator overload for insert(). + typename std::multiset::iterator insert( + typename std::multiset::const_iterator pos, const Key& value + ) { + this->markAsUsed(); return this->value_.insert(pos, value); + } + + /// Iterator move overload for insert(). + typename std::multiset::iterator insert( + typename std::multiset::const_iterator pos, Key&& value + ) { + this->markAsUsed(); return this->value_.insert(pos, std::move(value)); + } + + /// Range iterator overload for insert(). + template void insert(InputIt first, InputIt last) { + this->markAsUsed(); this->value_.insert(first, last); + } + + /// Initializer list overload for insert(). + void insert(std::initializer_list ilist) { + this->markAsUsed(); this->value_.insert(ilist); + } + + /** + * Construct elements inplace in the temporary set. + * @param args The elements to insert. + * @return An iterator to the last inserted element. + */ + template typename std::multiset::iterator emplace(Args&&... args) { + this->markAsUsed(); return this->value_.emplace(std::forward(args)...); + } + + /** + * Construct elements inplace using a hint. + * @param hint The hint as to where to insert the elements. + * @param args The elements to insert. + * @return An iterator to the last inserted element. +n */ + template typename std::multiset::iterator emplace_hint( + typename std::multiset::const_iterator hint, Args&&... args + ) { + this->markAsUsed(); return this->value_.emplace_hint(hint, std::forward(args)...); + } + + /** + * Erase an element from the set. + * @param pos An iterator to the element to be erased. + * @return An iterator to the first element following the erased one. + */ + typename std::multiset::const_iterator erase(typename std::multiset::const_iterator pos) { + this->markAsUsed(); return this->value_.erase(pos); + } + + /// Ranged overload of erase(). Erases [ first, last ) . + typename std::multiset::const_iterator erase( + typename std::multiset::const_iterator first, typename std::multiset::const_iterator last + ) { + this->markAsUsed(); return this->value_.erase(first, last); + } + + /// Element-specific overload of erase(). Returns the number of erased elements. + size_t erase(const Key& key) { + this->markAsUsed(); return this->value_.erase(key); + } + + /** + * Swap elements with another set. Swaps only elements from the TEMPORARY sets. + * @param other The set to swap with. + */ + void swap(SafeMultiSet& other) { + this->markAsUsed(); this->value_.swap(other.value_); + } + + /** + * Extract an element from the temporary set. Get the value itself with `.value()`. + * @param pos An iterator to the element to be extracted. + * @return The extracted element. + */ + typename std::multiset::node_type extract(typename std::multiset::iterator pos) { + this->markAsUsed(); return this->value_.extract(pos); + } + + /// Element-specific overload of extract(), copy-wise. + typename std::multiset::node_type extract(const Key& x) { + this->markAsUsed(); return this->value_.extract(x); + } + + /// Element-specific overload of extract(), move-wise. + typename std::multiset::node_type extract(Key&& x) { + this->markAsUsed(); return this->value_.extract(std::move(x)); + } + + /** + * Count the number of elements that exist in the set. + * @param key The key value to count. + * @return The number of found elements. + */ + size_t count(const Key& key) const { return this->value_.count(key); } + + /** + * Find an element in the temporary set. + * @param key The key to search for. + * @return An iterator to the found element. + */ + typename std::multiset::iterator find(const Key& key) { + this->markAsUsed(); return this->value_.find(key); + } + + + /** + * Find an element in the temporary set. (CONST OVERLOAD) + * @param key The key to search for. + * @return An iterator to the found element. + */ + typename std::multiset::const_iterator find(const Key& key) const { + return this->value_.find(key); + } + + /** + * Check if the set contains a given element. + * @param key The key to check. + * @return `true` if set contains the key, `false` otherwise. + */ + bool contains(const Key& key) const { return this->value_.contains(key); } + + /** + * Get the first element that is not less than the given one. + * @param key The key to use for comparison. + * @return An iterator to the found element. + */ + typename std::multiset::const_iterator lower_bound(const Key& key) { + return this->value_.lower_bound(key); + } + + /** + * Get the first element that is greater than the given one. + * @param key The key to use for comparison. + * @return An iterator to the found element. + */ + typename std::multiset::const_iterator upper_bound(const Key& key) { + return this->value_.upper_bound(key); + } + + /// Get the function that compares the keys (same as value_comp). + typename std::multiset::key_compare key_comp() const { + return this->value_.key_comp(); + } + + /// Get the function that compares the values (same as key_comp). + typename std::multiset::value_compare value_comp() const { + return this->value_.value_comp(); + } + + /** + * Erase all elements that satisfy the predicate from the container. + * @param pred The predicate that returns `true` if the element should be erased. + * @return The number of erased elements. + */ + template size_t erase_if(Pred pred) { + this->markAsUsed(); + size_t oldSize = this->value_.size(); + for (auto it = this->value_.begin(); it != this->value_.end();) { + if (pred(*it)) { + it = this->value_.erase(it); + } else { + ++it; + } + } + return oldSize - this->value_.size(); + } + + /// Commit function. + void commit() override { + this->copy_ = this->value_; + this->registered_ = false; + } + + /// Rollback function. + void revert() override { + this->value_ = this->copy_; + this->registered_ = false; + } + + /// Get the inner set (for const functions!) + inline const std::multiset& get() const { return this->value_; } +}; + +#endif // SAFEMULTISET_H diff --git a/src/contract/variables/safestring.h b/src/contract/variables/safestring.h index f28e06fa..6732e79a 100644 --- a/src/contract/variables/safestring.h +++ b/src/contract/variables/safestring.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -10,23 +10,18 @@ See the LICENSE.txt file in the project root for more information. #include #include +#include #include "safebase.h" /** - * Safe wrapper for a string variable. - * Used to safely store a string within a contract. + * Safe wrapper for a `std::string`. Used to safely store a string within a contract. * @see SafeBase */ class SafeString : public SafeBase { private: - std::string str_; ///< Value. - mutable std::unique_ptr strPtr_; ///< Pointer to the value. check() requires this to be mutable. - - /// Check if the pointer is initialized (and initialize it if not). - void check() const override { - if (strPtr_ == nullptr) strPtr_ = std::make_unique(str_); - } + std::string value_; ///< Current ("original") value. + std::unique_ptr copy_; ///< Previous ("temporary") value. public: /** @@ -34,49 +29,43 @@ class SafeString : public SafeBase { * @param owner The contract that owns the variable. * @param str The initial value. Defaults to an empty string. */ - SafeString(DynamicContract *owner, const std::string& str = std::string()) - : SafeBase(owner), strPtr_(std::make_unique(str)) + explicit SafeString(DynamicContract *owner, const std::string& str = std::string()) + : SafeBase(owner), value_(str), copy_(nullptr) {}; /// Empty constructor. Initializes an empty string. - SafeString() : SafeBase(nullptr), strPtr_(std::make_unique()) {}; + SafeString() : SafeBase(nullptr), value_(std::string()), copy_(nullptr) {} /** * Non-owning constructor. - * @param str The string initial value. + * @param str The initial value. */ - explicit SafeString(const std::string& str) - : SafeBase(nullptr), strPtr_(std::make_unique(str)) - {}; + explicit SafeString(const std::string& str) : SafeBase(nullptr), value_(str), copy_(nullptr) {} - /// Copy constructor. - SafeString(const SafeString& other) : SafeBase(nullptr) { - other.check(); strPtr_ = std::make_unique(*other.strPtr_); - } + /// Copy constructor. Only copies the CURRENT value. + SafeString(const SafeString& other) : SafeBase(nullptr), value_(other.value_), copy_(nullptr) {} - /// Getter for the value. Returns the value from the pointer. - inline const std::string& get() const { check(); return *strPtr_; } + /// Getter for the CURRENT value. + inline const std::string& get() const { return this->value_; } /** - * Commit the value. Updates the value from the pointer, nullifies it and - * unregisters the variable. + * Assign a new value from another string. + * @param str The string to assign. + * @return The new value. */ - inline void commit() override { check(); str_ = *strPtr_; registered_ = false; } - - /// Revert the value. Nullifies the pointer and unregisters the variable. - inline void revert() const override { strPtr_ = nullptr; registered_ = false; } + inline SafeString& assign(const std::string& str) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.assign(str); return *this; + } /** - * Assign a new value from a number of chars. - * @param count The number of characters to assign. - * @param ch The character to fill the string with. + * Assign a new value from another string, using move + * @param str The string to assign. * @return The new value. */ - inline SafeString& assign(size_t count, char ch) { - check(); - markAsUsed(); - strPtr_->assign(count, ch); - return *this; + inline SafeString& assign(std::string&& str) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.assign(std::move(str)); return *this; } /** @@ -85,10 +74,22 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& assign(const SafeString& str) { - check(); - markAsUsed(); - strPtr_->assign(str.get()); - return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.assign(str.get()); return *this; + } + + /** + * Assign a new value from another substring. + * @param str The string to use for replacement. + * @param pos The position of the first character to be assigned. + * @param count The number of characters of the substring to use. + * If the string itself is shorter, it will use as many + * characters as possible. Defaults to the end of the string. + * @return The new value. + */ + inline SafeString& assign(const std::string& str, size_t pos, size_t count = std::string::npos) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.assign(str, pos, count); return *this; } /** @@ -101,10 +102,19 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& assign(const SafeString& str, size_t pos, size_t count = std::string::npos) { - check(); - markAsUsed(); - strPtr_->assign(str.get(), pos, count); - return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.assign(str.get(), pos, count); return *this; + } + + /** + * Assign a new value from a number of chars. + * @param count The number of characters to assign. + * @param ch The character to fill the string with. + * @return The new value. + */ + inline SafeString& assign(size_t count, char ch) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.assign(count, ch); return *this; } /** @@ -114,10 +124,8 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& assign(const char* s, size_t count) { - check(); - markAsUsed(); - strPtr_->assign(s, count); - return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.assign(s, count); return *this; } /** @@ -126,23 +134,20 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& assign(const char* s) { - check(); - markAsUsed(); - strPtr_->assign(s); - return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.assign(s); return *this; } /** * Assign a new value from input iterators. + * @tparam InputIt Any iterator type. * @param first An iterator pointing to the start of the input. * @param last An iterator pointing to the end of the input. * @return The new value. */ template inline SafeString& assign(InputIt first, InputIt last) { - check(); - markAsUsed(); - strPtr_->assign(first, last); - return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.assign(first, last); return *this; } /** @@ -151,113 +156,151 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& assign(std::initializer_list ilist) { - check(); - markAsUsed(); - strPtr_->assign(ilist); - return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.assign(ilist); return *this; } + ///@{ /** * Get a character from a specified position. * @param pos The position of the character. * @return The requsted character. + * @throws std::out_of_range if pos is bigger than the string's size. */ - inline char& at(size_t pos) { check(); markAsUsed(); return strPtr_->at(pos); } - - /// Const overload for at(). - inline const char& at(size_t pos) const { check(); return strPtr_->at(pos); } - - /// Get the first character from the string. - inline char& front() { check(); markAsUsed(); return strPtr_->front(); } - - /// Const overload for front(). - inline const char& front() const { check(); return strPtr_->front(); } - - /// Get the last character from the string. - inline char& back() { check(); markAsUsed(); return strPtr_->back(); } + inline char& at(size_t pos) { + char& ret = this->value_.at(pos); + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); return ret; + } + inline const char& at(size_t pos) const { return this->value_.at(pos); } + ///@} - /// Const overload for back(). - inline const char& back() const { check(); return strPtr_->back(); } + ///@{ + /** Get the first character from the string. */ + inline char& front() { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); return this->value_.front(); + } + inline const char& front() const { return this->value_.front(); } + ///@} - /// Get the value from the pointer as a NULL-terminated C-style string. - inline const char* c_str() const { check(); return strPtr_->c_str(); } + ///@{ + /** Get the last character from the string. */ + inline char& back() { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); return this->value_.back(); + } + inline const char& back() const { return this->value_.back(); } + ///@} /// Get the value from the pointer. - inline const char* data() const { check(); return strPtr_->data(); } - - /// Get an iterator to the start of the string. - inline std::string::iterator begin() { check(); markAsUsed(); return strPtr_->begin(); } - - /// Get a const iterator to the start of the string. - inline std::string::const_iterator cbegin() const { check(); return strPtr_->cbegin(); } - - /// Get an iterator to the end of the string. - inline std::string::iterator end() { check(); markAsUsed(); return strPtr_->end(); } + inline const char* data() const { return this->value_.data(); } - /// Get a const iterator to the end of the string. - inline std::string::const_iterator cend() const { check(); return strPtr_->cend(); } + /// Same as data, but returns a NULL-terminated C-style string. + inline const char* c_str() const { return this->value_.c_str(); } - /// Get a reverse iterator to the start of a string. - inline std::string::reverse_iterator rbegin() { check(); markAsUsed(); return strPtr_->rbegin(); } + ///@{ + /** Get an iterator to the start of the string. */ + inline std::string::iterator begin() { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); return this->value_.begin(); + } + inline std::string::const_iterator cbegin() const { return this->value_.cbegin(); } + ///@} - /// Get a const reverse iterator to the start of a string. - inline std::string::const_reverse_iterator crbegin() const { check(); return strPtr_->crbegin(); } + ///@{ + /** Get an iterator to the end of the string. */ + inline std::string::iterator end() { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); return this->value_.end(); + } + inline std::string::const_iterator cend() const { return this->value_.cend(); } + ///@} - /// Get a reverse iterator to the end of a string. - inline std::string::reverse_iterator rend() { check(); markAsUsed(); return strPtr_->rend(); } + ///@{ + /** Get a reverse iterator to the start of a string. */ + inline std::string::reverse_iterator rbegin() { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); return this->value_.rbegin(); + } + inline std::string::const_reverse_iterator crbegin() const { return this->value_.crbegin(); } + ///@} - /// Get a const reverse iterator to the end of a string. - inline std::string::const_reverse_iterator crend() const { check(); return strPtr_->crend(); } + ///@{ + /** Get a reverse iterator to the end of a string. */ + inline std::string::reverse_iterator rend() { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); return this->value_.rend(); + } + inline std::string::const_reverse_iterator crend() const { return this->value_.crend(); } + ///@} /** * Check if the string is empty (has no characters, aka ""). * @return `true` if string is empty, `false` otherwise. */ - inline bool empty() const { check(); return strPtr_->empty(); } + inline bool empty() const { return this->value_.empty(); } - /// Get the number of characters in the string. - inline size_t size() const { check(); return strPtr_->size(); } + ///@{ + /** Get the number of characters in the string. */ + inline size_t size() const { return this->value_.size(); } + inline size_t length() const { return this->value_.length(); } + ///@} - /// Same as size(). - inline size_t length() const { check(); return strPtr_->length(); } - - /// Get the maximum number of characters the string can hold - inline size_t max_size() const { check(); return strPtr_->max_size(); } + /// Get the maximum number of characters the string can hold. + inline size_t max_size() const { return this->value_.max_size(); } /** * Increase the capacity of the string (how many characters it can hold). * @param newcap The new string capacity. */ - inline void reserve(size_t newcap) { check(); markAsUsed(); strPtr_->reserve(newcap); } + inline void reserve(size_t newcap) { + if (this->copy_ == nullptr) { + this->copy_ = std::make_unique(this->value_); + this->copy_->reserve(this->value_.capacity()); + } + markAsUsed(); this->value_.reserve(newcap); + } /// Get the number of characters that can be held in the currently allocated string. - inline size_t capacity() const { check(); return strPtr_->capacity(); } + inline size_t capacity() const { return this->value_.capacity(); } /// Shrink the string to remove unused capacity. - inline void shrink_to_fit() { check(); markAsUsed(); strPtr_->shrink_to_fit(); } + inline void shrink_to_fit() { + if (this->copy_ == nullptr) { + this->copy_ = std::make_unique(this->value_); + this->copy_->reserve(this->value_.capacity()); + } + markAsUsed(); this->value_.shrink_to_fit(); + } /// Clear the contents of the string. - inline void clear() { check(); markAsUsed(); strPtr_->clear(); } + inline void clear() { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.clear(); + } /** - * Insert characters into the string. + * Insert repeated characters into the string. * @param index The index at which the characters will be inserted. * @param count The number of characters to insert. * @param ch The character to insert. * @return The new value. */ inline SafeString& insert(size_t index, size_t count, char ch) { - check(); markAsUsed(); strPtr_->insert(index, count, ch); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.insert(index, count, ch); return *this; } /** - * Insert a NULL_terminated C-style string into the string. + * Insert a NULL-terminated C-style string into the string. * @param index The index at which the substring will be inserted. * @param s The substring to insert. * @return The new value. */ inline SafeString& insert(size_t index, const char* s) { - check(); markAsUsed(); strPtr_->insert(index, s); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.insert(index, s); return *this; } /** @@ -268,7 +311,8 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& insert(size_t index, const char* s, size_t count) { - check(); markAsUsed(); strPtr_->insert(index, s, count); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.insert(index, s, count); return *this; } /** @@ -278,7 +322,8 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& insert(size_t index, const SafeString& str) { - check(); markAsUsed(); strPtr_->insert(index, str.get()); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.insert(index, str.get()); return *this; } /** @@ -288,7 +333,8 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& insert(size_t index, const std::string& str) { - check(); markAsUsed(); strPtr_->insert(index, str); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.insert(index, str); return *this; } /** @@ -302,7 +348,8 @@ class SafeString : public SafeBase { inline SafeString& insert( size_t index, const SafeString& str, size_t index_str, size_t count = std::string::npos ) { - check(); markAsUsed(); strPtr_->insert(index, str.get(), index_str, count); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.insert(index, str.get(), index_str, count); return *this; } /** @@ -316,7 +363,8 @@ class SafeString : public SafeBase { inline SafeString& insert( size_t index, const std::string& str, size_t index_str, size_t count = std::string::npos ) { - check(); markAsUsed(); strPtr_->insert(index, str, index_str, count); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.insert(index, str, index_str, count); return *this; } /** @@ -326,7 +374,8 @@ class SafeString : public SafeBase { * @return An iterator that points to the inserted character. */ inline std::string::iterator insert(std::string::const_iterator pos, char ch) { - check(); markAsUsed(); return strPtr_->insert(pos, ch); + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); return this->value_.insert(pos, ch); } /** @@ -337,11 +386,13 @@ class SafeString : public SafeBase { * @return An iterator that points to the first inserted character. */ inline std::string::iterator insert(std::string::const_iterator pos, size_t count, char ch) { - check(); markAsUsed(); return strPtr_->insert(pos, count, ch); + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); return this->value_.insert(pos, count, ch); } /** * Insert a range [first, last) of characters into the string. + * @tparam InputIt Any iterator type. * @param pos The position at which the characters will be inserted. * @param first An iterator that points to the first character to insert. * @param last An iterator that points to one past the last character to insert. @@ -350,7 +401,8 @@ class SafeString : public SafeBase { template inline std::string::iterator insert( std::string::const_iterator pos, InputIt first, InputIt last ) { - check(); markAsUsed(); return strPtr_->insert(pos, first, last); + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); return this->value_.insert(pos, first, last); } /** @@ -362,7 +414,8 @@ class SafeString : public SafeBase { inline std::string::iterator insert( std::string::const_iterator pos, std::initializer_list ilist ) { - check(); markAsUsed(); return strPtr_->insert(pos, ilist); + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); return this->value_.insert(pos, ilist); } /** @@ -372,7 +425,8 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& erase(size_t index = 0, size_t count = std::string::npos) { - check(); markAsUsed(); strPtr_->erase(index, count); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.erase(index, count); return *this; } /** @@ -381,7 +435,8 @@ class SafeString : public SafeBase { * @return An iterator that points to the character immediately following the erased character. */ inline std::string::iterator erase(std::string::const_iterator position) { - check(); markAsUsed(); return strPtr_->erase(position); + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); return this->value_.erase(position); } /** @@ -394,17 +449,24 @@ class SafeString : public SafeBase { inline std::string::iterator erase( std::string::const_iterator first, std::string::const_iterator last ) { - check(); markAsUsed(); return strPtr_->erase(first, last); + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); return this->value_.erase(first, last); } /** * Append a character to the end of the string. * @param ch The character to append. */ - inline void push_back(char ch) { check(); markAsUsed(); strPtr_->push_back(ch); } + inline void push_back(char ch) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.push_back(ch); + } /// Remove the last character from the string. - inline void pop_back() { check(); markAsUsed(); strPtr_->pop_back(); } + inline void pop_back() { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.pop_back(); + } /** * Append a number of characters to the end of the string. @@ -413,7 +475,8 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& append(size_t count, char ch) { - check(); markAsUsed(); strPtr_->append(count, ch); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.append(count, ch); return *this; } /** @@ -422,7 +485,8 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& append(const SafeString& str) { - check(); markAsUsed(); strPtr_->append(str.get()); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.append(str.get()); return *this; } /** @@ -431,7 +495,8 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& append(const std::string& str) { - check(); markAsUsed(); strPtr_->append(str); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.append(str); return *this; } /** @@ -444,7 +509,8 @@ class SafeString : public SafeBase { inline SafeString& append( const SafeString& str, size_t pos, size_t count = std::string::npos ) { - check(); markAsUsed(); strPtr_->append(str.get(), pos, count); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.append(str.get(), pos, count); return *this; } /** @@ -457,7 +523,8 @@ class SafeString : public SafeBase { inline SafeString& append( const std::string& str, size_t pos, size_t count = std::string::npos ) { - check(); markAsUsed(); strPtr_->append(str, pos, count); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.append(str, pos, count); return *this; } /** @@ -467,7 +534,8 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& append(const char* s, size_t count) { - check(); markAsUsed(); strPtr_->append(s, count); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.append(s, count); return *this; } /** @@ -476,57 +544,44 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& append(const char* s) { - check(); markAsUsed(); strPtr_->append(s); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.append(s); return *this; } /** * Append a range of [first, last) characters to the end of the string. + * @tparam InputIt Any iterator type. * @param first An iterator that points to the first character to append. * @param last An iterator that points to one past the last character to append. * @return The new value. */ - template - inline SafeString& append(InputIt first, InputIt last) { - check(); markAsUsed(); strPtr_->append(first, last); return *this; + template inline SafeString& append(InputIt first, InputIt last) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.append(first, last); return *this; } /** * Append characters from an initializer list to the end of a string. - * @param ilist The initializer list to append. + * @param ilist The initializer list to append. * @return The new value. */ inline SafeString& append(std::initializer_list ilist) { - check(); markAsUsed(); strPtr_->append(ilist); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.append(ilist); return *this; } - /** - * Compare the string to another SafeString. - * @param str The string to compare to. - * @return An integer less than, equal to, or greater than zero if the string - * is less than, equal to, or greater than the compared string, respectively. - */ - inline int compare(const SafeString& str) const { check(); return strPtr_->compare(str.get()); } - + ///@{ /** * Compare the string to another string. * @param str The string to compare to. * @return An integer less than, equal to, or greater than zero if the string * is less than, equal to, or greater than the compared string, respectively. */ - inline int compare(const std::string& str) const { check(); return strPtr_->compare(str); } - - /** - * Compare the string to another SafeString substring. - * @param pos The index of the first character of the substring to compare to. - * @param count The number of characters of the substring to compare to. - * @param str The string to compare to. - * @return An integer less than, equal to, or greater than zero if the string - * is less than, equal to, or greater than the compared string, respectively. - */ - inline int compare(size_t pos, size_t count, const SafeString& str) const { - check(); return strPtr_->compare(pos, count, str.get()); - } + inline int compare(const std::string& str) const { return this->value_.compare(str); } + inline int compare(const SafeString& str) const { return this->value_.compare(str.get()); } + ///@} + ///@{ /** * Compare the string to another substring. * @param pos The index of the first character of the substring to compare to. @@ -536,11 +591,16 @@ class SafeString : public SafeBase { * is less than, equal to, or greater than the compared string, respectively. */ inline int compare(size_t pos, size_t count, const std::string& str) const { - check(); return strPtr_->compare(pos, count, str); + return this->value_.compare(pos, count, str); } + inline int compare(size_t pos, size_t count, const SafeString& str) const { + return this->value_.compare(pos, count, str.get()); + } + ///@} + ///@{ /** - * Compare a substring of this string to another SafeString substring + * Compare a substring of this string to another substring * (you better check yourself before you wreck yourself!). * @param pos1 The index of the first character of this string. * @param count1 The number of characters of the substring of this string. @@ -551,28 +611,18 @@ class SafeString : public SafeBase { * is less than, equal to, or greater than the compared string, respectively. */ inline int compare( - size_t pos1, size_t count1, const SafeString& str, + size_t pos1, size_t count1, const std::string& str, size_t pos2, size_t count2 = std::string::npos ) const { - check(); return strPtr_->compare(pos1, count1, str.get(), pos2, count2); + return this->value_.compare(pos1, count1, str, pos2, count2); } - - /** - * Compare a substring of this string to another substring. - * @param pos1 The index of the first character of this string. - * @param count1 The number of characters of the substring of this string. - * @param str The substring to compare to. - * @param pos2 The index of the first character of the substring to compare to. - * @param count2 The number of characters of the substring to compare to. - * @return An integer less than, equal to, or greater than zero if the string - * is less than, equal to, or greater than the compared string, respectively. - */ inline int compare( - size_t pos1, size_t count1, const std::string& str, + size_t pos1, size_t count1, const SafeString& str, size_t pos2, size_t count2 = std::string::npos ) const { - check(); return strPtr_->compare(pos1, count1, str, pos2, count2); + return this->value_.compare(pos1, count1, str.get(), pos2, count2); } + ///@} /** * Compare the string to another C-style string. @@ -580,7 +630,7 @@ class SafeString : public SafeBase { * @return An integer less than, equal to, or greater than zero if the string * is less than, equal to, or greater than the compared string, respectively. */ - inline int compare(const char* s) const { check(); return strPtr_->compare(s); } + inline int compare(const char* s) const { return this->value_.compare(s); } /** * Compare the string to another C-style substring. @@ -591,16 +641,9 @@ class SafeString : public SafeBase { * is less than, equal to, or greater than the compared string, respectively. */ inline int compare(size_t pos, size_t count, const char* s) const { - check(); return strPtr_->compare(pos, count, s); + return this->value_.compare(pos, count, s); } - /**constexpr int compare( size_type pos1, size_type count1,const CharT* s, - size_type count2 ) const; - @brief Compares the safe string to a substring of an array of characters. - @return An integer less than, equal to, or greater than zero if the substring - of the safe string is less than, equal to, or greater than the substring of - the string s, respectively. - */ /** * Compare a substring of this string to another C-style substring. * @param pos1 The index of the first character of this string. @@ -611,7 +654,7 @@ class SafeString : public SafeBase { * is less than, equal to, or greater than the compared string, respectively. */ inline int compare(size_t pos1, size_t count1, const char* s, size_t count2) const { - check(); return strPtr_->compare(pos1, count1, s, count2); + return this->value_.compare(pos1, count1, s, count2); } /** @@ -619,69 +662,85 @@ class SafeString : public SafeBase { * @param sv The substring to check for. * @return `true` if there's a match, `false` otherwise. */ - inline bool starts_with(const std::string& sv) const { check(); return strPtr_->starts_with(sv); } + inline bool starts_with(std::string_view sv) const { return this->value_.starts_with(sv); } /** * Check if the string starts with a given character. * @param ch The character to check for. * @return `true` if there's a match, `false` otherwise. */ - inline bool starts_with(char ch) const { check(); return strPtr_->starts_with(ch); } + inline bool starts_with(char ch) const { return this->value_.starts_with(ch); } /** * Check if the string starts with a given C-style substring. * @param s The substring to check for. * @return `true` if there's a match, `false` otherwise. */ - inline bool starts_with(const char* s) const { check(); return strPtr_->starts_with(s); } + inline bool starts_with(const char* s) const { return this->value_.starts_with(s); } /** * Check if the string ends with a given substring. * @param sv The substring to check for. * @return `true` if there's a match, `false` otherwise. */ - inline bool ends_with(const std::string& sv) const { check(); return strPtr_->ends_with(sv); } + inline bool ends_with(std::string_view sv) const { return this->value_.ends_with(sv); } /** * Check if the string ends with a given character. * @param ch The character to check for. * @return `true` if there's a match, `false` otherwise. */ - inline bool ends_with(char ch) const { check(); return strPtr_->ends_with(ch); } + inline bool ends_with(char ch) const { return this->value_.ends_with(ch); } /** * Check if the string ends with a given C-style substring. * @param s The substring to check for. * @return `true` if there's a match, `false` otherwise. */ - inline bool ends_with(const char* s) const { check(); return strPtr_->ends_with(s); } + inline bool ends_with(const char* s) const { return this->value_.ends_with(s); } - // TODO: contains (C++23) - (1) in https://en.cppreference.com/w/cpp/string/basic_string/contains + /** + * Check if the string contains a given substring. + * @param sv The substring to check for. + * @return `true` if there's a match, `false` otherwise. + */ + inline bool contains(std::string_view sv) const { return this->value_.contains(sv); } /** - * Replace part of this string with a SafeString. - * @param pos The index of the first character of this string to replace. - * @param count The number of characters of this string to replace. - * @param str The string to use as a replacement. - * @return The new value. + * Check if the string contains a given character. + * @param ch The character to check for. + * @return `true` if there's a match, `false` otherwise. */ - inline SafeString& replace(size_t pos, size_t count, const SafeString& str) { - check(); markAsUsed(); strPtr_->replace(pos, count, str.get()); return *this; - } + inline bool contains(char ch) const { return this->value_.contains(ch); } /** - * Replace part of this string with a string. + * Check if the string contains a given C-style substring. + * @param s The substring to check for. + * @return `true` if there's a match, `false` otherwise. + */ + inline bool contains(const char* s) const { return this->value_.contains(s); } + + ///@{ + /** + * Replace part of this string with another string. * @param pos The index of the first character of this string to replace. * @param count The number of characters of this string to replace. * @param str The string to use as a replacement. * @return The new value. */ + inline SafeString& replace(size_t pos, size_t count, const SafeString& str) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(pos, count, str.get()); return *this; + } inline SafeString& replace(size_t pos, size_t count, const std::string& str) { - check(); markAsUsed(); strPtr_->replace(pos, count, str); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(pos, count, str); return *this; } + ///@} + ///@{ /** - * Replace part of this string with a SafeString, using iterators. + * Replace part of this string with another string, using iterators. * @param first An iterator to the first character of this string to replace. * @param last An iterator to the last character of this string to replace. * @param str The string to use as a replacement. @@ -690,24 +749,20 @@ class SafeString : public SafeBase { inline SafeString& replace( std::string::const_iterator first, std::string::const_iterator last, const SafeString& str ) { - check(); markAsUsed(); strPtr_->replace(first, last, str.get()); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(first, last, str.get()); return *this; } - - /** - * Replace part of this string with a string, using iterators. - * @param first An iterator to the first character of this string to replace. - * @param last An iterator to the last character of this string to replace. - * @param str The string to use as a replacement. - * @return The new value. - */ inline SafeString& replace( std::string::const_iterator first, std::string::const_iterator last, const std::string& str ) { - check(); markAsUsed(); strPtr_->replace(first, last, str); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(first, last, str); return *this; } + ///@} + ///@{ /** - * Replace part of this string with a SafeString substring. + * Replace part of this string with a substring. * @param pos The index of the first character of this string to replace. * @param count The number of characters of this string to replace. * @param str The string to use as a replacement. @@ -718,26 +773,20 @@ class SafeString : public SafeBase { inline SafeString& replace( size_t pos, size_t count, const SafeString& str, size_t pos2, size_t count2 = std::string::npos ) { - check(); markAsUsed(); strPtr_->replace(pos, count, str.get(), pos2, count2); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(pos, count, str.get(), pos2, count2); return *this; } - - /** - * Replace part of this string with a substring. - * @param pos The index of the first character of this string to replace. - * @param count The number of characters of this string to replace. - * @param str The string to use as a replacement. - * @param pos2 The index of the first character of the substring to use as a replacement. - * @param count2 The number of characters of the substring to use as a replacement. - * @return The new value. - */ inline SafeString& replace( size_t pos, size_t count, const std::string& str, size_t pos2, size_t count2 = std::string::npos ) { - check(); markAsUsed(); strPtr_->replace(pos, count, str, pos2, count2); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(pos, count, str, pos2, count2); return *this; } + ///@} /** * Replace part of this string with a substring, using iterators. + * @tparam InputIt Any iterator type. * @param first An iterator to the first character of this string to replace. * @param last An iterator to the last character of this string to replace. * @param first2 An iterator to the first character of the substring to use as a replacement. @@ -748,7 +797,8 @@ class SafeString : public SafeBase { std::string::const_iterator first, std::string::const_iterator last, InputIt first2, InputIt last2 ) { - check(); markAsUsed(); strPtr_->replace(first, last, first2, last2); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(first, last, first2, last2); return *this; } /** @@ -760,7 +810,8 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& replace(size_t pos, size_t count, const char* cstr, size_t count2) { - check(); strPtr_->replace(pos, count, cstr, count2); markAsUsed(); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(pos, count, cstr, count2); return *this; } /** @@ -775,7 +826,8 @@ class SafeString : public SafeBase { std::string::const_iterator first, std::string::const_iterator last, const char* cstr, size_t count ) { - check(); markAsUsed(); strPtr_->replace(first, last, cstr, count); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(first, last, cstr, count); return *this; } /** @@ -786,7 +838,8 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& replace(size_t pos, size_t count, const char* cstr) { - check(); markAsUsed(); strPtr_->replace(pos, count, cstr); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(pos, count, cstr); return *this; } /** @@ -799,11 +852,12 @@ class SafeString : public SafeBase { inline SafeString& replace( std::string::const_iterator first, std::string::const_iterator last, const char* cstr ) { - check(); markAsUsed(); strPtr_->replace(first, last, cstr); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(first, last, cstr); return *this; } /** - * Replace part of this string with a number of characters. + * Replace part of this string with a number of repeated characters. * @param pos The index of the first character of this string to replace. * @param count The number of characters in this string to replace. * @param count2 The number of characters to replace. @@ -811,11 +865,12 @@ class SafeString : public SafeBase { * @return The new value. */ inline SafeString& replace(size_t pos, size_t count, size_t count2, char ch) { - check(); markAsUsed(); strPtr_->replace(pos, count, count2, ch); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(pos, count, count2, ch); return *this; } /** - * Replace part of this string with a number of characters, using iterators. + * Replace part of this string with a number of repeated characters, using iterators. * @param first An iterator to the first character of this string to replace. * @param last An iterator to the last character of this string to replace. * @param count The number of characters to replace. @@ -826,7 +881,8 @@ class SafeString : public SafeBase { std::string::const_iterator first, std::string::const_iterator last, size_t count, char ch ) { - check(); markAsUsed(); strPtr_->replace(first, last, count, ch); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(first, last, count, ch); return *this; } /** @@ -840,7 +896,52 @@ class SafeString : public SafeBase { std::string::const_iterator first, std::string::const_iterator last, std::initializer_list ilist ) { - check(); markAsUsed(); strPtr_->replace(first, last, ilist); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(first, last, ilist); return *this; + } + + /** + * Replace part of this string with a string view. + * @param pos The index of the first character of this string to replace. + * @param count The number of characters in this string to replace. + * @param sv The string view to use as a replacement. + * @return The new value. + */ + inline SafeString& replace( + size_t pos, size_t count, const std::string_view& sv + ) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(pos, count, sv); return *this; + } + + /** + * Replace part of this string with a string view, using iterators. + * @param first An iterator to the first character of this string to replace. + * @param last An iterator to the last character of this string to replace. + * @param sv The string view to use as a replacement. + * @return The new value. + */ + inline SafeString& replace( + std::string::const_iterator first, std::string::const_iterator last, const std::string_view& sv + ) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(first, last, sv); return *this; + } + + /** + * Replace part of this string with a string view. + * @param pos The index of the first character of this string to replace. + * @param count The number of characters in this string to replace. + * @param sv The string view to use as a replacement. + * @param pos2 The index of the first character of the string view to use as a replacement. + * @param count2 The number of characters of the string view to use as a replacement. + * @return The new value. + */ + inline SafeString& replace( + size_t pos, size_t count, const std::string_view& sv, size_t pos2, size_t count2 = std::string::npos + ) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.replace(pos, count, sv, pos2, count2); return *this; } /** @@ -850,7 +951,7 @@ class SafeString : public SafeBase { * @return The substring itself. */ inline SafeString substr(size_t pos = 0, size_t count = std::string::npos) const { - check(); return SafeString(strPtr_->substr(pos, count)); + return SafeString(this->value_.substr(pos, count)); } /** @@ -861,49 +962,57 @@ class SafeString : public SafeBase { * @return The number of characters that were copied. */ inline size_t copy(char* dest, size_t count, size_t pos = 0) const { - check(); return strPtr_->copy(dest, count, pos); + return this->value_.copy(dest, count, pos); } /** * Resize the string. * @param count The new size of the string. */ - inline void resize(size_t count) { check(); markAsUsed(); strPtr_->resize(count); } + inline void resize(size_t count) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.resize(count); + } /** - * Resize the safe string and fill the extra space with a given character. + * Resize the string and fill the extra space with a given character. * @param count The new size of the string. * @param ch The character to use as filling. */ - inline void resize(size_t count, char ch) { check(); markAsUsed(); strPtr_->resize(count, ch); } + inline void resize(size_t count, char ch) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.resize(count, ch); + } /** - * Swap the contents of this string with another SafeString. - * @param other The string to swap with. + * Swap the contents of this string with another string. + * @param str The string to swap with. */ - inline void swap(SafeString& other) { - check(); other.check(); markAsUsed(); other.markAsUsed(); strPtr_.swap(other.strPtr_); + inline void swap(std::string& str) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.swap(str); } /** - * Find the first occurrence of a given SafeString. - * @param str The string to find. - * @param pos The index of the first character to search. Defaults to the start of the string. - * @return The index of the first occurrence, or std::string::npos if not found. + * Swap the contents of this string with another SafeString. + * @param other The string to swap with. */ - inline size_t find(const SafeString& str, size_t pos = 0) const { - check(); return strPtr_->find(str.get(), pos); + inline void swap(SafeString& other) noexcept { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + if (other.copy_ == nullptr) other.copy_ = std::make_unique(other.value_); + markAsUsed(); other.markAsUsed(); this->value_.swap(other.value_); } + ///@{ /** * Find the first occurrence of a given string. * @param str The string to find. * @param pos The index of the first character to search. Defaults to the start of the string. * @return The index of the first occurrence, or std::string::npos if not found. */ - inline size_t find(const std::string& str, size_t pos = 0) const { - check(); return strPtr_->find(str, pos); - } + inline size_t find(const std::string& str, size_t pos = 0) const { return this->value_.find(str, pos); } + inline size_t find(const SafeString& str, size_t pos = 0) const { return this->value_.find(str.get(), pos); } + ///@} /** * Find the first occurrence of a given C-style substring. @@ -912,9 +1021,7 @@ class SafeString : public SafeBase { * @param count The number of characters to search. * @return The index of the first occurrence, or std::string::npos if not found. */ - inline size_t find(const char* s, size_t pos, size_t count) const { - check(); return strPtr_->find(s, pos, count); - } + inline size_t find(const char* s, size_t pos, size_t count) const { return this->value_.find(s, pos, count); } /** * Find the first occurrence of a given C-style string. @@ -922,9 +1029,7 @@ class SafeString : public SafeBase { * @param pos The index of the first character to search. Defaults to the start of the string. * @return The index of the first occurrence, or std::string::npos if not found. */ - inline size_t find(const char* s, size_t pos = 0) const { - check(); return strPtr_->find(s, pos); - } + inline size_t find(const char* s, size_t pos = 0) const { return this->value_.find(s, pos); } /** * Find the first occurrence of a given character. @@ -932,29 +1037,22 @@ class SafeString : public SafeBase { * @param pos The index of the first character to search. Defaults to the start of the string. * @return The index of the first occurrence, or std::string::npos if not found. */ - inline size_t find(char ch, size_t pos = 0) const { - check(); return strPtr_->find(ch, pos); - } + inline size_t find(char ch, size_t pos = 0) const { return this->value_.find(ch, pos); } + ///@{ /** - * Find the last occurrence of a given SafeString. + * Find the last occurrence of a given string. * @param str The string to find. * @param pos The index of the first character to search. Defaults to the end of the string. * @return The index of the last occurrence, or std::string::npos if not found. */ inline size_t rfind(const SafeString& str, size_t pos = std::string::npos) const { - check(); return strPtr_->rfind(str.get(), pos); + return this->value_.rfind(str.get(), pos); } - - /** - * Find the last occurrence of a given string. - * @param str The string to find. - * @param pos The index of the first character to search. Defaults to the end of the string. - * @return The index of the last occurrence, or std::string::npos if not found. - */ inline size_t rfind(const std::string& str, size_t pos = std::string::npos) const { - check(); return strPtr_->rfind(str, pos); + return this->value_.rfind(str, pos); } + ///@} /** * Find the last occurrence of a given C-style substring. @@ -964,7 +1062,7 @@ class SafeString : public SafeBase { * @return The index of the last occurrence, or std::string::npos if not found. */ inline size_t rfind(const char* s, size_t pos, size_t count) const { - check(); return strPtr_->rfind(s, pos, count); + return this->value_.rfind(s, pos, count); } /** @@ -974,7 +1072,7 @@ class SafeString : public SafeBase { * @return The index of the last occurrence, or std::string::npos if not found. */ inline size_t rfind(const char* s, size_t pos = std::string::npos) const { - check(); return strPtr_->rfind(s, pos); + return this->value_.rfind(s, pos); } /** @@ -984,28 +1082,23 @@ class SafeString : public SafeBase { * @return The index of the last occurrence, or std::string::npos if not found. */ inline size_t rfind(char ch, size_t pos = std::string::npos) const { - check(); return strPtr_->rfind(ch, pos); + return this->value_.rfind(ch, pos); } + ///@{ /** - * Find the first occurrence of any of the characters in a given SafeString. + * Find the first occurrence of any of the characters in a given string. * @param str The string to use as a reference for searching. * @param pos The index of the first character to search. Defaults to the start of the string. * @return The index of the first occurrence, or std::string::npos if not found. */ inline size_t find_first_of(const SafeString& str, size_t pos = 0) const { - check(); return strPtr_->find_first_of(str.get(), pos); + return this->value_.find_first_of(str.get(), pos); } - - /** - * Find the first occurrence of any of the characters in a given string. - * @param str The string to use as a reference for searching. - * @param pos The index of the first character to search. Defaults to the start of the string. - * @return The index of the first occurrence, or std::string::npos if not found. - */ inline size_t find_first_of(const std::string& str, size_t pos = 0) const { - check(); return strPtr_->find_first_of(str, pos); + return this->value_.find_first_of(str, pos); } + ///@} /** * Find the first occurrence of any of the characters in a given C-style substring. @@ -1015,7 +1108,7 @@ class SafeString : public SafeBase { * @return The index of the first occurrence, or std::string::npos if not found. */ inline size_t find_first_of(const char* s, size_t pos, size_t count) const { - check(); return strPtr_->find_first_of(s, pos, count); + return this->value_.find_first_of(s, pos, count); } /** @@ -1025,7 +1118,7 @@ class SafeString : public SafeBase { * @return The index of the first occurrence, or std::string::npos if not found. */ inline size_t find_first_of(const char* s, size_t pos = 0) const { - check(); return strPtr_->find_first_of(s, pos); + return this->value_.find_first_of(s, pos); } /** @@ -1035,28 +1128,23 @@ class SafeString : public SafeBase { * @return The index of the first occurrence, or std::string::npos if not found. */ inline size_t find_first_of(char ch, size_t pos = 0) const { - check(); return strPtr_->find_first_of(ch, pos); + return this->value_.find_first_of(ch, pos); } + ///@{ /** - * Find the first occurrence of none of the characters in a given SafeString. + * Find the first occurrence of none of the characters in a given string. * @param str The string to use as a reference for searching. * @param pos The index of the first character to search. Defaults to the start of the string. * @return The index of the first occurrence, or std::string::npos if not found. */ inline size_t find_first_not_of(const SafeString& str, size_t pos = 0) const { - check(); return strPtr_->find_first_not_of(str.get(), pos); + return this->value_.find_first_not_of(str.get(), pos); } - - /** - * Find the first occurrence of none of the characters in a given string. - * @param str The string to use as a reference for searching. - * @param pos The index of the first character to search. Defaults to the start of the string. - * @return The index of the first occurrence, or std::string::npos if not found. - */ inline size_t find_first_not_of(const std::string& str, size_t pos = 0) const { - check(); return strPtr_->find_first_not_of(str, pos); + return this->value_.find_first_not_of(str, pos); } + ///@} /** * Find the first occurrence of none of the characters in a given C-style substring. @@ -1066,7 +1154,7 @@ class SafeString : public SafeBase { * @return The index of the first occurrence, or std::string::npos if not found. */ inline size_t find_first_not_of(const char* s, size_t pos, size_t count) const { - check(); return strPtr_->find_first_not_of(s, pos, count); + return this->value_.find_first_not_of(s, pos, count); } /** @@ -1076,7 +1164,7 @@ class SafeString : public SafeBase { * @return The index of the first occurrence, or std::string::npos if not found. */ inline size_t find_first_not_of(const char* s, size_t pos = 0) const { - check(); return strPtr_->find_first_not_of(s, pos); + return this->value_.find_first_not_of(s, pos); } /** @@ -1086,28 +1174,23 @@ class SafeString : public SafeBase { * @return The index of the first occurrence, or std::string::npos if not found. */ inline size_t find_first_not_of(char ch, size_t pos = 0) const { - check(); return strPtr_->find_first_not_of(ch, pos); + return this->value_.find_first_not_of(ch, pos); } + ///@{ /** - * Find the last occurrence of any of the characters in a given SafeString. + * Find the last occurrence of any of the characters in a given string. * @param str The string to use as a reference for searching. * @param pos The index of the first character to search. Defaults to the end of the string. * @return The index of the last occurrence, or std::string::npos if not found. */ inline size_t find_last_of(const SafeString& str, size_t pos = std::string::npos) const { - check(); return strPtr_->find_last_of(str.get(), pos); + return this->value_.find_last_of(str.get(), pos); } - - /** - * Find the last occurrence of any of the characters in a given string. - * @param str The string to use as a reference for searching. - * @param pos The index of the first character to search. Defaults to the end of the string. - * @return The index of the last occurrence, or std::string::npos if not found. - */ inline size_t find_last_of(const std::string& str, size_t pos = std::string::npos) const { - check(); return strPtr_->find_last_of(str, pos); + return this->value_.find_last_of(str, pos); } + ///@} /** * Find the last occurrence of any of the characters in a given C-style substring. @@ -1117,7 +1200,7 @@ class SafeString : public SafeBase { * @return The index of the last occurrence, or std::string::npos if not found. */ inline size_t find_last_of(const char* s, size_t pos, size_t count) const { - check(); return strPtr_->find_last_of(s, pos, count); + return this->value_.find_last_of(s, pos, count); } /** @@ -1127,7 +1210,7 @@ class SafeString : public SafeBase { * @return The index of the last occurrence, or std::string::npos if not found. */ inline size_t find_last_of(const char* s, size_t pos = std::string::npos) const { - check(); return strPtr_->find_last_of(s, pos); + return this->value_.find_last_of(s, pos); } /** @@ -1137,28 +1220,23 @@ class SafeString : public SafeBase { * @return The index of the last occurrence, or std::string::npos if not found. */ inline size_t find_last_of(char ch, size_t pos = std::string::npos) const { - check(); return strPtr_->find_last_of(ch, pos); + return this->value_.find_last_of(ch, pos); } + ///@{ /** - * Find the last occurrence of none of the characters in a given SafeString. + * Find the last occurrence of none of the characters in a given string. * @param str The string to use as a reference for searching. * @param pos The index of the first character to search. Defaults to the end of the string. * @return The index of the last occurrence, or std::string::npos if not found. */ inline size_t find_last_not_of(const SafeString& str, size_t pos = std::string::npos) const { - check(); return strPtr_->find_last_not_of(str.get(), pos); + return this->value_.find_last_not_of(str.get(), pos); } - - /** - * Find the last occurrence of none of the characters in a given string. - * @param str The string to use as a reference for searching. - * @param pos The index of the first character to search. Defaults to the end of the string. - * @return The index of the last occurrence, or std::string::npos if not found. - */ inline size_t find_last_not_of(const std::string& str, size_t pos = std::string::npos) const { - check(); return strPtr_->find_last_not_of(str, pos); + return this->value_.find_last_not_of(str, pos); } + ///@} /** * Find the last occurrence of none of the characters in a given C-style substring. @@ -1168,7 +1246,7 @@ class SafeString : public SafeBase { * @return The index of the last occurrence, or std::string::npos if not found. */ inline size_t find_last_not_of(const char* s, size_t pos, size_t count) const { - check(); return strPtr_->find_last_not_of(s, pos, count); + return this->value_.find_last_not_of(s, pos, count); } /** @@ -1178,7 +1256,7 @@ class SafeString : public SafeBase { * @return The index of the last occurrence, or std::string::npos if not found. */ inline size_t find_last_not_of(const char* s, size_t pos = std::string::npos) const { - check(); return strPtr_->find_last_not_of(s, pos); + return this->value_.find_last_not_of(s, pos); } /** @@ -1188,168 +1266,137 @@ class SafeString : public SafeBase { * @return The index of the last occurrence, or std::string::npos if not found. */ inline size_t find_last_not_of(char ch, size_t pos = std::string::npos) const { - check(); return strPtr_->find_last_not_of(ch, pos); + return this->value_.find_last_not_of(ch, pos); } - /// Assignment operator. + ///@{ + /** Assignment operator. */ inline SafeString& operator=(const SafeString& other) { - check(); markAsUsed(); *strPtr_ = other.get(); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_ = other.get(); return *this; } - - /// Assignment operator. inline SafeString& operator=(const std::string& other) { - check(); markAsUsed(); *strPtr_ = other; return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_ = other; return *this; } - - /// Assignment operator. inline SafeString& operator=(const char* s) { - check(); markAsUsed(); *strPtr_ = s; return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_ = s; return *this; } - - /// Assignment operator. inline SafeString& operator=(char ch) { - check(); markAsUsed(); *strPtr_ = ch; return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_ = ch; return *this; } - - /// Assignment operator. inline SafeString& operator=(std::initializer_list ilist) { - check(); markAsUsed(); *strPtr_ = ilist; return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_ = ilist; return *this; } + ///@} - /// Compound assignment operator. + ///@{ + /** Compound assignment operator. */ inline SafeString& operator+=(const SafeString& str) { - check(); markAsUsed(); strPtr_->operator+=(str.get()); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.operator+=(str.get()); return *this; } - - /// Compound assignment operator. inline SafeString& operator+=(const std::string& str) { - check(); markAsUsed(); strPtr_->operator+=(str); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.operator+=(str); return *this; } - - /// Compound assignment operator. - inline SafeString& operator+=(char ch) { - check(); markAsUsed(); strPtr_->operator+=(ch); return *this; - } - - /// Compound assignment operator. inline SafeString& operator+=(const char* s) { - check(); markAsUsed(); strPtr_->operator+=(s); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.operator+=(s); return *this; + } + inline SafeString& operator+=(char ch) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.operator+=(ch); return *this; } - - /// Compound assignment operator. inline SafeString& operator+=(std::initializer_list ilist) { - check(); markAsUsed(); strPtr_->operator+=(ilist); return *this; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); this->value_.operator+=(ilist); return *this; } + ///@} - /// Subscript/Indexing operator. + ///@{ + /** Subscript/Indexing operator. */ inline char& operator[](size_t pos) { - check(); markAsUsed(); return strPtr_->operator[](pos); - } - - /// Subscript/Indexing operator. - inline const char& operator[](size_t pos) const { - check(); return strPtr_->operator[](pos); - } - - /// Concat operator. - inline SafeString operator+(const SafeString& rhs) const { - check(); return SafeString(*strPtr_ + rhs.get()); - }; - - /// Concat operator. - inline SafeString operator+(const std::string& rhs) const { - check(); return SafeString(*strPtr_ + rhs); - }; - - /// Concat operator. - inline SafeString operator+(const char* rhs) const { - check(); return SafeString(*strPtr_ + rhs); - }; - - /// Concat operator. - inline SafeString operator+(char rhs) const { - check(); return SafeString(*strPtr_ + rhs); - }; - - /// Equality operator. - inline bool operator==(const SafeString& rhs) const { - check(); return *strPtr_ == rhs.get(); - }; - - /// Equality operator. - inline bool operator==(const std::string& rhs) const { - check(); return *strPtr_ == rhs; - }; - - /// Equality operator. - inline bool operator==(const char* rhs) const { - check(); return *strPtr_ == rhs; - }; - - /// Inequality operator. - inline bool operator!=(const char* rhs) const { - check(); return *strPtr_ != rhs; - }; - - /// Lesser comparison operator. - inline bool operator<(const SafeString& rhs) const { - check(); return *strPtr_ < rhs.get(); - }; - - /// Lesser comparison operator. - inline bool operator<(const std::string& rhs) const { - check(); return *strPtr_ < rhs; - }; - - /// Lesser comparison operator. - inline bool operator<(const char* rhs) const { - check(); return *strPtr_ < rhs; - }; - - /// Greater comparison operator. - inline bool operator>(const SafeString& rhs) const { - check(); return *strPtr_ > rhs.get(); - }; - - /// Greater comparison operator. - inline bool operator>(const std::string& rhs) const { - check(); return *strPtr_ > rhs; - }; - - /// Greater comparison operator. - inline bool operator>(const char* rhs) const { - check(); return *strPtr_ > rhs; - }; - - /// Lesser-or-equal comparison operator. - inline bool operator<=(const SafeString& rhs) const { - check(); return *strPtr_ <= rhs.get(); - }; - - /// Lesser-or-equal comparison operator. - inline bool operator<=(const std::string& rhs) const { - check(); return *strPtr_ <= rhs; - }; - - /// Lesser-or-equal comparison operator. - inline bool operator<=(const char* rhs) const { - check(); return *strPtr_ <= rhs; - }; - - /// Greater-or-equal comparison operator. - inline bool operator>=(const SafeString& rhs) const { - check(); return *strPtr_ >= rhs.get(); - }; - - /// Greater-or-equal comparison operator. - inline bool operator>=(const std::string& rhs) const { - check(); return *strPtr_ >= rhs; - }; - - /// Greater-or-equal comparison operator. - inline bool operator>=(const char* rhs) const { - check(); return *strPtr_ >= rhs; - }; + if (this->copy_ == nullptr) this->copy_ = std::make_unique(this->value_); + markAsUsed(); return this->value_.operator[](pos); + } + inline const char& operator[](size_t pos) const { return this->value_.operator[](pos); } + ///@} + + ///@{ + /** Concat operator. */ + inline SafeString operator+(const SafeString& rhs) const { return SafeString(this->value_ + rhs.get()); }; + inline SafeString operator+(const std::string& rhs) const { return SafeString(this->value_ + rhs); }; + inline SafeString operator+(const char* rhs) const { return SafeString(this->value_ + rhs); }; + inline SafeString operator+(char rhs) const { return SafeString(this->value_ + rhs); }; + ///@} + + ///@{ + /** Equality operator. */ + inline bool operator==(const SafeString& rhs) const { return this->value_ == rhs.get(); }; + inline bool operator==(const std::string& rhs) const { return this->value_ == rhs; }; + inline bool operator==(const char* rhs) const { return this->value_ == rhs; }; + ///@} + + ///@{ + /** Inequality operator. */ + inline bool operator!=(const SafeString& rhs) const { return this->value_ != rhs.get(); }; + inline bool operator!=(const std::string& rhs) const { return this->value_ != rhs; }; + inline bool operator!=(const char* rhs) const { return this->value_ != rhs; }; + ///@} + + ///@{ + /** Lesser comparison operator. */ + inline bool operator<(const SafeString& rhs) const { return this->value_ < rhs.get(); }; + inline bool operator<(const std::string& rhs) const { return this->value_ < rhs; }; + inline bool operator<(const char* rhs) const { return this->value_ < rhs; }; + ///@} + + ///@{ + /** Greater comparison operator. */ + inline bool operator>(const SafeString& rhs) const { return this->value_ > rhs.get(); }; + inline bool operator>(const std::string& rhs) const { return this->value_ > rhs; }; + inline bool operator>(const char* rhs) const { return this->value_ > rhs; }; + ///@} + + ///@{ + /** Lesser-or-equal comparison operator. */ + inline bool operator<=(const SafeString& rhs) const { return this->value_ <= rhs.get(); }; + inline bool operator<=(const std::string& rhs) const { return this->value_ <= rhs; }; + inline bool operator<=(const char* rhs) const { return this->value_ <= rhs; }; + ///@} + + ///@{ + /** Greater-or-equal comparison operator. */ + inline bool operator>=(const SafeString& rhs) const { return this->value_ >= rhs.get(); }; + inline bool operator>=(const std::string& rhs) const { return this->value_ >= rhs; }; + inline bool operator>=(const char* rhs) const { return this->value_ >= rhs; }; + ///@} + + /// Commit the value. + inline void commit() override { this->copy_ = nullptr; this->registered_ = false; } + + /// Revert the value. + inline void revert() override { + if (this->copy_ != nullptr) { + // Copying a string doesn't copy its capacity, we have to do it manually. + // Same goes for reserve() and shrink_to_fit(). + // See https://stackoverflow.com/a/24399554 and https://stackoverflow.com/a/38785417 + if (this->copy_->capacity() > this->value_.capacity()) { + this->value_.reserve(this->copy_->capacity()); + this->value_ = *this->copy_; + } else if (this->copy_->capacity() < this->value_.capacity()) { + this->value_ = *this->copy_; + this->value_.shrink_to_fit(); + } else { + this->value_ = *this->copy_; + } + } + this->copy_ = nullptr; this->registered_ = false; + } }; /** @@ -1358,8 +1405,6 @@ class SafeString : public SafeBase { * @param _t The safe string to write. * @return The output stream. */ -inline std::ostream& operator<<(std::ostream& _out, SafeString const& _t) { - _out << _t.get(); return _out; -} +inline std::ostream& operator<<(std::ostream& _out, SafeString const& _t) { _out << _t.get(); return _out; } #endif // SAFESTRING_H diff --git a/src/contract/variables/safetuple.h b/src/contract/variables/safetuple.h index 85516f62..db8952c9 100644 --- a/src/contract/variables/safetuple.h +++ b/src/contract/variables/safetuple.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -10,27 +10,130 @@ See the LICENSE.txt file in the project root for more information. #include #include +#include +#include #include "safebase.h" -template class SafeTuple; ///< Forward declaration of SafeTuple. @see SafeTuple - -/// Non-member get function for SafeTuple (const). @see SafeTuple +// Forward declarations. +template class SafeTuple; template decltype(auto) get(const SafeTuple& st); - -/// Non-member get function for SafeTuple (non-const). @see SafeTuple template decltype(auto) get(SafeTuple& st); /// Safe wrapper for a tuple. Used to safely store a tuple within a contract. @see SafeBase template class SafeTuple : public SafeBase { private: - std::tuple tuple_; ///< The tuple to store. - mutable std::unique_ptr> tuplePtr_; ///< The pointer to the tuple. - std::tuple committedTuple_; ///< Tracks the committed tuple. + std::tuple value_; ///< Current ("original") value. + std::tuple...> copy_; ///< Previous ("temporary") value. + + // Helper templates to detect if a type is a std::unique_ptr or not. + template struct is_unique_ptr : std::false_type {}; + template struct is_unique_ptr> : std::true_type {}; + + /** + * Template for copying a specific index from one tuple to another. + * Both tuples MUST contain the exact same quantity of items. + * Specialization for copying from the original value to the temporary one. + * Expects Tp2 to be unique_ptr, which means the underlying type inside + * the pointer MUST be the exact same as the normal type. + * @tparam I The index to access. + * @tparam Tp The types of the first tuple. + * @tparam Tp2 The types of the second tuple. + * @param from The origin tuple. + * @param to The destination tuple. + */ + template + void copyIndex(const std::tuple& from, std::tuple& to) + requires (sizeof...(Tp) == sizeof...(Tp2) && I < sizeof...(Tp) && + !is_unique_ptr(from))>>::value && + is_unique_ptr(to))>>::value && + std::is_same_v(from))>, std::decay_t(to))>> + ) { + if constexpr (!std::is_same_v(to))>>) { + using FromType = std::decay_t(from))>; + std::get(to) = std::make_unique(std::get(from)); + } + } + + /** + * Template for copying a specific index from one tuple to another. + * Both tuples MUST contain the exact same quantity of items. + * Specialization for copying from the temporary value back to the original one. + * Same concepts apply, except "from" and "to" are flipped now - "from" is + * the one with unique_ptrs and "to" is the one with normal types. + * @tparam I The index to access. + * @tparam Tp The types of the first tuple. + * @tparam Tp2 The types of the second tuple. + * @param from The origin tuple. + * @param to The destination tuple. + */ + template + void copyIndex(const std::tuple& from, std::tuple& to) + requires (sizeof...(Tp) == sizeof...(Tp2) && I < sizeof...(Tp) && + is_unique_ptr(from))>>::value && + !is_unique_ptr(to))>>::value && + std::is_same_v(from))>, std::decay_t(to))>> + ) { + if constexpr (!std::is_same_v(from))>>) { + std::get(to) = *std::get(from); + } + } + + /** + * Helper template to copy all indexes from one tuple to another. Leverages the previous templates. + * @tparam Is A sequence of indexes automatically generated from the tuples. + * @tparam Tp The types of the first tuple. + * @tparam Tp2 The types of the second tuple. + * @param from The origin tuple. + * @param to The destination tuple. + */ + template + void copyAll(const std::tuple& from, std::tuple& to, std::index_sequence) { + (copyIndex(from, to), ...); // Call each index to be copied in sequence + } + + /** + * Template to copy all indexes from one tuple to another. Leverages the respective helper. + * @tparam Tp The types of the first tuple. + * @tparam Tp2 The types of the second tuple. + * @param from The origin tuple. + * @param to The destination tuple. + */ + template + void copyAll(const std::tuple& from, std::tuple& to) { + static_assert(sizeof...(Tp) == sizeof...(Tp2), "Tuples must have the same size"); + copyAll(from, to, std::index_sequence_for{}); // Create the sequence that will be used in Is above + } + + /** + * Template to set a specific index of a pointer tuple back to nullptr. + * Expects Tp... to always be a unique_ptr. + * @tparam I The index to access. + * @tparam Tp The types of the tuple. + * @param t The tuple to operate on. + */ + template void setIndexAsNullptr(std::tuple& t) + requires(I < sizeof...(Tp) && is_unique_ptr(t))>>::value) { + std::get(t) = nullptr; + } - /// Check if the pointer is initialized (and initialize it if not). - inline void check() const { - if (tuplePtr_ == nullptr) tuplePtr_ = std::make_unique>(tuple_); + /** + * Helper template to clear the temporary tuple (set everything to nullptr). Leverages the previous template. + * @tparam Is A sequence of indexes automatically generated from the tuple. + * @tparam Tp The types of the tuple. + * @param t The tuple to operate on. + */ + template void clearCopy(std::tuple& t, std::index_sequence) { + ((setIndexAsNullptr(t)), ...); // Call each index to be nullified in sequence + } + + /** + * Template to clear a temporary tuple. Leverages the respective helper. + * @tparam Tp The types of the tuple. + * @param t The tuple to operate on. + */ + template void clearCopy(std::tuple& t) { + clearCopy(t, std::index_sequence_for{}); // Create the sequence that will be used in Is above } /// Friend get declaration for access to private members. @@ -73,61 +176,28 @@ template class SafeTuple : public SafeBase { template friend class SafeTuple; public: - SafeTuple() : SafeBase(nullptr), tuple_() {} ///< Empty constructor. + SafeTuple() : SafeBase(nullptr), value_(), copy_() {} ///< Empty constructor. /** - * Constructor with owner, also empty. + * Empty constructor with owner. * @param owner The contract that owns the variable. */ - SafeTuple(DynamicContract* owner) : SafeBase(owner), tuple_() {} + explicit SafeTuple(DynamicContract* owner) : SafeBase(owner), value_(), copy_() {} + ///@{ /** * Forward declaration constructor. * @param tpl The tuple to forward. */ - template SafeTuple(const std::tuple& tpl) : tuple_(tpl) {} - - /** - * Forward declaration constructor. - * @param tpl The tuple to forward. - */ - template SafeTuple(std::tuple&& tpl) : tuple_(std::move(tpl)) {} - - /** - * Copy constructor. - * @param other The SafeTuple to copy. - */ - SafeTuple(const SafeTuple& other) { - other.check(); - tuple_ = other.tuple_; - tuplePtr_ = std::make_unique>(*other.tuplePtr_); - } + template explicit SafeTuple(const std::tuple& tpl) : value_(tpl), copy_() {} + template explicit SafeTuple(std::tuple&& tpl) : value_(std::move(tpl)), copy_() {} + ///@} - /** - * Move constructor. - * @param other The SafeTuple to move. - */ - SafeTuple(SafeTuple&& other) noexcept { - other.check(); - tuple_ = std::move(other.tuple_); - tuplePtr_ = std::make_unique>(*other.tuplePtr_); - } + /// Copy constructor. Copies only the CURRENT value. + SafeTuple(const SafeTuple& other) : value_(other.value_), copy_() {} - /** - * Variadic constructor. - * @tparam U The argument types. - * @param args The arguments to construct the tuple with. - */ - template< - typename... U, - typename = std::enable_if_t< - !(... && std::is_base_of_v>) && - !std::conjunction_v::type...>, U>...> - > - > SafeTuple(U&&... args) : tuple_(std::forward(args)...) { - check(); - static_assert(sizeof...(U) == sizeof...(Types), "Number of arguments must match tuple size."); - } + /// Move constructor. Moves only the CURRENT value. + SafeTuple(SafeTuple&& other) noexcept : value_(std::move(other.value_)), copy_() {} /** * From pair constructor. @@ -135,23 +205,30 @@ template class SafeTuple : public SafeBase { * @tparam V The second type of the pair. * @param pair The pair to construct the tuple with. */ - template SafeTuple(const std::pair& pair) { + template explicit SafeTuple(const std::pair& pair) { static_assert(sizeof...(Types) == 2, "Tuple must have 2 elements to be constructed from a pair"); - tuple_ = std::make_tuple(pair.first, pair.second); + this->value_ = std::make_tuple(pair.first, pair.second); + } + + /** + * Helper template for getting a specific index from the original tuple (non-const). + * Used by the non-member equivalent wrapper (so the value can actually be copied). + * @tparam I The index to get. + */ + template decltype(auto) get() { + copyIndex(this->value_, this->copy_); markAsUsed(); return std::get(this->value_); } + /// Getter for the value. + inline const std::tuple& raw() const { return this->value_; } + /** * Copy assignment operator. * @param other The SafeTuple to copy. */ SafeTuple& operator=(const SafeTuple& other) { - check(); - markAsUsed(); - if (&other == this) return *this; - tuplePtr_ = std::make_unique>( - (other.tuplePtr_) ? *other.tuplePtr_ : other.tuple_ - ); - return *this; + copyAll(this->value_, this->copy_); markAsUsed(); + this->value_ = other.value_; return *this; } /** @@ -159,11 +236,8 @@ template class SafeTuple : public SafeBase { * @param other The SafeTuple to move. */ SafeTuple& operator=(SafeTuple&& other) noexcept { - check(); - markAsUsed(); - tuplePtr_ = std::move(other.tuplePtr_); - if (!tuplePtr_) tuplePtr_ = std::make_unique>(std::move(other.tuple_)); - return *this; + copyAll(this->value_, this->copy_); markAsUsed(); other.markAsUsed(); + this->value_ = std::move(other.value_); return *this; } /** @@ -173,12 +247,8 @@ template class SafeTuple : public SafeBase { * @return The SafeTuple as a tuple. */ template SafeTuple& operator=(const SafeTuple& other) { - check(); - markAsUsed(); - tuplePtr_ = std::make_unique>( - (other.tuplePtr_) ? *other.tuplePtr_ : other.tuple_ - ); - return *this; + copyAll(this->value_, this->copy_); markAsUsed(); + this->value_ = other.value_; return *this; } /** @@ -188,83 +258,86 @@ template class SafeTuple : public SafeBase { * @param pair The pair to assign. */ template SafeTuple& operator=(const std::pair& pair) { - check(); - markAsUsed(); static_assert(sizeof...(Types) == 2, "Tuple must have 2 elements to be assigned from a pair"); - tuplePtr_ = std::make_unique>(pair.first, pair.second); - return *this; + copyAll(this->value_, this->copy_); markAsUsed(); + this->value_ = pair; return *this; } /** - * Swap the contents of two SafeTuples. + * Tuple assignment operator. + * @param tpl The tuple to assign. + */ + SafeTuple& operator=(const std::tuple& tpl) { + copyAll(this->value_, this->copy_); markAsUsed(); + this->value_ = tpl; return *this; + } + + /** + * Swap the contents of two SafeTuples. Swaps only the CURRENT value. * @param other The other SafeTuple to swap with. */ void swap(SafeTuple& other) noexcept { - check(); - other.check(); - markAsUsed(); - other.markAsUsed(); - std::swap(tuple_, other.tuple_); - std::swap(tuplePtr_, other.tuplePtr_); + copyAll(this->value_, this->copy_); markAsUsed(); + copyAll(other.value_, other.copy_); other.markAsUsed(); + std::swap(this->value_, other.value_); } - /// Commit the value. Updates the value from the pointer and nullifies it. - inline void commit() override { check(); tuple_ = *tuplePtr_; tuplePtr_ = nullptr; } + /// Commit the value. + inline void commit() override { clearCopy(this->copy_); this->registered_ = false; } - /// Revert the value. Nullifies the pointer. - inline void revert() const override { tuplePtr_ = nullptr; } + /// Revert the value. + inline void revert() override { copyAll(this->copy_, this->value_); this->registered_ = false; } }; -/// Non-member get function for SafeTuple (const). @see SafeTuple +/// Non-member get function for SafeTuple (const). template decltype(auto) get(const SafeTuple& st) { - st.check(); return std::get(*st.tuplePtr_); + return std::get(st.value_); } -/// Non-member get function for SafeTuple (non-const). @see SafeTuple +/// Non-member get function for SafeTuple (non-const). template decltype(auto) get(SafeTuple& st) { - st.check(); return std::get(*st.tuplePtr_); + return st.template get(); } -/// Non-member swap function for SafeTuple. @see SafeTuple +/// Non-member swap function for SafeTuple. template void swap(SafeTuple& lhs, SafeTuple& rhs) noexcept { - lhs.check(); rhs.check(); lhs.markAsUsed(); rhs.markAsUsed(); lhs.swap(rhs); + lhs.markAsUsed(); rhs.markAsUsed(); lhs.swap(rhs); } -/// Non-member equality operator for SafeTuple. @see SafeTuple +/// Non-member equality operator for SafeTuple. template bool operator==(const SafeTuple& lhs, const SafeTuple& rhs) { - lhs.check(); rhs.check(); return lhs.tuple_ == rhs.tuple_; + return lhs.value_ == rhs.value_; } -/// Non-member inequality operator for SafeTuple. @see SafeTuple +/// Non-member inequality operator for SafeTuple. template bool operator!=(const SafeTuple& lhs, const SafeTuple& rhs) { - lhs.check(); rhs.check(); return lhs.tuple_ != rhs.tuple_; + return lhs.value_ != rhs.value_; } -/// Non-member less than operator for SafeTuple. @see SafeTuple +/// Non-member less than operator for SafeTuple. template bool operator<(const SafeTuple& lhs, const SafeTuple& rhs) { - lhs.check(); rhs.check(); return lhs.tuple_ < rhs.tuple_; + return lhs.value_ < rhs.value_; } -/// Non-member less than or equal to operator for SafeTuple. @see SafeTuple +/// Non-member less than or equal to operator for SafeTuple. template bool operator<=(const SafeTuple& lhs, const SafeTuple& rhs) { - lhs.check(); rhs.check(); return lhs.tuple_ <= rhs.tuple_; + return lhs.value_ <= rhs.value_; } - -/// Non-member greater than operator for SafeTuple. @see SafeTuple +/// Non-member greater than operator for SafeTuple. template bool operator>(const SafeTuple& lhs, const SafeTuple& rhs) { - lhs.check(); rhs.check(); return lhs.tuple_ > rhs.tuple_; + return lhs.value_ > rhs.value_; } -/// Non-member greater than or equal to operator for SafeTuple. @see SafeTuple +/// Non-member greater than or equal to operator for SafeTuple. template bool operator>=(const SafeTuple& lhs, const SafeTuple& rhs) { - lhs.check(); rhs.check(); return lhs.tuple_ >= rhs.tuple_; + return lhs.value_ >= rhs.value_; } #endif // SAFETUPLE_H diff --git a/src/contract/variables/safeuint.cpp b/src/contract/variables/safeuint.cpp new file mode 100644 index 00000000..c4446cda --- /dev/null +++ b/src/contract/variables/safeuint.cpp @@ -0,0 +1,549 @@ +#include "safeuint.h" + +// Explicit instantiations + +template class SafeUint_t<8>; +template class SafeUint_t<16>; +template class SafeUint_t<32>; +template class SafeUint_t<64>; + +template class SafeUint_t<24>; +template class SafeUint_t<40>; +template class SafeUint_t<48>; +template class SafeUint_t<56>; +template class SafeUint_t<72>; +template class SafeUint_t<80>; +template class SafeUint_t<88>; +template class SafeUint_t<96>; +template class SafeUint_t<104>; +template class SafeUint_t<112>; +template class SafeUint_t<120>; +template class SafeUint_t<128>; +template class SafeUint_t<136>; +template class SafeUint_t<144>; +template class SafeUint_t<152>; +template class SafeUint_t<160>; +template class SafeUint_t<168>; +template class SafeUint_t<176>; +template class SafeUint_t<184>; +template class SafeUint_t<192>; +template class SafeUint_t<200>; +template class SafeUint_t<208>; +template class SafeUint_t<216>; +template class SafeUint_t<224>; +template class SafeUint_t<232>; +template class SafeUint_t<240>; +template class SafeUint_t<248>; +template class SafeUint_t<256>; + +/// Biggest int type for dealing with uint/int operations +using int256_t = boost::multiprecision::number< + boost::multiprecision::cpp_int_backend< + 256, 256, boost::multiprecision::signed_magnitude, + boost::multiprecision::cpp_int_check_type::checked, void + > +>; + +// Class impl starts here + +template requires UintInterval +SafeUint_t SafeUint_t::operator+(const SafeUint_t& other) const { + if (this->value_ > std::numeric_limits::max() - other.value_) { + throw std::overflow_error("Overflow in addition operation."); + } + return SafeUint_t(this->value_ + other.value_); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator+(const uint_t& other) const { + if (this->value_ > std::numeric_limits::max() - other) { + throw std::overflow_error("Overflow in addition operation."); + } + return SafeUint_t(this->value_ + other); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator+(const int& other) const { + // uint/int operations require a conversion to a bigger int type, which will + // be operated on instead of the original value, then the remainder is checked + // to see if it fits back into the original. This is expected behaviour. + // We use int256_t as it is the biggest int type used within the project. + // Another option is to disable `checked` in the Boost types, but then we lose the + // intrinsic over/underflow checks and rely on Boost itself to do the conversion correctly. + // See https://www.boost.org/doc/libs/1_77_0/libs/multiprecision/doc/html/boost_multiprecision/tut/ints/cpp_int.html + auto tmp = static_cast(this->value_); + tmp = tmp + other; + if (tmp < 0) { + throw std::underflow_error("Underflow in addition operation."); + } else if (tmp > static_cast(std::numeric_limits::max())) { + throw std::overflow_error("Overflow in addition operation."); + } + return SafeUint_t(static_cast(tmp)); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator-(const SafeUint_t& other) const { + if (this->value_ < other.value_) throw std::underflow_error("Underflow in subtraction operation."); + return SafeUint_t(this->value_ - other.value_); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator-(const uint_t& other) const { + if (this->value_ < other) throw std::underflow_error("Underflow in subtraction operation."); + return SafeUint_t(this->value_ - other); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator-(const int& other) const { + // See operator+. + auto tmp = static_cast(this->value_); + tmp = tmp - other; + if (tmp < 0) { + throw std::underflow_error("Underflow in addition operation."); + } else if (tmp > static_cast(std::numeric_limits::max())) { + throw std::overflow_error("Overflow in addition operation."); + } + return SafeUint_t(static_cast(tmp)); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator*(const SafeUint_t& other) const { + if (other.value_ == 0 || this->value_ == 0) throw std::domain_error("Multiplication by zero"); + if (this->value_ > std::numeric_limits::max() / other.value_) { + throw std::overflow_error("Overflow in multiplication operation."); + } + return SafeUint_t(this->value_ * other.value_); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator*(const uint_t& other) const { + if (other == 0 || this->value_ == 0) throw std::domain_error("Multiplication by zero"); + if (this->value_ > std::numeric_limits::max() / other) { + throw std::overflow_error("Overflow in multiplication operation."); + } + return SafeUint_t(this->value_ * other); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator*(const int& other) const { + if (other == 0 || this->value_ == 0) throw std::domain_error("Multiplication by zero"); + if (other < 0) { + throw std::underflow_error("Underflow in multiplication operation."); + } else { + if (this->value_ > std::numeric_limits::max() / other) { + throw std::overflow_error("Overflow in multiplication operation."); + } + } + return SafeUint_t(this->value_ * other); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator/(const SafeUint_t& other) const { + if (this->value_ == 0 || other.value_ == 0) throw std::domain_error("Division by zero"); + return SafeUint_t(this->value_ / other.value_); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator/(const uint_t& other) const { + if (this->value_ == 0 || other == 0) throw std::domain_error("Division by zero"); + return SafeUint_t(this->value_ / other); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator/(const int& other) const { + if (other == 0) throw std::domain_error("Division by zero"); + // Division by a negative number results in a negative result, which cannot be represented in an unsigned integer. + if (other < 0) throw std::domain_error("Division by a negative number"); + return SafeUint_t(this->value_ / other); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator%(const SafeUint_t& other) const { + if (this->value_ == 0 || other.value_ == 0) throw std::domain_error("Modulus by zero"); + return SafeUint_t(this->value_ % other.value_); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator%(const uint_t& other) const { + if (this->value_ == 0 || other == 0) throw std::domain_error("Modulus by zero"); + return SafeUint_t(this->value_ % other); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator%(const int& other) const { + if (this->value_ == 0 || other == 0) throw std::domain_error("Modulus by zero"); + return SafeUint_t(this->value_ % static_cast(other)); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator&(const SafeUint_t& other) const { + return SafeUint_t(this->value_ & other.value_); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator&(const uint_t& other) const { + return SafeUint_t(this->value_ & other); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator&(const int& other) const { + if (other < 0) throw std::domain_error("Bitwise AND with a negative number"); + return SafeUint_t(this->value_ & static_cast(other)); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator|(const SafeUint_t& other) const { + return SafeUint_t(this->value_ | other.value_); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator|(const uint_t& other) const { + return SafeUint_t(this->value_ | other); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator|(const int& other) const { + if (other < 0) throw std::domain_error("Bitwise OR with a negative number"); + return SafeUint_t(this->value_ | static_cast(other)); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator^(const SafeUint_t& other) const { + return SafeUint_t(this->value_ ^ other.value_); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator^(const uint_t& other) const { + return SafeUint_t(this->value_ ^ other); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator^(const int& other) const { + if (other < 0) throw std::domain_error("Bitwise XOR with a negative number"); + return SafeUint_t(this->value_ ^ static_cast(other)); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator<<(const uint8_t& other) const { + return SafeUint_t(this->value_ << other); +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator>>(const uint8_t& other) const { + return SafeUint_t(this->value_ >> other); +} + +template requires UintInterval +bool SafeUint_t::operator!() const { return !(this->value_); } + +template requires UintInterval +bool SafeUint_t::operator&&(const SafeUint_t& other) const { return this->value_ && other.value_; } + +template requires UintInterval +bool SafeUint_t::operator&&(const uint_t& other) const { return this->value_ && other; } + +template requires UintInterval +bool SafeUint_t::operator||(const SafeUint_t& other) const { return this->value_ || other.value_; } + +template requires UintInterval +bool SafeUint_t::operator||(const uint_t& other) const { return this->value_ || other; } + +template requires UintInterval +bool SafeUint_t::operator==(const SafeUint_t& other) const { return this->value_ == other.value_; } + +template requires UintInterval +bool SafeUint_t::operator==(const uint_t& other) const { return this->value_ == other; } + +template requires UintInterval +bool SafeUint_t::operator==(const int& other) const { + if (other < 0) return false; // Unsigned value will never equal a negative + return this->value_ == static_cast(other); +} + +template requires UintInterval +bool SafeUint_t::operator!=(const SafeUint_t& other) const { return this->value_ != other.value_; } + +template requires UintInterval +bool SafeUint_t::operator!=(const uint_t& other) const { return this->value_ != other; } + +template requires UintInterval +bool SafeUint_t::operator!=(const int& other) const { + if (other < 0) return true; // Unsigned value will always differ from a negative + return this->value_ != static_cast(other); +} + +template requires UintInterval +bool SafeUint_t::operator<(const SafeUint_t& other) const { return this->value_ < other.value_; } + +template requires UintInterval +bool SafeUint_t::operator<(const uint_t& other) const { return this->value_ < other; } + +template requires UintInterval +bool SafeUint_t::operator<(const int& other) const { + if (other < 0) return false; // Unsigned value will never be less than a negative + return this->value_ < static_cast(other); +} + +template requires UintInterval +bool SafeUint_t::operator<=(const SafeUint_t& other) const { return this->value_ <= other.value_; } + +template requires UintInterval +bool SafeUint_t::operator<=(const uint_t& other) const { return this->value_ <= other; } + +template requires UintInterval +bool SafeUint_t::operator<=(const int& other) const { + if (other < 0) return false; // Unsigned value will never be less nor equal than a negative + return this->value_ <= static_cast(other); +} + +template requires UintInterval +bool SafeUint_t::operator>(const SafeUint_t& other) const { return this->value_ > other.value_; } + +template requires UintInterval +bool SafeUint_t::operator>(const uint_t& other) const { return this->value_ > other; } + +template requires UintInterval +bool SafeUint_t::operator>(const int& other) const { + if (other < 0) return true; // Unsigned value will always be more than a negative + return this->value_ > static_cast(other); +} + +template requires UintInterval +bool SafeUint_t::operator>=(const SafeUint_t& other) const { return this->value_ >= other.value_; } + +template requires UintInterval +bool SafeUint_t::operator>=(const uint_t& other) const { return this->value_ >= other; } + +template requires UintInterval +bool SafeUint_t::operator>=(const int& other) const { + if (other < 0) return true; // Unsigned value will be never equal, but always more than a negative + return this->value_ >= static_cast(other); +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator=(const SafeUint_t& other) { + markAsUsed(); this->value_ = other.value_; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator=(const uint_t& other) { + markAsUsed(); this->value_ = other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator=(const int& other) { + if (other < 0) throw std::domain_error("Cannot assign negative value to SafeUint_t"); + markAsUsed(); this->value_ = static_cast(other); return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator+=(const SafeUint_t& other) { + if (this->value_ > std::numeric_limits::max() - other.value_) { + throw std::overflow_error("Overflow in addition assignment operation."); + } + markAsUsed(); this->value_ += other.value_; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator+=(const uint_t& other) { + if (this->value_ > std::numeric_limits::max() - other) { + throw std::overflow_error("Overflow in addition assignment operation."); + } + markAsUsed(); this->value_ += other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator+=(const int& other) { + // See operator+. + auto tmp = static_cast(this->value_); + tmp += other; + if (tmp < 0) { + throw std::underflow_error("Underflow in addition assignment operation."); + } else if (tmp > static_cast(std::numeric_limits::max())) { + throw std::overflow_error("Overflow in addition assignment operation."); + } + markAsUsed(); this->value_ = static_cast(tmp); return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator-=(const SafeUint_t& other) { + if (this->value_ < other.value_) throw std::underflow_error("Underflow in subtraction assignment operation."); + markAsUsed(); this->value_ -= other.value_; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator-=(const uint_t& other) { + if (this->value_ < other) throw std::underflow_error("Underflow in subtraction assignment operation."); + markAsUsed(); this->value_ -= other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator-=(const int& other) { + // See operator+. + auto tmp = static_cast(this->value_); + tmp -= other; + if (tmp < 0) { + throw std::underflow_error("Underflow in addition assignment operation."); + } else if (tmp > static_cast(std::numeric_limits::max())) { + throw std::overflow_error("Overflow in addition assignment operation."); + } + markAsUsed(); this->value_ = static_cast(tmp); return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator*=(const SafeUint_t& other) { + if (other.value_ == 0 || this->value_ == 0) throw std::domain_error("Multiplication assignment by zero"); + if (this->value_ > std::numeric_limits::max() / other.value_) { + throw std::overflow_error("Overflow in multiplication assignment operation."); + } + markAsUsed(); this->value_ *= other.value_; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator*=(const uint_t& other) { + if (other == 0 || this->value_ == 0) throw std::domain_error("Multiplication assignment by zero"); + if (this->value_ > std::numeric_limits::max() / other) { + throw std::overflow_error("Overflow in multiplication assignment operation."); + } + markAsUsed(); this->value_ *= other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator*=(const int& other) { + if (other == 0 || this->value_ == 0) throw std::domain_error("Multiplication assignment by zero"); + if (other < 0) { + throw std::underflow_error("Underflow in multiplication assignment operation."); + } else { + if (this->value_ > std::numeric_limits::max() / other) { + throw std::overflow_error("Overflow in multiplication assignment operation."); + } + } + markAsUsed(); this->value_ *= other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator/=(const SafeUint_t& other) { + if (this->value_ == 0 || other.value_ == 0) throw std::domain_error("Division assignment by zero"); + markAsUsed(); this->value_ /= other.value_; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator/=(const uint_t& other) { + if (this->value_ == 0 || other == 0) throw std::domain_error("Division assignment by zero"); + markAsUsed(); this->value_ /= other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator/=(const int& other) { + if (other == 0) throw std::domain_error("Division assignment by zero"); + // Division by a negative number results in a negative result, which cannot be represented in an unsigned integer. + if (other < 0) throw std::domain_error("Division assignment by a negative number"); + markAsUsed(); this->value_ /= other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator%=(const SafeUint_t& other) { + if (this->value_ == 0 || other.value_ == 0) throw std::domain_error("Modulus assignment by zero"); + markAsUsed(); this->value_ %= other.value_; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator%=(const uint_t& other) { + if (this->value_ == 0 || other == 0) throw std::domain_error("Modulus assignment by zero"); + markAsUsed(); this->value_ %= other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator%=(const int& other) { + if (this->value_ == 0 || other == 0) throw std::domain_error("Modulus assignment by zero"); + markAsUsed(); this->value_ %= other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator&=(const SafeUint_t& other) { + markAsUsed(); this->value_ &= other.value_; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator&=(const uint_t& other) { + markAsUsed(); this->value_ &= other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator&=(const int& other) { + if (other < 0) throw std::domain_error("Bitwise AND assignment with a negative value"); + markAsUsed(); this->value_ &= other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator|=(const SafeUint_t& other) { + markAsUsed(); this->value_ |= other.value_; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator|=(const uint_t& other) { + markAsUsed(); this->value_ |= other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator|=(const int& other) { + if (other < 0) throw std::domain_error("Bitwise OR assignment with a negative value"); + markAsUsed(); this->value_ |= other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator^=(const SafeUint_t& other) { + markAsUsed(); this->value_ ^= other.value_; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator^=(const uint_t& other) { + markAsUsed(); this->value_ ^= other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator^=(const int& other) { + if (other < 0) throw std::domain_error("Bitwise XOR assignment with a negative value."); + markAsUsed(); this->value_ ^= other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator<<=(const uint8_t& other) { + markAsUsed(); this->value_ <<= other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator>>=(const uint8_t& other) { + markAsUsed(); this->value_ >>= other; return *this; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator++() { + if (this->value_ == std::numeric_limits::max()) { + throw std::overflow_error("Overflow in prefix increment operation."); + } + markAsUsed(); ++(this->value_); return *this; +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator++(int) { + if (this->value_ == std::numeric_limits::max()) { + throw std::overflow_error("Overflow in postfix increment operation."); + } + SafeUint_t tmp(this->value_); + markAsUsed(); ++(this->value_); return tmp; +} + +template requires UintInterval +SafeUint_t& SafeUint_t::operator--() { + if (this->value_ == 0) throw std::underflow_error("Underflow in prefix decrement operation."); + markAsUsed(); --(this->value_); return *this; +} + +template requires UintInterval +SafeUint_t SafeUint_t::operator--(int) { + if (this->value_ == 0) throw std::underflow_error("Underflow in postfix decrement operation."); + SafeUint_t tmp(this->value_); + markAsUsed(); --(this->value_); return tmp; +} + diff --git a/src/contract/variables/safeuint.h b/src/contract/variables/safeuint.h index 60a2e502..9c6c1c1e 100644 --- a/src/contract/variables/safeuint.h +++ b/src/contract/variables/safeuint.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,7 +8,6 @@ See the LICENSE.txt file in the project root for more information. #ifndef SAFEUINT_T_H #define SAFEUINT_T_H -#include #include #include "safebase.h" @@ -18,7 +17,9 @@ See the LICENSE.txt file in the project root for more information. */ template struct UintType { /// The type of the uint with the given size. - using type = boost::multiprecision::number>; + using type = boost::multiprecision::number>; }; /// Specialization for the type of a uint with 8 bits. @@ -41,1572 +42,627 @@ template <> struct UintType<64> { using type = uint64_t; ///< Type of the uint with 64 bits. }; -/// Template for a safe wrapper for a uint variable. +/// Biggest int type for dealing with uint/int operations +using int256_t = boost::multiprecision::number< + boost::multiprecision::cpp_int_backend< + 256, 256, boost::multiprecision::signed_magnitude, + boost::multiprecision::cpp_int_check_type::checked, void + > +>; + +/** + * Safe wrapper for a uint_t variable. + * @tparam Size The size of the uint. + */ template class SafeUint_t : public SafeBase { private: using uint_t = typename UintType::type; ///< Type of the uint. - uint_t value_; ///< The value. - mutable std::unique_ptr valuePtr_; ///< Pointer to the value. - - inline void check() const override { - if (valuePtr_ == nullptr) valuePtr_ = std::make_unique(value_); - }; + uint_t value_; ///< Current ("original") value. + uint_t copy_; ///< Previous ("temporary") value. public: static_assert(Size >= 8 && Size <= 256 && Size % 8 == 0, "Size must be between 8 and 256 and a multiple of 8."); /** * Constructor. - * @param owner The owner of the variable. - * @param value The initial value. - */ - SafeUint_t(DynamicContract* owner, const uint_t& value = 0) - : SafeBase(owner), value_(0), valuePtr_(std::make_unique(value)) - {}; - - /** - * Constructor. - * @param value The initial value. - */ - explicit SafeUint_t(const uint_t& value = 0) - : SafeBase(nullptr), value_(0), valuePtr_(std::make_unique(value)) - {}; - - /** - * Copy constructor. - * @param other The SafeUint_t to copy. - */ - SafeUint_t(const SafeUint_t& other) : SafeBase(nullptr) { - other.check(); value_ = 0; valuePtr_ = std::make_unique(*other.valuePtr_); - }; - - /** - * Getter for the value. - * @return The value. + * @param value The initial value of the variable. Defaults to 0. */ - inline uint_t get() const { check(); return *valuePtr_; }; + explicit SafeUint_t(const uint_t& value = 0) : SafeBase(nullptr), value_(value), copy_(value) {} /** - * Commit the value. + * Constructor with owner. + * @param owner The DynamicContract that owns this variable. + * @param value The initial value of the variable. Defaults to 0. */ - inline void commit() override { check(); value_ = *valuePtr_; valuePtr_ = nullptr; registered_ = false; }; + explicit SafeUint_t(DynamicContract* owner, const uint_t& value = 0) : SafeBase(owner), value_(value), copy_(value) {} - /** - * Revert the value. - */ - inline void revert() const override { valuePtr_ = nullptr; registered_ = false; }; + /// Copy constructor. Only copies the CURRENT value. + SafeUint_t(const SafeUint_t& other) : SafeBase(nullptr), value_(other.value_), copy_(other.value_) {} - // ==================== - // Arithmetic operators - // ==================== + /// Getter for the temporary value. + inline const uint_t& get() const { return this->value_; } + ///@{ /** * Addition operator. - * @param other The SafeUint_t to add. + * @param other The integer to add. * @throw std::overflow_error if an overflow happens. + * @throw std::underflow_error if an underflow happens. * @return A new SafeUint_t with the result of the addition. */ inline SafeUint_t operator+(const SafeUint_t& other) const { - check(); - if (*valuePtr_ > std::numeric_limits::max() - other.get()) - { + if (this->value_ > std::numeric_limits::max() - other.value_) { throw std::overflow_error("Overflow in addition operation."); } - return SafeUint_t(*valuePtr_ + other.get()); + return SafeUint_t(this->value_ + other.value_); } - - /** - * Addition operator. - * @param other The uint_t to add. - * @throw std::overflow_error if an overflow happens. - * @return A new SafeUint_t with the result of the addition. - */ inline SafeUint_t operator+(const uint_t& other) const { - check(); - if (*valuePtr_ > std::numeric_limits::max() - other) - { + if (this->value_ > std::numeric_limits::max() - other) { throw std::overflow_error("Overflow in addition operation."); } - return SafeUint_t(*valuePtr_ + other); + return SafeUint_t(this->value_ + other); } - - /** - * Addition operator. - * @param other The int to add. - * @throw std::overflow_error if an overflow happens. - * @throw std::underflow_error if an underflow happens. - * @return A new SafeUint_t with the result of the addition. - */ inline SafeUint_t operator+(const int& other) const { - check(); - if (other < 0) { - if (*valuePtr_ < static_cast(-other)) { - throw std::underflow_error("Underflow in addition operation."); - } - } else { - if (*valuePtr_ > std::numeric_limits::max() - other) { - throw std::overflow_error("Overflow in addition operation."); - } - } - return SafeUint_t(*valuePtr_ + other); - } - - /** - * Addition operator. - * @param other The uint_t to add. - * @throw std::overflow_error if an overflow happens. - * @return A new SafeUint_t with the result of the addition. - */ - template - inline typename std::enable_if::value, SafeUint_t>::type - operator+(const uint_t& other) const { - check(); - if (*valuePtr_ > std::numeric_limits::max() - other) - { + // uint/int operations require a conversion to a bigger int type, which will + // be operated on instead of the original value, then the remainder is checked + // to see if it fits back into the original. This is expected behaviour. + // We use int256_t as it is the biggest int type used within the project. + // Another option is to disable `checked` in the Boost types, but then we lose the + // intrinsic over/underflow checks and rely on Boost itself to do the conversion correctly. + // See https://www.boost.org/doc/libs/1_77_0/libs/multiprecision/doc/html/boost_multiprecision/tut/ints/cpp_int.html + auto tmp = static_cast(this->value_); + tmp = tmp + other; + if (tmp < 0) { + throw std::underflow_error("Underflow in addition operation."); + } else if (tmp > static_cast(std::numeric_limits::max())) { throw std::overflow_error("Overflow in addition operation."); } - return SafeUint_t(*valuePtr_ + other); + return SafeUint_t(static_cast(tmp)); } + ///@} - + ///@{ /** * Subtraction operator. - * @param other The SafeUint_t to subtract. + * @param other The integer to subtract. + * @throw std::overflow_error if an overflow happens. * @throw std::underflow_error if an underflow happens. * @return A new SafeUint_t with the result of the subtraction. */ inline SafeUint_t operator-(const SafeUint_t& other) const { - check(); - if (*valuePtr_ < other.get()) - { - throw std::underflow_error("Underflow in subtraction operation."); - } - return SafeUint_t(*valuePtr_ - other.get()); + if (this->value_ < other.value_) throw std::underflow_error("Underflow in subtraction operation."); + return SafeUint_t(this->value_ - other.value_); } - - /** - * Subtraction operator. - * @param other The uint_t to subtract. - * @throw std::underflow_error if an underflow happens. - * @return A new SafeUint_t with the result of the subtraction. - */ inline SafeUint_t operator-(const uint_t& other) const { - check(); - if (*valuePtr_ < other) - { - throw std::underflow_error("Underflow in subtraction operation."); - } - return SafeUint_t(*valuePtr_ - other); + if (this->value_ < other) throw std::underflow_error("Underflow in subtraction operation."); + return SafeUint_t(this->value_ - other); } - - /** - * Subtraction operator. - * @param other The uint_t to subtract. - * @throw std::underflow_error if an underflow happens. - * @return A new SafeUint_t with the result of the subtraction. - */ - template - inline typename std::enable_if::value, SafeUint_t>::type - operator-(const uint_t& other) const { - check(); - if (*valuePtr_ < other) - { - throw std::underflow_error("Underflow in subtraction operation."); - } - return SafeUint_t(*valuePtr_ - other); - } - - /** - * Subtraction operator. - * @param other The int to subtract. - * @throw std::overflow_error if an overflow happens. - * @throw std::underflow_error if an underflow happens. - * @return A new SafeUint_t with the result of the subtraction. - */ inline SafeUint_t operator-(const int& other) const { - check(); - if (other > 0) { - if (*valuePtr_ < static_cast(other)) { - throw std::underflow_error("Underflow in subtraction operation."); - } - } else { - if (*valuePtr_ > std::numeric_limits::max() + other) { - throw std::overflow_error("Overflow in subtraction operation."); - } + // See operator+. + auto tmp = static_cast(this->value_); + tmp = tmp - other; + if (tmp < 0) { + throw std::underflow_error("Underflow in addition operation."); + } else if (tmp > static_cast(std::numeric_limits::max())) { + throw std::overflow_error("Overflow in addition operation."); } - return SafeUint_t(*valuePtr_ - other); + return SafeUint_t(static_cast(tmp)); } + ///@} - + ///@{ /** * Multiplication operator. - * @param other The SafeUint_t to multiply. + * @param other The integer to multiply. * @throw std::overflow_error if an overflow happens. + * @throw std::underflow_error if an underflow happens. * @throw std::domain_error if the other value is zero. * @return A new SafeUint_t with the result of the multiplication. */ inline SafeUint_t operator*(const SafeUint_t& other) const { - check(); - if (other.get() == 0 || *valuePtr_ == 0) throw std::domain_error("Multiplication by zero"); - if (*valuePtr_ > std::numeric_limits::max() / other.get()) - { + if (other.value_ == 0 || this->value_ == 0) throw std::domain_error("Multiplication by zero"); + if (this->value_ > std::numeric_limits::max() / other.value_) { throw std::overflow_error("Overflow in multiplication operation."); } - return SafeUint_t(*valuePtr_ * other.get()); + return SafeUint_t(this->value_ * other.value_); } - - /** - * Multiplication operator. - * @param other The uint_t to multiply. - * @throw std::overflow_error if an overflow happens. - * @throw std::domain_error if the other value is zero. - * @return A new SafeUint_t with the result of the multiplication. - */ - inline SafeUint_t operator*(const uint_t& other) const { - check(); - if (other == 0 || *valuePtr_ == 0) throw std::domain_error("Multiplication by zero"); - if (*valuePtr_ > std::numeric_limits::max() / other) - { + if (other == 0 || this->value_ == 0) throw std::domain_error("Multiplication by zero"); + if (this->value_ > std::numeric_limits::max() / other) { throw std::overflow_error("Overflow in multiplication operation."); } - return SafeUint_t(*valuePtr_ * other); + return SafeUint_t(this->value_ * other); } - - /** - * Multiplication operator. - * @param other The uint_t to multiply. - * @throw std::overflow_error if an overflow happens. - * @throw std::domain_error if the other value is zero. - * @return A new SafeUint_t with the result of the multiplication. - */ - template - inline typename std::enable_if::value, SafeUint_t>::type - operator*(const uint_t& other) const { - check(); - if (other == 0 || *valuePtr_ == 0) throw std::domain_error("Multiplication by zero"); - if (*valuePtr_ > std::numeric_limits::max() / other) - { - throw std::overflow_error("Overflow in multiplication operation."); - } - return SafeUint_t(*valuePtr_ * other); - } - - /** - * Multiplication operator. - * @param other The int to multiply with. - * @throw std::overflow_error if an overflow happens. - * @throw std::underflow_error if an underflow happens. - * @return A new SafeUint_t with the result of the multiplication. - */ inline SafeUint_t operator*(const int& other) const { - check(); - if (other == 0 || *valuePtr_ == 0) throw std::domain_error("Multiplication by zero"); - + if (other == 0 || this->value_ == 0) throw std::domain_error("Multiplication by zero"); if (other < 0) { throw std::underflow_error("Underflow in multiplication operation."); } else { - if (*valuePtr_ > std::numeric_limits::max() / other) { + if (this->value_ > std::numeric_limits::max() / other) { throw std::overflow_error("Overflow in multiplication operation."); } } - return SafeUint_t(*valuePtr_ * other); + return SafeUint_t(this->value_ * other); } + ///@} - + ///@{ /** * Division operator. - * @param other The SafeUint_t to divide. - * @throw std::domain_error if the other value is zero. + * @param other The integer to divide. + * @throw std::domain_error if the other value is zero, or if the division results in a negative number. * @return A new SafeUint_t with the result of the division. */ inline SafeUint_t operator/(const SafeUint_t& other) const { - check(); - if (*valuePtr_ == 0 || other.get() == 0) throw std::domain_error("Division by zero"); - return SafeUint_t(*valuePtr_ / other.get()); + if (this->value_ == 0 || other.value_ == 0) throw std::domain_error("Division by zero"); + return SafeUint_t(this->value_ / other.value_); } - - /** - * Division operator. - * @param other The uint_t to divide. - * @throw std::domain_error if the other value is zero. - * @return A new SafeUint_t with the result of the division. - */ inline SafeUint_t operator/(const uint_t& other) const { - check(); - if (*valuePtr_ == 0 || other == 0) throw std::domain_error("Division by zero"); - return SafeUint_t(*valuePtr_ / other); + if (this->value_ == 0 || other == 0) throw std::domain_error("Division by zero"); + return SafeUint_t(this->value_ / other); } - - /** - * Division operator. - * @param other The uint_t to divide. - * @throw std::domain_error if the other value is zero. - * @return A new SafeUint_t with the result of the division. - */ - template - inline typename std::enable_if::value, SafeUint_t>::type - operator/(const uint_t& other) const { - check(); - if (*valuePtr_ == 0 || other == 0) throw std::domain_error("Division by zero"); - return SafeUint_t(*valuePtr_ / other); - } - - - /** - * Division operator. - * @param other The int to divide by. - * @throw std::domain_error if the other value is zero. - * @return A new SafeUint_t with the result of the division. - */ inline SafeUint_t operator/(const int& other) const { - check(); if (other == 0) throw std::domain_error("Division by zero"); - - // Division by a negative number results in a negative result, - // which cannot be represented in an unsigned integer. + // Division by a negative number results in a negative result, which cannot be represented in an unsigned integer. if (other < 0) throw std::domain_error("Division by a negative number"); - - return SafeUint_t(*valuePtr_ / other); + return SafeUint_t(this->value_ / other); } + ///@} - + ///@{ /** * Modulus operator. - * @param other The SafeUint_t to divide. + * @param other The integer to take the modulo of. * @throw std::domain_error if the other value is zero. * @return A new SafeUint_t with the result of the modulus. */ inline SafeUint_t operator%(const SafeUint_t& other) const { - check(); - if (*valuePtr_ == 0 || other.get() == 0) throw std::domain_error("Modulus by zero"); - return SafeUint_t(*valuePtr_ % other.get()); + if (this->value_ == 0 || other.value_ == 0) throw std::domain_error("Modulus by zero"); + return SafeUint_t(this->value_ % other.value_); } - - /** - * Modulus operator. - * @param other The uint_t to divide. - * @throw std::domain_error if the other value is zero. - * @return A new SafeUint_t with the result of the modulus. - */ inline SafeUint_t operator%(const uint_t& other) const { - check(); - if (*valuePtr_ == 0 || other == 0) throw std::domain_error("Modulus by zero"); - return SafeUint_t(*valuePtr_ % other); + if (this->value_ == 0 || other == 0) throw std::domain_error("Modulus by zero"); + return SafeUint_t(this->value_ % other); } - - /** - * Modulus operator. - * @param other The uint64_t to divide. - * @throw std::domain_error if the other value is zero. - * @return A new SafeUint_t with the result of the modulus. - */ - template - inline typename std::enable_if::value, SafeUint_t>::type - operator%(const uint64_t& other) const { - check(); - if (*valuePtr_ == 0 || other == 0) throw std::domain_error("Modulus by zero"); - return SafeUint_t(*valuePtr_ % other); - } - - /** - * Modulo operator. - * @param other The int to modulo. - * @throw std::domain_error if the other value is zero. - * @return A new SafeUint_t with the result of the modulo. - */ inline SafeUint_t operator%(const int& other) const { - check(); - if (*valuePtr_ == 0 || other == 0) throw std::domain_error("Modulo by zero"); - return SafeUint_t(*valuePtr_ % static_cast(other)); + if (this->value_ == 0 || other == 0) throw std::domain_error("Modulus by zero"); + return SafeUint_t(this->value_ % static_cast(other)); } + ///@} - // ================= - // Bitwise operators - // ================= - + ///@{ /** * Bitwise AND operator. - * @param other The SafeUint_t to AND. + * @param other The integer to apply AND. * @return A new SafeUint_t with the result of the AND. + * @throw std::domain_error if AND is done with a negative number. */ inline SafeUint_t operator&(const SafeUint_t& other) const { - check(); - return SafeUint_t(*valuePtr_ & other.get()); + return SafeUint_t(this->value_ & other.value_); } - - /** - * Bitwise AND operator. - * @param other The uint_t to AND. - * @return A new SafeUint_t with the result of the AND. - */ inline SafeUint_t operator&(const uint_t& other) const { - check(); - return SafeUint_t(*valuePtr_ & other); + return SafeUint_t(this->value_ & other); } - - /** - * Bitwise AND operator. - * @param other The uint64_t to AND. - * @return A new SafeUint_t with the result of the AND. - */ - template - inline typename std::enable_if::value, SafeUint_t>::type - operator&(const uint64_t& other) const { - check(); - return SafeUint_t(*valuePtr_ & other); - } - - /** - * Bitwise AND operator. - * @param other The int to AND. - * @return A new SafeUint_t with the result of the AND. - */ inline SafeUint_t operator&(const int& other) const { - check(); if (other < 0) throw std::domain_error("Bitwise AND with a negative number"); - return SafeUint_t(*valuePtr_ & static_cast(other)); + return SafeUint_t(this->value_ & static_cast(other)); } + ///@} + ///@{ /** * Bitwise OR operator. - * @param other The SafeUint_t to OR. + * @param other The integer to apply OR. * @return A new SafeUint_t with the result of the OR. + * @throw std::domain_error if OR is done with a negative number. */ inline SafeUint_t operator|(const SafeUint_t& other) const { - check(); - return SafeUint_t(*valuePtr_ | other.get()); + return SafeUint_t(this->value_ | other.value_); } - - /** - * Bitwise OR operator. - * @param other The uint_t to OR. - * @return A new SafeUint_t with the result of the OR. - */ inline SafeUint_t operator|(const uint_t& other) const { - check(); - return SafeUint_t(*valuePtr_ | other); + return SafeUint_t(this->value_ | other); } - - /** - * Bitwise OR operator. - * @param other The uint64_t to OR. - * @return A new SafeUint_t with the result of the OR. - */ - template - inline typename std::enable_if::value, SafeUint_t>::type - operator|(const uint64_t& other) const { - check(); - return SafeUint_t(*valuePtr_ | other); - } - - /** - * Bitwise OR operator. - * @param other The int to OR. - * @return A new SafeUint_t with the result of the OR. - */ inline SafeUint_t operator|(const int& other) const { - check(); if (other < 0) throw std::domain_error("Bitwise OR with a negative number"); - return SafeUint_t(*valuePtr_ | static_cast(other)); + return SafeUint_t(this->value_ | static_cast(other)); } + ///@} + ///@{ /** * Bitwise XOR operator. - * @param other The SafeUint_t to XOR. + * @param other The integer to apply XOR. * @return A new SafeUint_t with the result of the XOR. + * @throw std::domain_error if XOR is done with a negative number. */ inline SafeUint_t operator^(const SafeUint_t& other) const { - check(); - return SafeUint_t(*valuePtr_ ^ other.get()); + return SafeUint_t(this->value_ ^ other.value_); } - - /** - * Bitwise XOR operator. - * @param other The uint_t to XOR. - * @return A new SafeUint_t with the result of the XOR. - */ inline SafeUint_t operator^(const uint_t& other) const { - check(); - return SafeUint_t(*valuePtr_ ^ other); + return SafeUint_t(this->value_ ^ other); } - - /** - * Bitwise XOR operator. - * @param other The uint64_t to XOR. - * @return A new SafeUint_t with the result of the XOR. - */ - template - inline typename std::enable_if::value, SafeUint_t>::type - operator^(const uint64_t& other) const { - check(); - return SafeUint_t(*valuePtr_ ^ other); - } - - /** - * Bitwise XOR operator. - * @param other The int to XOR. - * @return A new SafeUint_t with the result of the XOR. - */ inline SafeUint_t operator^(const int& other) const { - check(); if (other < 0) throw std::domain_error("Bitwise XOR with a negative number"); - return SafeUint_t(*valuePtr_ ^ static_cast(other)); - } - - /** - * Left shift operator. - * @param other The SafeUint_t to shift. - * @return A new SafeUint_t with the result of the shift. - */ - inline SafeUint_t operator<<(const SafeUint_t& other) const { - check(); - return SafeUint_t(*valuePtr_ << other.get()); + return SafeUint_t(this->value_ ^ static_cast(other)); } + ///@} /** * Left shift operator. - * @param other The uint_t to shift. + * @param other The integer to shift. * @return A new SafeUint_t with the result of the shift. + * @throw std::domain_error if shift is done with a negative number. */ - inline SafeUint_t operator<<(const uint_t& other) const { - check(); - return SafeUint_t(*valuePtr_ << other); - } - - /** - * Left shift operator. - * @param other The uint64_t to shift. - * @return A new SafeUint_t with the result of the shift. - */ - template - inline typename std::enable_if::value, SafeUint_t>::type - operator<<(const uint64_t& other) const { - check(); - return SafeUint_t(*valuePtr_ << other); - } - - /** - * Left shift operator. - * @param other The int to shift. - * @return A new SafeUint_t with the result of the shift. - */ - inline SafeUint_t operator<<(const int& other) const { - check(); - if (other < 0) throw std::domain_error("Bitwise left shift with a negative number"); - return SafeUint_t(*valuePtr_ << other); + inline SafeUint_t operator<<(const uint8_t& other) const { + return SafeUint_t(this->value_ << other); } /** * Right shift operator. * @param other The SafeUint_t to shift. * @return A new SafeUint_t with the result of the shift. + * @throw std::domain_error if shift is done with a negative number. */ - inline SafeUint_t operator>>(const SafeUint_t& other) const { - check(); - return SafeUint_t(*valuePtr_ >> other.get()); + inline SafeUint_t operator>>(const uint8_t& other) const { + return SafeUint_t(this->value_ >> other); } - /** - * Right shift operator. - * @param other The uint_t to shift. - * @return A new SafeUint_t with the result of the shift. - */ - template - inline typename std::enable_if::value, SafeUint_t>::type - operator>>(const uint_t& other) const { - check(); - return SafeUint_t(*valuePtr_ >> other); - } - - /** - * Right shift operator. - * @param other The uint64_t to shift. - * @return A new SafeUint_t with the result of the shift. - */ - inline SafeUint_t operator>>(const uint64_t& other) const { - check(); - return SafeUint_t(*valuePtr_ >> other); - } - - /** - * Right shift operator. - * @param other The int to shift. - * @return A new SafeUint_t with the result of the shift. - */ - inline SafeUint_t operator>>(const int& other) const { - check(); - if (other < 0) throw std::domain_error("Bitwise right shift with a negative number"); - return SafeUint_t(*valuePtr_ >> other); - } - - // ================= - // Logical operators - // ================= - /** * Logical NOT operator. - * @return True if the value is zero, false otherwise. - */ - inline bool operator!() const { - check(); - return !(*valuePtr_); - } - - /** - * Logical AND operator. - * @param other The SafeUint_t to AND. - * @return True if both values are not zero, false otherwise. - */ - inline bool operator&&(const SafeUint_t& other) const { - check(); - return *valuePtr_ && other.get(); - } - - /** - * Logical AND operator. - * @param other The uint_t to AND. - * @return True if both values are not zero, false otherwise. + * @return `true` if the value is zero, `false` otherwise. */ - inline bool operator&&(const uint_t& other) const { - check(); - return *valuePtr_ && other; - } + inline bool operator!() const { return !(this->value_); } + ///@{ /** * Logical AND operator. - * @param other The uint_t to AND. - * @return True if both values are not zero, false otherwise. - */ - template - inline typename std::enable_if::value, bool>::type - operator&&(const uint_t& other) const { - check(); - return *valuePtr_ && other; - } - - - /** - * Logical OR operator. - * @param other The SafeUint_t to OR. - * @return True if at least one value is not zero, false otherwise. - */ - inline bool operator||(const SafeUint_t& other) const { - check(); - return *valuePtr_ || other.get(); - } - - /** - * Logical OR operator. - * @param other The uint_t to OR. - * @return True if at least one value is not zero, false otherwise. - */ - inline bool operator||(const uint_t& other) const { - check(); - return *valuePtr_ || other; - } - - /** - * Logical OR operator. - * @param other The uint64_t to OR. - * @return True if at least one value is not zero, false otherwise. - */ - template - inline typename std::enable_if::value, bool>::type - operator||(const uint64_t& other) const { - check(); - return *valuePtr_ || other; - } - - // ==================== - // Comparison operators - // ==================== - - /** - * Equality operator. - * @param other The SafeUint_t to compare. - * @return True if both values are equal, false otherwise. - */ - inline bool operator==(const SafeUint_t& other) const { - check(); - return *valuePtr_ == other.get(); - } - - /** - * Equality operator. - * @param other The uint_t to compare. - * @return True if both values are equal, false otherwise. - */ - inline bool operator==(const uint_t& other) const { - check(); - return *valuePtr_ == other; - } - - /** - * Equality operator. - * @param other The uint64_t to compare. - * @return True if both values are equal, false otherwise. - */ - template - inline typename std::enable_if::value, bool>::type - operator==(const uint64_t& other) const { - check(); - return *valuePtr_ == other; - } - - /** - * Equality operator. - * @param other The int to compare. - * @return True if both values are equal, false otherwise. + * @param other The integer to apply AND. + * @return `true` if both values are not zero, `false` otherwise. */ - inline bool operator==(const int& other) const { - check(); - if (other < 0) { - return false; // unsigned value cannot be equal to negative int - } - return *valuePtr_ == static_cast(other); - } - - /** - * Inequality operator. - * @param other The uint_t to compare. - * @return True if both values are not equal, false otherwise. - */ - inline bool operator!=(const uint_t& other) const { - check(); - return *valuePtr_ != other; - } - - /** - * Inequality operator. - * @param other The uint64_t to compare. - * @return True if both values are not equal, false otherwise. - */ - template - inline typename std::enable_if::value, bool>::type - operator!=(const uint64_t& other) const { - check(); - return *valuePtr_ != other; - } - - /** - * Less than operator. - * @param other The SafeUint_t to compare. - * @return True if the value is less than the other value, false otherwise. - */ - inline bool operator<(const SafeUint_t& other) const { - check(); - return *valuePtr_ < other.get(); - } - - /** - * Less than operator. - * @param other The uint_t to compare. - * @return True if the value is less than the other value, false otherwise. - */ - inline bool operator<(const uint_t& other) const { - check(); - return *valuePtr_ < other; - } - - /** - * Less than operator. - * @param other The uint64_t to compare. - * @return True if the value is less than the other value, false otherwise. - */ - template - inline typename std::enable_if::value, bool>::type - operator<(const uint64_t& other) const { - check(); - return *valuePtr_ < other; - } - - /** - * Less than or equal operator. - * @param other The SafeUint_t to compare. - * @return True if the value is less than or equal to the other value, false otherwise. - */ - inline bool operator<=(const SafeUint_t& other) const { - check(); - return *valuePtr_ <= other.get(); - } - - /** - * Less than or equal operator. - * @param other The uint_t to compare. - * @return True if the value is less than or equal to the other value, false otherwise. - */ - template - inline typename std::enable_if::value, bool>::type - operator<=(const uint_t& other) const { - check(); - return *valuePtr_ <= other; - } + inline bool operator&&(const SafeUint_t& other) const { return this->value_ && other.value_; } + inline bool operator&&(const uint_t& other) const { return this->value_ && other; } + ///@} + ///@{ /** - * Less than or equal operator. - * @param other The uint64_t to compare. - * @return True if the value is less than or equal to the other value, false otherwise. + * Logical OR operator. + * @param other The integer to apply OR. + * @return `true` if at least one value is not zero, `false` otherwise. */ - inline bool operator<=(const uint64_t& other) const { - check(); - return *valuePtr_ <= other; - } + inline bool operator||(const SafeUint_t& other) const { return this->value_ || other.value_; } + inline bool operator||(const uint_t& other) const { return this->value_ || other; } + ///@} + ///@{ /** - * Greater than operator. - * @param other The SafeUint_t to compare. - * @return True if the value is greater than the other value, false otherwise. + * Equality operator. + * @param other The integer to compare. + * @return `true` if both values are equal, `false` otherwise. */ - inline bool operator>(const SafeUint_t& other) const { - check(); - return *valuePtr_ > other.get(); + inline bool operator==(const SafeUint_t& other) const { return this->value_ == other.value_; } + inline bool operator==(const uint_t& other) const { return this->value_ == other; } + inline bool operator==(const int& other) const { + if (other < 0) return false; // Unsigned value will never equal a negative + return this->value_ == static_cast(other); } + ///@} + ///@{ /** - * Greater than operator. - * @param other The uint_t to compare. - * @return True if the value is greater than the other value, false otherwise. + * Inequality operator. + * @param other The integer to compare. + * @return `true` if both values are not equal, `false` otherwise. */ - template - inline typename std::enable_if::value, bool>::type - operator>(const uint_t& other) const { - check(); - return *valuePtr_ > other; + inline bool operator!=(const SafeUint_t& other) const { return this->value_ != other.value_; } + inline bool operator!=(const uint_t& other) const { return this->value_ != other; } + inline bool operator!=(const int& other) const { + if (other < 0) return true; // Unsigned value will always differ from a negative + return this->value_ != static_cast(other); } + ///@} + ///@{ /** - * Greater than operator. - * @param other The uint64_t to compare. - * @return True if the value is greater than the other value, false otherwise. + * Less than operator. + * @param other The integer to compare. + * @return `true` if the value is less than the other value, `false` otherwise. */ - inline bool operator>(const uint64_t& other) const { - check(); - return *valuePtr_ > other; + inline bool operator<(const SafeUint_t& other) const { return this->value_ < other.value_; } + inline bool operator<(const uint_t& other) const { return this->value_ < other; } + inline bool operator<(const int& other) const { + if (other < 0) return false; // Unsigned value will never be less than a negative + return this->value_ < static_cast(other); } + ///@} + ///@{ /** - * Greater than or equal operator. - * @param other The SafeUint_t to compare. - * @return True if the value is greater than or equal to the other value, false otherwise. + * Less than or equal operator. + * @param other The integer to compare. + * @return `true` if the value is less than or equal to the other value, `false` otherwise. */ - inline bool operator>=(const SafeUint_t& other) const { - check(); - return *valuePtr_ >= other.get(); + inline bool operator<=(const SafeUint_t& other) const { return this->value_ <= other.value_; } + inline bool operator<=(const uint_t& other) const { return this->value_ <= other; } + inline bool operator<=(const int& other) const { + if (other < 0) return false; // Unsigned value will never be less nor equal than a negative + return this->value_ <= static_cast(other); } + ///@} + ///@{ /** - * Greater than or equal operator. - * @param other The uint_t to compare. - * @return True if the value is greater than or equal to the other value, false otherwise. + * Greater than operator. + * @param other The integer to compare. + * @return `true` if the value is greater than the other value, `false` otherwise. */ - inline bool operator>=(const uint_t& other) const { - check(); - return *valuePtr_ >= other; + inline bool operator>(const SafeUint_t& other) const { return this->value_ > other.value_; } + inline bool operator>(const uint_t& other) const { return this->value_ > other; } + inline bool operator>(const int& other) const { + if (other < 0) return true; // Unsigned value will always be more than a negative + return this->value_ > static_cast(other); } + ///@} + ///@{ /** * Greater than or equal operator. - * @param other The uint64_t to compare. - * @return True if the value is greater than or equal to the other value, false otherwise. + * @param other The integer to compare. + * @return `true` if the value is greater than or equal to the other value, `false` otherwise. */ - template - inline typename std::enable_if::value, bool>::type - operator>=(const uint64_t& other) const { - check(); - return *valuePtr_ >= other; + inline bool operator>=(const SafeUint_t& other) const { return this->value_ >= other.value_; } + inline bool operator>=(const uint_t& other) const { return this->value_ >= other; } + inline bool operator>=(const int& other) const { + if (other < 0) return true; // Unsigned value will be never equal, but always more than a negative + return this->value_ >= static_cast(other); } + ///@} - // ==================== - // Assignment operators - // ==================== - + ///@{ /** * Assignment operator. - * @param other The SafeUint_t to assign. + * @param other The integer to assign. * @return A reference to this SafeUint_t. + * @throw std::domain_error if a negative value is assigned. */ inline SafeUint_t& operator=(const SafeUint_t& other) { - check(); - markAsUsed(); - *valuePtr_ = other.get(); - return *this; + markAsUsed(); this->value_ = other.value_; return *this; } - - /** - * Assignment operator. - * @param other The uint_t to assign. - * @return A reference to this SafeUint_t. - */ inline SafeUint_t& operator=(const uint_t& other) { - check(); - markAsUsed(); - *valuePtr_ = other; - return *this; + markAsUsed(); this->value_ = other; return *this; } - - /** - * Assignment operator. - * @param other The uint_t to assign. - * @return A reference to this SafeUint_t. - */ - template - inline typename std::enable_if::value, SafeUint_t&>::type - operator=(const uint_t& other) { - check(); - markAsUsed(); - *valuePtr_ = other; - return *this; - } - - /** - * Assignment operator. - * @param other The int to assign. - * @return A reference to this SafeUint_t. - */ inline SafeUint_t& operator=(const int& other) { - check(); - if (other < 0) { - throw std::domain_error("Cannot assign negative value to SafeUint_t"); - } - markAsUsed(); - *valuePtr_ = static_cast(other); - return *this; + if (other < 0) throw std::domain_error("Cannot assign negative value to SafeUint_t"); + markAsUsed(); this->value_ = static_cast(other); return *this; } + ///@} - + ///@{ /** * Addition assignment operator. - * @param other The SafeUint_t to add. - * @throw std::overflow_error if an overflow happens. + * @param other The integer to add. * @return A reference to this SafeUint_t. + * @throw std::overflow_error if an overflow happens. + * @throw std::underflow_error if an underflow happens (for signed values). */ inline SafeUint_t& operator+=(const SafeUint_t& other) { - check(); - markAsUsed(); - if (*valuePtr_ > std::numeric_limits::max() - other.get()) - { + if (this->value_ > std::numeric_limits::max() - other.value_) { throw std::overflow_error("Overflow in addition assignment operation."); } - *valuePtr_ += other.get(); - return *this; + markAsUsed(); this->value_ += other.value_; return *this; } - - /** - * Addition assignment operator. - * @param other The uint_t to add. - * @throw std::overflow_error if an overflow happens. - * @return A reference to this SafeUint_t. - */ inline SafeUint_t& operator+=(const uint_t& other) { - check(); - markAsUsed(); - if (*valuePtr_ > std::numeric_limits::max() - other) - { + if (this->value_ > std::numeric_limits::max() - other) { throw std::overflow_error("Overflow in addition assignment operation."); } - *valuePtr_ += other; - return *this; + markAsUsed(); this->value_ += other; return *this; } - - /** - * Addition assignment operator. - * @param other The uint64_t to add. - * @throw std::overflow_error if an overflow happens. - * @return A reference to this SafeUint_t. - */ - template - inline typename std::enable_if::value, SafeUint_t&>::type - operator+=(const uint64_t& other) { - check(); - markAsUsed(); - if (*valuePtr_ > std::numeric_limits::max() - other) - { - throw std::overflow_error("Overflow in addition assignment operation."); - } - *valuePtr_ += other; - return *this; - } - - /** - * Addition assignment operator. - * @param other The int to add. - * @throw std::overflow_error if an overflow happens. - * @return A reference to this SafeUint_t. - */ inline SafeUint_t& operator+=(const int& other) { - check(); - markAsUsed(); - if (other < 0 || static_cast(other) > std::numeric_limits::max() - *valuePtr_) - { + // See operator+. + auto tmp = static_cast(this->value_); + tmp += other; + if (tmp < 0) { + throw std::underflow_error("Underflow in addition assignment operation."); + } else if (tmp > static_cast(std::numeric_limits::max())) { throw std::overflow_error("Overflow in addition assignment operation."); } - *valuePtr_ += static_cast(other); - return *this; + markAsUsed(); this->value_ = static_cast(tmp); return *this; } + ///@} - + ///@{ /** * Subtraction assignment operator. - * @param other The SafeUint_t to subtract. - * @throw std::underflow_error if an underflow happens. + * @param other The integer to subtract. * @return A reference to this SafeUint_t. + * @throw std::underflow_error if an underflow happens. + * @throw std::overflow_error if an overflow happens (for signed values). */ inline SafeUint_t& operator-=(const SafeUint_t& other) { - check(); - markAsUsed(); - if (*valuePtr_ < other.get()) - { - throw std::underflow_error("Underflow in subtraction assignment operation."); - } - *valuePtr_ -= other.get(); - return *this; + if (this->value_ < other.value_) throw std::underflow_error("Underflow in subtraction assignment operation."); + markAsUsed(); this->value_ -= other.value_; return *this; } - - /** - * Subtraction assignment operator. - * @param other The uint_t to subtract. - * @throw std::underflow_error if an underflow happens. - * @return A reference to this SafeUint_t. - */ inline SafeUint_t& operator-=(const uint_t& other) { - check(); - markAsUsed(); - if (*valuePtr_ < other) - { - throw std::underflow_error("Underflow in subtraction assignment operation."); - } - *valuePtr_ -= other; - return *this; + if (this->value_ < other) throw std::underflow_error("Underflow in subtraction assignment operation."); + markAsUsed(); this->value_ -= other; return *this; } - - /** - * Subtraction assignment operator. - * @param other The uint64_t to subtract. - * @throw std::underflow_error if an underflow happens. - * @return A reference to this SafeUint_t. - */ - template - inline typename std::enable_if::value, SafeUint_t&>::type - operator-=(const uint64_t& other) { - check(); - markAsUsed(); - if (*valuePtr_ < other) - { - throw std::underflow_error("Underflow in subtraction assignment operation."); - } - *valuePtr_ -= other; - return *this; - } - - /** - * Subtraction assignment operator. - * @param other The int to subtract. - * @throw std::underflow_error if an underflow happens. - * @throw std::invalid_argument if other is negative. - * @return A reference to this SafeUint_t. - */ inline SafeUint_t& operator-=(const int& other) { - check(); - markAsUsed(); - if (other < 0) { - throw std::invalid_argument("Cannot subtract a negative value."); - } - auto other_uint = static_cast(other); - if (*valuePtr_ < other_uint) - { - throw std::underflow_error("Underflow in subtraction assignment operation."); + // See operator+. + auto tmp = static_cast(this->value_); + tmp -= other; + if (tmp < 0) { + throw std::underflow_error("Underflow in addition assignment operation."); + } else if (tmp > static_cast(std::numeric_limits::max())) { + throw std::overflow_error("Overflow in addition assignment operation."); } - *valuePtr_ -= other_uint; - return *this; + markAsUsed(); this->value_ = static_cast(tmp); return *this; } + ///@} + ///@{ /** * Multiplication assignment operator. - * @param other The SafeUint_t to multiply. - * @throw std::overflow_error if an overflow happens. - * @throw std::domain_error if the other value is zero. + * @param other The integer to multiply. * @return A reference to this SafeUint_t. + * @throw std::domain_error if the other value is zero. + * @throw std::overflow_error if an overflow happens. + * @throw std::underflow_error if an underflow happens (for signed values). */ inline SafeUint_t& operator*=(const SafeUint_t& other) { - check(); - markAsUsed(); - if (other.get() == 0 || *valuePtr_ == 0) throw std::domain_error("Multiplication assignment by zero"); - if (*valuePtr_ > std::numeric_limits::max() / other.get()) { + if (other.value_ == 0 || this->value_ == 0) throw std::domain_error("Multiplication assignment by zero"); + if (this->value_ > std::numeric_limits::max() / other.value_) { throw std::overflow_error("Overflow in multiplication assignment operation."); } - *valuePtr_ *= other.get(); - return *this; + markAsUsed(); this->value_ *= other.value_; return *this; } - - /** - * Multiplication assignment operator. - * @param other The uint_t to multiply. - * @throw std::overflow_error if an overflow happens. - * @throw std::domain_error if the other value is zero. - * @return A reference to this SafeUint_t. - */ inline SafeUint_t& operator*=(const uint_t& other) { - check(); - markAsUsed(); - if (other == 0 || *valuePtr_ == 0) throw std::domain_error("Multiplication assignment by zero"); - if (*valuePtr_ > std::numeric_limits::max() / other) { + if (other == 0 || this->value_ == 0) throw std::domain_error("Multiplication assignment by zero"); + if (this->value_ > std::numeric_limits::max() / other) { throw std::overflow_error("Overflow in multiplication assignment operation."); } - *valuePtr_ *= other; - return *this; + markAsUsed(); this->value_ *= other; return *this; } - - /** - * Multiplication assignment operator. - * @param other The int to multiply. - * @throw std::overflow_error if an overflow happens. - * @throw std::domain_error if the other value is zero or negative. - * @return A reference to this SafeUint_t. - */ inline SafeUint_t& operator*=(const int& other) { - check(); - markAsUsed(); + if (other == 0 || this->value_ == 0) throw std::domain_error("Multiplication assignment by zero"); if (other < 0) { - throw std::invalid_argument("Cannot multiply by a negative value."); - } - if (other == 0 || *valuePtr_ == 0) { - throw std::domain_error("Multiplication assignment by zero"); - } - auto other_uint = static_cast(other); - if (*valuePtr_ > std::numeric_limits::max() / other_uint) - { - throw std::overflow_error("Overflow in multiplication assignment operation."); + throw std::underflow_error("Underflow in multiplication assignment operation."); + } else { + if (this->value_ > std::numeric_limits::max() / other) { + throw std::overflow_error("Overflow in multiplication assignment operation."); + } } - *valuePtr_ *= other_uint; - return *this; + markAsUsed(); this->value_ *= other; return *this; } + ///@} - + ///@{ /** * Division assignment operator. - * @param other The SafeUint_t to divide. - * @throw std::domain_error if the other value is zero. + * @param other The integer to divide. * @return A reference to this SafeUint_t. + * @throw std::domain_error if the other value is zero or a negative number. */ inline SafeUint_t& operator/=(const SafeUint_t& other) { - check(); - markAsUsed(); - if (*valuePtr_ == 0 || other.get() == 0) throw std::domain_error("Division assignment by zero"); - *valuePtr_ /= other.get(); - return *this; + if (this->value_ == 0 || other.value_ == 0) throw std::domain_error("Division assignment by zero"); + markAsUsed(); this->value_ /= other.value_; return *this; } - - /** - * Division assignment operator. - * @param other The uint_t to divide. - * @throw std::domain_error if the other value is zero. - * @return A reference to this SafeUint_t. - */ inline SafeUint_t& operator/=(const uint_t& other) { - check(); - markAsUsed(); - if (*valuePtr_ == 0 || other == 0) throw std::domain_error("Division assignment by zero"); - *valuePtr_ /= other; - return *this; + if (this->value_ == 0 || other == 0) throw std::domain_error("Division assignment by zero"); + markAsUsed(); this->value_ /= other; return *this; } - - /** - * Division assignment operator. - * @param other The uint64_t to divide. - * @throw std::domain_error if the other value is zero. - * @return A reference to this SafeUint_t. - */ - template - inline typename std::enable_if::value, SafeUint_t&>::type - operator/=(const uint64_t& other) { - check(); - markAsUsed(); - if (*valuePtr_ == 0 || other == 0) throw std::domain_error("Division assignment by zero"); - *valuePtr_ /= other; - return *this; - } - - /** - * Division assignment operator. - * @param other The int to divide by. - * @throw std::domain_error if the other value is zero or negative. - * @return A reference to this SafeUint_t. - */ inline SafeUint_t& operator/=(const int& other) { - check(); - markAsUsed(); - if (other <= 0) { - throw std::invalid_argument("Cannot divide by a non-positive value."); - } - if (*valuePtr_ == 0) { - throw std::domain_error("Division assignment by zero"); - } - auto other_uint = static_cast(other); - *valuePtr_ /= other_uint; - return *this; + if (other == 0) throw std::domain_error("Division assignment by zero"); + // Division by a negative number results in a negative result, which cannot be represented in an unsigned integer. + if (other < 0) throw std::domain_error("Division assignment by a negative number"); + markAsUsed(); this->value_ /= other; return *this; } + ///@} - + ///@{ /** * Modulus assignment operator. - * @param other The SafeUint_t to divide. - * @throw std::domain_error if the other value is zero. + * @param other The integer to take the modulus of. * @return A reference to this SafeUint_t. + * @throw std::domain_error if the other value is zero. */ inline SafeUint_t& operator%=(const SafeUint_t& other) { - check(); - markAsUsed(); - if (*valuePtr_ == 0 || other.get() == 0) throw std::domain_error("Modulus assignment by zero"); - *valuePtr_ %= other.get(); - return *this; + if (this->value_ == 0 || other.value_ == 0) throw std::domain_error("Modulus assignment by zero"); + markAsUsed(); this->value_ %= other.value_; return *this; } - - /** - * Modulus assignment operator. - * @param other The uint_t to divide. - * @throw std::domain_error if the other value is zero. - * @return A reference to this SafeUint_t. - */ inline SafeUint_t& operator%=(const uint_t& other) { - check(); - markAsUsed(); - if (*valuePtr_ == 0 || other == 0) throw std::domain_error("Modulus assignment by zero"); - *valuePtr_ %= other; - return *this; + if (this->value_ == 0 || other == 0) throw std::domain_error("Modulus assignment by zero"); + markAsUsed(); this->value_ %= other; return *this; } - - /** - * Modulus assignment operator. - * @param other The uint64_t to divide. - * @throw std::domain_error if the other value is zero. - * @return A reference to this SafeUint_t. - */ - template - inline typename std::enable_if::value, SafeUint_t&>::type - operator%=(const uint64_t& other) { - check(); - markAsUsed(); - if (*valuePtr_ == 0 || other == 0) throw std::domain_error("Modulus assignment by zero"); - *valuePtr_ %= other; - return *this; - } - - /** - * Modulus assignment operator. - * @param other The int to divide by. - * @throw std::domain_error if the other value is zero or negative. - * @return A reference to this SafeUint_t. - */ inline SafeUint_t& operator%=(const int& other) { - check(); - markAsUsed(); - if (other <= 0) { - throw std::invalid_argument("Cannot modulus by a non-positive value."); - } - if (*valuePtr_ == 0) { - throw std::domain_error("Modulus assignment by zero"); - } - auto other_uint = static_cast(other); - *valuePtr_ %= other_uint; - return *this; + if (this->value_ == 0 || other == 0) throw std::domain_error("Modulus assignment by zero"); + markAsUsed(); this->value_ %= other; return *this; } + ///@} - + ///@{ /** * Bitwise AND assignment operator. - * @param other The SafeUint_t to AND. + * @param other The integer to apply AND. * @return A reference to this SafeUint_t. + * @throw std::domain_error if the other value is negative. */ inline SafeUint_t& operator&=(const SafeUint_t& other) { - check(); - markAsUsed(); - *valuePtr_ &= other.get(); - return *this; + markAsUsed(); this->value_ &= other.value_; return *this; } - - /** - * Bitwise AND assignment operator. - * @param other The uint_t to AND. - * @return A reference to this SafeUint_t. - */ - template - inline typename std::enable_if::value, SafeUint_t&>::type - operator&=(const uint_t& other) { - check(); - markAsUsed(); - *valuePtr_ &= other; - return *this; - } - - /** - * Bitwise AND assignment operator. - * @param other The uint64_t to AND. - * @return A reference to this SafeUint_t. - */ - inline SafeUint_t& operator&=(const uint64_t& other) { - check(); - markAsUsed(); - *valuePtr_ &= other; - return *this; + inline SafeUint_t& operator&=(const uint_t& other) { + markAsUsed(); this->value_ &= other; return *this; } - - /** - * Bitwise AND assignment operator. - * @param other The int to AND with. - * @throw std::invalid_argument if the other value is negative. - * @return A reference to this SafeUint_t. - */ inline SafeUint_t& operator&=(const int& other) { - check(); - markAsUsed(); - if (other < 0) { - throw std::invalid_argument("Cannot perform bitwise operation with a negative value."); - } - auto other_uint = static_cast(other); - *valuePtr_ &= other_uint; - return *this; + if (other < 0) throw std::domain_error("Bitwise AND assignment with a negative value"); + markAsUsed(); this->value_ &= other; return *this; } + ///@} - + ///@{ /** * Bitwise OR assignment operator. - * @param other The SafeUint_t to OR. + * @param other The integer to apply OR. * @return A reference to this SafeUint_t. + * @throw std::domain_error if the other value is negative. */ inline SafeUint_t& operator|=(const SafeUint_t& other) { - check(); - markAsUsed(); - *valuePtr_ |= other.get(); - return *this; + markAsUsed(); this->value_ |= other.value_; return *this; } - - /** - * Bitwise OR assignment operator. - * @param other The uint_t to OR. - * @return A reference to this SafeUint_t. - */ - template - inline typename std::enable_if::value, SafeUint_t&>::type - operator|=(const uint_t& other) { - check(); - markAsUsed(); - *valuePtr_ |= other; - return *this; - } - - /** - * Bitwise OR assignment operator. - * @param other The uint64_t to OR. - * @return A reference to this SafeUint_t. - */ - inline SafeUint_t& operator|=(const uint64_t& other) { - check(); - markAsUsed(); - *valuePtr_ |= other; - return *this; + inline SafeUint_t& operator|=(const uint_t& other) { + markAsUsed(); this->value_ |= other; return *this; } - - /** - * Bitwise OR assignment operator. - * @param other The int to OR with. - * @throw std::invalid_argument if the other value is negative. - * @return A reference to this SafeUint_t. - */ inline SafeUint_t& operator|=(const int& other) { - check(); - markAsUsed(); - if (other < 0) { - throw std::invalid_argument("Cannot perform bitwise operation with a negative value."); - } - auto other_uint = static_cast(other); - *valuePtr_ |= other_uint; - return *this; + if (other < 0) throw std::domain_error("Bitwise OR assignment with a negative value"); + markAsUsed(); this->value_ |= other; return *this; } + ///@} + ///@{ /** * Bitwise XOR assignment operator. - * @param other The SafeUint_t to XOR. + * @param other The integer to apply XOR. * @return A reference to this SafeUint_t. + * @throw std::domain_error if the other value is negative. */ inline SafeUint_t& operator^=(const SafeUint_t& other) { - check(); - markAsUsed(); - *valuePtr_ ^= other.get(); - return *this; + markAsUsed(); this->value_ ^= other.value_; return *this; } - - /** - * Bitwise XOR assignment operator. - * @param other The uint_t to XOR. - * @return A reference to this SafeUint_t. - */ - template - inline typename std::enable_if::value, SafeUint_t&>::type - operator^=(const uint_t& other) { - check(); - markAsUsed(); - *valuePtr_ ^= other; - return *this; - } - - /** - * Bitwise XOR assignment operator. - * @param other The uint64_t to XOR. - * @return A reference to this SafeUint_t. - */ - inline SafeUint_t& operator^=(const uint64_t& other) { - check(); - markAsUsed(); - *valuePtr_ ^= other; - return *this; + inline SafeUint_t& operator^=(const uint_t& other) { + markAsUsed(); this->value_ ^= other; return *this; } - - /** - * Bitwise XOR assignment operator. - * @param other The int to XOR with. - * @throw std::invalid_argument if the other value is negative. - * @return A reference to this SafeUint_t. - */ inline SafeUint_t& operator^=(const int& other) { - check(); - markAsUsed(); - if (other < 0) { - throw std::invalid_argument("Cannot perform bitwise operation with a negative value."); - } - auto other_uint = static_cast(other); - *valuePtr_ ^= other_uint; - return *this; - } - - /** - * Left shift assignment operator. - * @param other The SafeUint_t to shift. - * @return A reference to this SafeUint_t. - */ - inline SafeUint_t& operator<<=(const SafeUint_t& other) { - check(); - markAsUsed(); - *valuePtr_ <<= other.get(); - return *this; - } - - /** - * Left shift assignment operator. - * @param other The uint_t to shift. - * @return A reference to this SafeUint_t. - */ - template - inline typename std::enable_if::value, SafeUint_t&>::type - operator<<=(const uint_t& other) { - check(); - markAsUsed(); - *valuePtr_ <<= other; - return *this; - } - - /** - * Left shift assignment operator. - * @param other The uint64_t to shift. - * @return A reference to this SafeUint_t. - */ - inline SafeUint_t& operator<<=(const uint64_t& other) { - check(); - markAsUsed(); - *valuePtr_ <<= other; - return *this; + if (other < 0) throw std::domain_error("Bitwise XOR assignment with a negative value."); + markAsUsed(); this->value_ ^= other; return *this; } + ///@} /** * Left shift assignment operator. - * @param other The int to shift by. - * @throw std::invalid_argument if the other value is negative. - * @return A reference to this SafeUint_t. - */ - inline SafeUint_t& operator<<=(const int& other) { - check(); - markAsUsed(); - if (other < 0) { - throw std::invalid_argument("Cannot perform bitwise operation with a negative value."); - } - auto other_uint = static_cast(other); - *valuePtr_ <<= other_uint; - return *this; - } - - /** - * Right shift assignment operator. - * @param other The SafeUint_t to shift. - * @return A reference to this SafeUint_t. - */ - inline SafeUint_t& operator>>=(const SafeUint_t& other) { - check(); - markAsUsed(); - *valuePtr_ >>= other.get(); - return *this; - } - - /** - * Right shift assignment operator. - * @param other The uint_t to shift. + * @param other The integer to shift. * @return A reference to this SafeUint_t. */ - inline SafeUint_t& operator>>=(const uint_t& other) { - check(); - markAsUsed(); - *valuePtr_ >>= other; - return *this; + inline SafeUint_t& operator<<=(const uint8_t& other) { + markAsUsed(); this->value_ <<= other; return *this; } /** * Right shift assignment operator. - * @param other The uint64_t to shift. - * @return A reference to this SafeUint_t. - */ - template - inline typename std::enable_if::value, SafeUint_t&>::type - operator>>=(const uint64_t& other) { - check(); - markAsUsed(); - *valuePtr_ >>= other; - return *this; - } - - /** - * Right shift assignment operator. - * @param other The int to shift by. - * @throw std::invalid_argument if the other value is negative. + * @param other The integer to shift. * @return A reference to this SafeUint_t. */ - inline SafeUint_t& operator>>=(const int& other) { - check(); - markAsUsed(); - if (other < 0) { - throw std::invalid_argument("Cannot perform bitwise operation with a negative value."); - } - auto other_uint = static_cast(other); - *valuePtr_ >>= other_uint; - return *this; + inline SafeUint_t& operator>>=(const uint8_t& other) { + markAsUsed(); this->value_ >>= other; return *this; } - // ================================= - // Increment and decrement operators - // ================================= - /** * Prefix increment operator. * @throw std::overflow_error if an overflow happens. * @return A reference to this SafeUint_t. */ inline SafeUint_t& operator++() { - check(); - markAsUsed(); - if (*valuePtr_ == std::numeric_limits::max()) - { + if (this->value_ == std::numeric_limits::max()) { throw std::overflow_error("Overflow in prefix increment operation."); } - ++(*valuePtr_); - return *this; + markAsUsed(); ++(this->value_); return *this; } /** @@ -1615,15 +671,11 @@ template class SafeUint_t : public SafeBase { * @return A new SafeUint_t with the value before the increment. */ inline SafeUint_t operator++(int) { - check(); - markAsUsed(); - if (*valuePtr_ == std::numeric_limits::max()) - { + if (this->value_ == std::numeric_limits::max()) { throw std::overflow_error("Overflow in postfix increment operation."); } - SafeUint_t tmp(*valuePtr_); - ++(*valuePtr_); - return tmp; + SafeUint_t tmp(this->value_); + markAsUsed(); ++(this->value_); return tmp; } /** @@ -1632,14 +684,8 @@ template class SafeUint_t : public SafeBase { * @return A reference to this SafeUint_t. */ inline SafeUint_t& operator--() { - check(); - markAsUsed(); - if (*valuePtr_ == 0) - { - throw std::underflow_error("Underflow in prefix decrement operation."); - } - --(*valuePtr_); - return *this; + if (this->value_ == 0) throw std::underflow_error("Underflow in prefix decrement operation."); + markAsUsed(); --(this->value_); return *this; } /** @@ -1648,16 +694,53 @@ template class SafeUint_t : public SafeBase { * @return A new SafeUint_t with the value before the decrement. */ inline SafeUint_t operator--(int) { - check(); - markAsUsed(); - if (*valuePtr_ == 0) - { - throw std::underflow_error("Underflow in postfix decrement operation."); - } - SafeUint_t tmp(*valuePtr_); - --(*valuePtr_); - return tmp; + if (this->value_ == 0) throw std::underflow_error("Underflow in postfix decrement operation."); + SafeUint_t tmp(this->value_); + markAsUsed(); --(this->value_); return tmp; + } + + /// Commit the value. + inline void commit() override { this->copy_ = this->value_; this->registered_ = false; } + + /// Revert the value. + inline void revert() override { + this->value_ = this->copy_; + this->registered_ = false; } }; +// Aliases for SafeUint +using SafeUint8_t = SafeUint_t<8>; +using SafeUint16_t = SafeUint_t<16>; +using SafeUint24_t = SafeUint_t<24>; +using SafeUint32_t = SafeUint_t<32>; +using SafeUint40_t = SafeUint_t<40>; +using SafeUint48_t = SafeUint_t<48>; +using SafeUint56_t = SafeUint_t<56>; +using SafeUint64_t = SafeUint_t<64>; +using SafeUint72_t = SafeUint_t<72>; +using SafeUint80_t = SafeUint_t<80>; +using SafeUint88_t = SafeUint_t<88>; +using SafeUint96_t = SafeUint_t<96>; +using SafeUint104_t = SafeUint_t<104>; +using SafeUint112_t = SafeUint_t<112>; +using SafeUint120_t = SafeUint_t<120>; +using SafeUint128_t = SafeUint_t<128>; +using SafeUint136_t = SafeUint_t<136>; +using SafeUint144_t = SafeUint_t<144>; +using SafeUint152_t = SafeUint_t<152>; +using SafeUint160_t = SafeUint_t<160>; +using SafeUint168_t = SafeUint_t<168>; +using SafeUint176_t = SafeUint_t<176>; +using SafeUint184_t = SafeUint_t<184>; +using SafeUint192_t = SafeUint_t<192>; +using SafeUint200_t = SafeUint_t<200>; +using SafeUint208_t = SafeUint_t<208>; +using SafeUint216_t = SafeUint_t<216>; +using SafeUint224_t = SafeUint_t<224>; +using SafeUint232_t = SafeUint_t<232>; +using SafeUint240_t = SafeUint_t<240>; +using SafeUint248_t = SafeUint_t<248>; +using SafeUint256_t = SafeUint_t<256>; + #endif // SAFEUINT_T_H diff --git a/src/contract/variables/safeunorderedmap.h b/src/contract/variables/safeunorderedmap.h index eb6c3be8..1c24583b 100644 --- a/src/contract/variables/safeunorderedmap.h +++ b/src/contract/variables/safeunorderedmap.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -9,500 +9,584 @@ See the LICENSE.txt file in the project root for more information. #define SAFEUNORDEREDMAP_H #include -#include #include - -#include "../../utils/safehash.h" #include "safebase.h" +#include "../../utils/safehash.h" -// TODO: somehow figure out a way to make loops work with this class -// (for (const auto& [key, value] : map) { ... }) - +// TODO: somehow figure out a way to make loops work with this class (for (const auto& [key, value] : map) { ... }) /** - * Safe wrapper for an unordered_map variable. - * Used to safely store an unordered_map within a contract. + * Safe wrapper for a `boost::unordered_flat_map`. Used to safely store an unordered map within a contract. + * @tparam Key The map's key type. + * @tparam T The map's value type. * @see SafeBase */ template class SafeUnorderedMap : public SafeBase { -private: - std::unordered_map map_; ///< Value. - mutable std::unique_ptr> mapPtr_; ///< Pointer to the value. - mutable std::unique_ptr> erasedKeys_; ///< Pointer to the set of erased keys. - - /// Check if pointers are initialized (and initialize them if not). - inline void check() const override { - if (mapPtr_ == nullptr) mapPtr_ = std::make_unique>(); - if (erasedKeys_ == nullptr) erasedKeys_ = std::make_unique>(); - } - - /** - * Check if a key exists and only copy if it truly does. - * @param key The key to check. - */ - inline void checkKeyAndCopy(const Key& key) const { - check(); - auto itP = mapPtr_->find(key); - if (itP == mapPtr_->end()) { - auto itM = map_.find(key); - if (itM != map_.end()) { - auto itD = erasedKeys_->find(key); - if (itD == erasedKeys_->end()) (*mapPtr_)[key] = itM->second; + private: + boost::unordered_flat_map value_; ///< Current ("original") value. + boost::unordered_flat_map, SafeHash> copy_; ///< Previous ("temporary") value. Stores changed keys only. + + public: + /** + * Constructor. + * @param owner The contract that owns the variable. + * @param map The initial value. Defaults to an empty map. + */ + explicit SafeUnorderedMap( + DynamicContract* owner, const boost::unordered_flat_map& map = {} + ) : SafeBase(owner), value_(map), copy_() {} + + /** + * Empty constructor. + * @param map The initial value. Defaults to an empty map. + */ + explicit SafeUnorderedMap(const boost::unordered_flat_map& map = {}) + : SafeBase(nullptr), value_(map), copy_() {} + + /// Copy constructor. Copies only the CURRENT value. + SafeUnorderedMap(const SafeUnorderedMap& other) : SafeBase(nullptr), value_(other.value_), copy_() {} + + /// Get the current value. + boost::unordered_flat_map get() const { return this->value_; } + + /** + * Get the number of values with the given key. + * @param key The key of the values to count. + * @return The number of values with the given key. + */ + inline size_t count(const Key &key) const { return this->value_.count(key); } + + // TODO: find, begin() and end() are NOT safe at the moment! we need SafeIterators to do this right (normal iterator doesn't have copy logic) + + /** + * Find a given key (non-const). + * @param key The key to find. + * @return An iterator to the found key and its value. + */ + const typename boost::unordered_flat_map::iterator find(const Key& key) { + auto it = this->value_.find(key); + if (it != this->value_.end()) { + this->copy_.try_emplace((*it).first, std::in_place, (*it).second); + this->markAsUsed(); } + return it; } - } - - /** - * Check if a key exists and create a new one if it doesn't. - * @param key The key to check. - */ - inline void checkKeyAndCreate(const Key& key) const { - check(); - auto itP = mapPtr_->find(key); - if (itP == mapPtr_->end()) { - auto itM = map_.find(key); - if (itM == map_.end()) { - (*mapPtr_)[key] = T(); - } else { - auto itD = erasedKeys_->find(key); - if (itD == erasedKeys_->end()) { - (*mapPtr_)[key] = itM->second; + + /** + * Find a given key (const). + * @param key The key to find. + * @return An iterator to the found key and its value. + */ + typename boost::unordered_flat_map::const_iterator find(const Key& key) const { + return this->value_.find(key); + } + + /** + * Check if the map contains a given key. + * @param key The key to check. + * @return `true` if the unordered_map contains the given key, `false` otherwise. + */ + inline bool contains(const Key &key) const { return this->value_.contains(key); } + + /// Get an iterator to the start of the original map value. + inline const typename boost::unordered_flat_map::iterator begin() { + // begin() points to *the* first element (if it exists), so a copy is required + if ( + auto itValue = this->value_.find((*this->value_.begin()).first); + itValue != this->value_.end() + ) { + this->copy_.try_emplace((*itValue).first, std::in_place, (*itValue).second); + } + markAsUsed(); return this->value_.begin(); + } + + /// Get an iterator to the end of the original map value. + inline const typename boost::unordered_flat_map::iterator end() { + // end() points to *past* the last element (not *the* last one), so no copy is required + markAsUsed(); return this->value_.end(); + } + + /// Get a const iterator to the start of the original map value. + inline typename boost::unordered_flat_map::const_iterator cbegin() const noexcept { return this->value_.cbegin(); } + + /// Get a const iterator to the end of the original map value. + inline typename boost::unordered_flat_map::const_iterator cend() const noexcept { return this->value_.cend(); } + + /** + * Check if the map is empty (has no values). + * @return `true` if map is empty, `false` otherwise. + */ + inline bool empty() const noexcept { return this->value_.empty(); } + + /** + * Get the size of the map. + * @return The current size of the map. + */ + inline size_t size() const noexcept { return this->value_.size(); } + + /// Clear the map. + inline void clear() { + for (const auto& [key, value] : this->value_) { + // try_emplace will only insert if the key doesn't exist. + this->copy_.try_emplace(key, std::in_place, value); + } + markAsUsed(); this->value_.clear(); + } + + /** + * Insert a value into the map. Does nothing if the key already exists. + * @param value The value to insert. + * @return A pair consisting of an iterator to the inserted value and a + * boolean indicating whether the insertion was successful. + */ + std::pair::const_iterator, bool> insert( + const typename boost::unordered_flat_map::value_type& value + ) { + auto ret = this->value_.insert(value); + // Only register as changed if insert was successful. + if (ret.second) { + markAsUsed(); + // We must use try_emplace here because the key might already exist in copy_ (and we NEVER overwrite copy_). + this->copy_.try_emplace(ret.first->first, std::nullopt); + } + return ret; + } + + ///@{ + /** + * Insert a value into the map, using move. + * @param value The value to insert. + * @return A pair consisting of an iterator to the inserted value and a + * boolean indicating whether the insertion was successful. + */ + std::pair::const_iterator, bool> insert( + typename boost::unordered_flat_map::value_type&& value + ) { + auto ret = this->value_.insert(std::move(value)); + // Only register as changed if insert was successful. + if (ret.second) { + markAsUsed(); + // We must use try_emplace here because the key might already exist in copy_ (and we NEVER overwrite copy_). + this->copy_.try_emplace((*ret.first).first, std::nullopt); + } + return ret; + } + + template requires std::is_same_v> + std::pair::const_iterator, bool> insert(P&& value) { + auto ret = this->value_.insert(std::move(value)); + // Only register as changed if insert was successful. + if (ret.second) { + markAsUsed(); + // We must use try_emplace here because the key might already exist in copy_ (and we NEVER overwrite copy_). + this->copy_.try_emplace(ret.first->first, std::nullopt); + } + return ret; + } + ///@} + + /** + * Insert a value into the map, using copy and a hint (the position before the insertion). + * @param hint The hint to use. + * @param value The value to insert. + * @return An iterator to the inserted value. + */ + typename boost::unordered_flat_map::const_iterator insert( + typename boost::unordered_flat_map::const_iterator hint, + const typename boost::unordered_flat_map::value_type& value + ) { + auto ret = this->value_.insert(hint, value); + // Only register as changed if insert was successful. + if (ret != this->value_.cend()) { + markAsUsed(); + // We must use try_emplace here because the key might already exist in copy_ (and we NEVER overwrite copy_). + this->copy_.try_emplace(ret->first, std::nullopt); + } + return ret; + } + + ///@{ + /** + * Insert a value into the map, using move and a hint (the position before the insertion). + * @param hint The hint to use. + * @param value The value to insert. + * @return An iterator to the inserted value. + */ + typename boost::unordered_flat_map::const_iterator insert( + typename boost::unordered_flat_map::const_iterator hint, + typename boost::unordered_flat_map::value_type&& value + ) { + auto ret = this->value_.insert(hint, std::move(value)); + // Only register as changed if insert was successful. + if (ret != this->value_.cend()) { + markAsUsed(); + // We must use try_emplace here because the key might already exist in copy_ (and we NEVER overwrite copy_). + this->copy_.try_emplace(ret->first, std::nullopt); + } + return ret; + } + template typename boost::unordered_flat_map::const_iterator insert( + typename boost::unordered_flat_map::const_iterator hint, P&& value + ) { + auto ret = this->value_.insert(hint, std::forward(value)); + // Only register as changed if insert was successful. + if (ret != this->value_.cend()) { + markAsUsed(); + // We must use try_emplace here because the key might already exist in copy_ (and we NEVER overwrite copy_). + this->copy_.try_emplace(ret->first, std::nullopt); + } + return ret; + } + ///@} + + /** + * Insert a range of values into the map. + * @tparam InputIt Any type of iterator. + * @param first An iterator to the first value of the range. + * @param last An iterator to the last value of the range. + */ + template void insert(InputIt first, InputIt last) { + // On this insert, we copy everything because we cannot check the insert + // return to see what keys were inserted. + for (auto it = first; it != last; ++it) { + auto valueIt = this->value_.find(it->first); + if (valueIt != this->value_.end()) { + this->copy_.try_emplace(it->first, std::in_place, valueIt->second); } else { - (*mapPtr_)[key] = T(); + this->copy_.try_emplace(it->first, std::nullopt); } } + markAsUsed(); this->value_.insert(first, last); } - } - - /** - * Check if a key exists, throw if it doesn't. - * @param key The key to check. - * @throw std::runtime_error if key doesn't exist. - */ - inline void checkKeyAndThrow(const Key& key) const { - check(); - auto itP = mapPtr_->find(key); - if (itP == mapPtr_->end()) { - auto itM = map_.find(key); - if (itM == map_.end()) { - throw std::runtime_error("Key not found"); - } else { - auto itD = erasedKeys_->find(key); - if (itD == erasedKeys_->end()) { - (*mapPtr_)[key] = itM->second; + + /** + * Insert a list of values into the map. + * @param ilist The list of values to insert. + */ + void insert(std::initializer_list< + typename boost::unordered_flat_map::value_type + > ilist) { + for (const std::pair& item : ilist) { + auto valueIt = this->value_.find(item.first); + if (valueIt != this->value_.end()) { + // Try to make a original copy of the value. + this->copy_.try_emplace(item.first, std::in_place, valueIt->second); } else { - throw std::runtime_error("Key not found"); + // No value found, insert a empty optional. + this->copy_.try_emplace(item.first, std::nullopt); } } + markAsUsed(); this->value_.insert(ilist); + } + + /** + * Insert a value into the map, using move with a node handle. + * @param nh The value to insert. + * @return A pair consisting of an iterator to the inserted value and a + * boolean indicating whether the insertion was successful. + */ + std::pair::iterator, bool> + insert(typename boost::unordered_flat_map::init_type&& nh) { + // Since node will be moved we can't rely on insert return as it'll be empty, + // so we have to predict if insert will fail or not + if (!this->value_.contains(nh.first)) { markAsUsed(); this->copy_.try_emplace(nh.first, std::nullopt); } + return this->value_.insert(std::move(nh)); + } + + /** + * Insert a value into the map, using move with a node handle and a hint (the position before the insertion). + * @param nh The value to insert. + * @param hint The hint to use. + * @return An iterator to the inserted value. + */ + typename boost::unordered_flat_map::const_iterator insert( + typename boost::unordered_flat_map::const_iterator hint, + typename boost::unordered_flat_map::init_type&& nh + ) { + // Since node will be moved we can't rely on insert return as it'll be empty, + // so we have to predict if insert will fail or not + if (!this->value_.contains(nh.first)) { markAsUsed(); this->copy_.try_emplace(nh.first, std::nullopt); } + return this->value_.insert(hint, std::move(nh)); + } + + /** + * Insert a value into the map, or assign it if the key already exists. + * @param k The key to insert. + * @param obj The value to insert. + * @return A pair consisting of an iterator to the inserted value and a + * boolean indicating whether the insertion was successful. + */ + std::pair::const_iterator, bool> + insert_or_assign(const Key& k, const T& obj) { + if (auto valueIt = this->value_.find(k); valueIt != this->value_.end()) { + this->copy_.try_emplace(k, std::in_place, (*valueIt).second); + } else { + this->copy_.try_emplace(k, std::nullopt); + } + markAsUsed(); return this->value_.insert_or_assign(k, obj); + } + + /** + * Insert a value into the map, or assign it if the key already exists, using move. + * @param k The key to insert. + * @param obj The value to insert. + * @return A pair consisting of an iterator to the inserted value and a + * boolean indicating whether the insertion was successful. + */ + std::pair::const_iterator, bool> insert_or_assign(Key&& k, T&& obj) { + if (auto valueIt = this->value_.find(k); valueIt != this->value_.end()) { + this->copy_.try_emplace(k, std::in_place, valueIt->second); + } else { + this->copy_.try_emplace(k, std::nullopt); + } + markAsUsed(); return this->value_.insert_or_assign(std::move(k), std::move(obj)); } - } - -public: - /** - * Constructor. - * @param owner The contract that owns the variable. - * @param map The initial value. Defaults to an empty map. - */ - SafeUnorderedMap( - DynamicContract* owner, const std::unordered_map& map = {} - ) : SafeBase(owner), map_(map) {} - - /** - * Empty constructor. - * @param map The initial value. Defaults to an empty map. - */ - explicit SafeUnorderedMap(const std::unordered_map& map = {}) - : SafeBase(nullptr), mapPtr_(std::make_unique>(map)) {} - - /// Copy constructor. - SafeUnorderedMap(const SafeUnorderedMap& other) : SafeBase(nullptr) { - other.check(); - map_ = other.map_; - mapPtr_ = std::make_unique>(*other.mapPtr_); - erasedKeys_ = std::make_unique>(*other.erasedKeys_); - } - - /** - * Get the number of values with the given key. - * @param key The key of the values to count. - * @return The number of values with the given key. - */ - inline size_t count(const Key &key) const { checkKeyAndCopy(key); return mapPtr_->count(key); } - - /** - * Find a given key. - * @param key The key to find. - * @return An iterator to the found key and its value. - */ - typename std::unordered_map::iterator find(const Key& key) { - checkKeyAndCopy(key); markAsUsed(); return mapPtr_->find(key); - } - - /** - * Find a given key. - * @param key The key to find. - * @return An const iterator to the found key and its value. - */ - const typename std::unordered_map::const_iterator find(const Key& key) const { - checkKeyAndCopy(key); return mapPtr_->find(key); - } - - /** - * Check if the map contains a given key. - * @param key The key to check. - * @return `true` if the unordered_map contains the given key, `false` otherwise. - */ - inline bool contains(const Key &key) const { checkKeyAndCopy(key); return mapPtr_->contains(key); } - - /** - * Commit the value. Updates the values from the pointers, nullifies them - * and unregisters the variable. - */ - void commit() override { - check(); - map_.merge(*mapPtr_); - for (const auto &[key, value] : (*mapPtr_)) map_[key] = value; - for (const auto& key : (*erasedKeys_)) map_.erase(key); - mapPtr_ = nullptr; - registered_ = false; - } - - /// Revert the value. Nullifies the pointers and unregisters the variable. - void revert() const override { mapPtr_ = nullptr; erasedKeys_ = nullptr; registered_ = false; } - - /** - * Get an iterator to the start of the original map value. - * This function can only be used within a view/const function. - * Iterating over it DOES NOT load temporary values. - * @return An iterator to the start of the original map. - */ - inline std::unordered_map::const_iterator cbegin() const noexcept { return map_.cbegin(); } - - /** - * Get an iterator to the end of the original map value. - * This function can only be used within a view/const function. - * Iterating over it DOES NOT load temporary values. - * @return An iterator to the end of the original map. - */ - inline std::unordered_map::const_iterator cend() const noexcept { return map_.cend(); } - - /** - * Get an iterator to the start of the temporary map value. - * Can be used within a find() + end() combo. - * Iterating over it DOES NOT load temporary values. - * @return An iterator to the start of the temporary map. - */ - inline std::unordered_map::iterator begin() const noexcept { check(); return mapPtr_->begin(); } - - /** - * Get an iterator to the end of the temporary map value. - * Can be used within a find() + end() combo. - * Iterating over it DOES NOT load temporary values. - * @return An iterator to the end of the temporary map. - */ - inline std::unordered_map::iterator end() const noexcept { check(); return mapPtr_->end(); } - - /** - * Check if the map is empty (has no values). - * Checks both original and temporary maps. - * @return `true` if map is empty, `false` otherwise. - */ - inline bool empty() const noexcept { check(); return (map_.empty() || mapPtr_->empty()); } - - /** - * Get the size of the original map. - * ATTENTION: Only use this with care, it only return the size of the original map.. - * @return The size of the original map. - */ - inline size_t size() const noexcept { check(); return map_.size(); } - - // TODO: Find a way to implement loops, clear and iterators. - - /** - * Insert a value into the map. - * @param value The value to insert. - * @return A pair consisting of an iterator to the inserted value and a - * boolean indicating whether the insertion was successful. - */ - const std::pair::iterator, bool> insert( - const typename std::unordered_map::value_type& value - ) { - check(); markAsUsed(); return mapPtr_->insert(value); - } - - /** - * Insert a value into the map, using move. - * @param value The value to insert. - * @return A pair consisting of an iterator to the inserted value and a - * boolean indicating whether the insertion was successful. - */ - const std::pair::iterator, bool> insert( - typename std::unordered_map::value_type&& value - ) { - check(); markAsUsed(); return mapPtr_->insert(std::move(value)); - } - - /** - * Insert a value into the map, using move. - * @param value The value to insert. - * @return A pair consisting of an iterator to the inserted value and a - * boolean indicating whether the insertion was successful. - */ - const std::pair::iterator, bool> insert(T&& value) { - check(); markAsUsed(); return mapPtr_->insert(std::move(value)); - } - - /** - * Insert a value into the map, using copy and a hint (the position before the insertion). - * @param hint The hint to use. - * @param value The value to insert. - * @return An iterator to the inserted value. - */ - const typename std::unordered_map::iterator insert( - typename std::unordered_map::const_iterator hint, - const typename std::unordered_map::value_type& value + + /** + * Insert a value into the map, or assign it if the key already exists, + * using a hint (the position before the insertion). + * @param hint The hint to use. + * @param k The key to insert. + * @param obj The value to insert. + * @return An iterator to the inserted value. + */ + typename boost::unordered_flat_map::const_iterator insert_or_assign( + typename boost::unordered_flat_map::const_iterator hint, + const Key& k, const T& obj ) { - check(); markAsUsed(); return mapPtr_->insert(hint, value); - } - - /** - * Insert a value into the map, using move and a hint (the position before the insertion). - * @param hint The hint to use. - * @param value The value to insert. - * @return An iterator to the inserted value. - */ - const typename std::unordered_map::iterator insert( - typename std::unordered_map::const_iterator hint, - typename std::unordered_map::value_type&& value - ) { - check(); markAsUsed(); return mapPtr_->insert(hint, std::move(value)); - } - - /** - * Insert a value into the map, using move and a hint (the position before the insertion). - * @param hint The hint to use. - * @param value The value to insert. - * @return An iterator to the inserted value. - */ - const typename std::unordered_map::iterator insert( - typename std::unordered_map::const_iterator hint, T&& value - ) { - check(); markAsUsed(); return mapPtr_->insert(hint, std::move(value)); - } - - /** - * Insert a range of values into the map. - * @param first An iterator to the first value of the range. - * @param last An iterator to the last value of the range. - */ - template void insert(InputIt first, InputIt last) { - check(); markAsUsed(); mapPtr_->insert(first, last); - } - - /** - * Insert a list of values into the map. - * @param ilist The list of values to insert. - */ - void insert(std::initializer_list< - typename std::unordered_map::value_type - > ilist) { - check(); markAsUsed(); mapPtr_->insert(ilist); - } - - /** - * Insert a value into the map, using move with a node handle. - * @param nh The value to insert. - * @return A pair consisting of an iterator to the inserted value and a - * boolean indicating whether the insertion was successful. - */ - typename std::unordered_map::insert_return_type - insert(typename std::unordered_map::node_type&& nh) { - check(); - markAsUsed(); - return mapPtr_->insert(std::move(nh)); - } - - /** - * Insert a value into the map, using move with a node handle and a hint - * (the position before the insertion). - * @param nh The value to insert. - * @param hint The hint to use. - * @return An iterator to the inserted value. - */ - const typename std::unordered_map::iterator insert( - typename std::unordered_map::const_iterator hint, - typename std::unordered_map::node_type&& nh - ) { - check(); markAsUsed(); return mapPtr_->insert(hint, std::move(nh)); - } - - /** - * Insert a value into the map, or assign it if the key already exists. - * @param k The key to insert. - * @param obj The value to insert. - * @return A pair consisting of an iterator to the inserted value and a - * boolean indicating whether the insertion was successful. - */ - const std::pair::iterator, bool> insert_or_assign( - const Key& k, const T& obj - ) { - check(); markAsUsed(); return mapPtr_->insert_or_assign(k, obj); - } - - /** - * Insert a value into the map, or assign it if the key already exists, using move. - * @param k The key to insert. - * @param obj The value to insert. - * @return A pair consisting of an iterator to the inserted value and a - * boolean indicating whether the insertion was successful. - */ - const std::pair::iterator, bool> - insert_or_assign(Key&& k, T&& obj) { - check(); - markAsUsed(); - return mapPtr_->insert_or_assign(std::move(k), std::move(obj)); - } - - /** - * Insert a value into the map, or assign it if the key already exists, - * using a hint (the position before the insertion). - * @param hint The hint to use. - * @param k The key to insert. - * @param obj The value to insert. - * @return An iterator to the inserted value. - */ - const typename std::unordered_map::iterator insert_or_assign( - typename std::unordered_map::const_iterator hint, - const Key& k, const T& obj - ) { - check(); markAsUsed(); return mapPtr_->insert_or_assign(hint, k, obj); - } - - /** - * Insert a value into the map, or assign it if the key already exists, - * using move and a hint (the position before the insertion). - * @param hint The hint to use. - * @param k The key to insert. - * @param obj The value to insert. - * @return An iterator to the inserted value. - */ - const typename std::unordered_map::iterator insert_or_assign( - typename std::unordered_map::const_iterator hint, - Key&& k, T&& obj - ) { - check(); markAsUsed(); return mapPtr_->insert_or_assign(hint, std::move(k), std::move(obj)); - } - - /** - * Emplace a value into the map. - * @param args The arguments to build the value for insertion. - * @return A pair consisting of an iterator to the inserted value and a - * boolean indicating whether the insertion was successful. - */ - template const std::pair< - typename std::unordered_map::iterator, bool - > emplace(Args&&... args) { - check(); markAsUsed(); return mapPtr_->emplace(std::forward(args)...); - } - - /** - * Emplace a value into the map, using a hint (the position before the insertion). - * @param hint The hint to use. - * @param args The arguments to build the value for insertion. - * @return An iterator to the inserted value. - */ - template const typename std::unordered_map::iterator - emplace_hint( - typename std::unordered_map::const_iterator hint, - Args&& ...args - ) { - check(); markAsUsed(); return mapPtr_->emplace_hint(hint, std::forward(args)...); - } - - /** - * Erase a value from the map. - * @param pos The position of the value to erase. - * @return An iterator to the next value. - */ - const typename std::unordered_map::iterator erase( - typename std::unordered_map::iterator pos - ) { - check(); markAsUsed(); erasedKeys_->insert(pos->first); return mapPtr_->erase(pos); - } - - /** - * Erase a value from the map, using a const_iterator. - * @param pos The position of the value to erase. - * @return An iterator to the next value. - */ - const typename std::unordered_map::iterator erase( - typename std::unordered_map::const_iterator pos - ) { - check(); markAsUsed(); erasedKeys_->insert(pos->first); return mapPtr_->erase(pos); - } - - /** - * Erase a range of values from the map. - * @param first The first position to erase. - * @param last The last position to erase. - * @return An iterator to the next value. - */ - const typename std::unordered_map::iterator erase( - typename std::unordered_map::const_iterator first, - typename std::unordered_map::const_iterator last - ) { - check(); - markAsUsed(); - for (auto it = first; it != last; ++it) erasedKeys_->insert(it->first); - return mapPtr_->erase(first, last); - } - - /** - * Erase a value from the map, using a key. - * @param key The key of the value to erase. - * @return The number of values erased. - */ - typename std::unordered_map::size_type erase(const Key& key) { - check(); markAsUsed(); erasedKeys_->insert(key); return mapPtr_->erase(key); - } - - /** - * Erase a value from the map, using a key and move/forward. - * @param key The key of the value to erase. - * @return The number of values erased. - */ - template typename std::unordered_map::size_type erase(K&& key) { - check(); markAsUsed(); erasedKeys_->insert(std::forward(key)); - return mapPtr_->erase(std::forward(key)); - } - - /** - * Get the value with the given key. - * @param key The key to get the value from. - * @return A reference to the value within the key. - */ - inline T& at(const Key& key) { checkKeyAndThrow(key); markAsUsed(); return (*mapPtr_)[key]; } - - /// Const overload of at(). - inline const T& at(const Key& key) const { checkKeyAndThrow(key); return (*mapPtr_)[key]; } - - /// Subscript/indexing operator. Creates the key if it doesn't exist. - T& operator[](const Key& key) { checkKeyAndCreate(key); markAsUsed(); return (*mapPtr_)[key]; } - - /// Subscript/indexing operator. Creates the key if it doesn't exist. - T& operator[](Key&& key) { checkKeyAndCreate(key); markAsUsed(); return (*mapPtr_)[key]; } - - // TODO: operator= can't really be used, because it would require a copy of the map, not reversible - /// Assignment operator. - SafeUnorderedMap& operator=(const SafeUnorderedMap& other) { - if (this !=& other) { - markAsUsed(); - other.check(); - map_ = other.map; - mapPtr_ = std::make_unique(*other.mapPtr_); - erasedKeys_ = std::make_unique(*other.erasedKeys_); - } - return *this; - } + if (auto valueIt = this->value_.find(k); valueIt != this->value_.end()) { + this->copy_.try_emplace(k, std::in_place, valueIt->second); + } else { + this->copy_.try_emplace(k, std::nullopt); + } + markAsUsed(); return this->value_.insert_or_assign(hint, k, obj); + } + + /** + * Insert a value into the map, or assign it if the key already exists, + * using move and a hint (the position before the insertion). + * @param hint The hint to use. + * @param k The key to insert. + * @param obj The value to insert. + * @return An iterator to the inserted value. + */ + typename boost::unordered_flat_map::const_iterator insert_or_assign( + typename boost::unordered_flat_map::const_iterator hint, + Key&& k, T&& obj + ) { + if (auto valueIt = this->value_.find(k); valueIt != this->value_.end()) { + this->copy_.try_emplace(k, std::in_place, valueIt->second); + } else { + this->copy_.try_emplace(k, std::nullopt); + } + markAsUsed(); return this->value_.insert_or_assign(hint, std::move(k), std::move(obj)); + } + + /** + * Emplace a value into the map. + * @param args The argument to build the value for insertion (not variadic! it's just one value). + * @return A pair consisting of an iterator to the inserted value and a + * boolean indicating whether the insertion was successful. + */ + template std::pair< + typename boost::unordered_flat_map::const_iterator, bool + > emplace(Args&&... args) { + // emplace is the same as insert, it doesn`t replace the value if it already exists. + // So as there is no "copy" is the + auto ret = this->value_.emplace(std::forward(args)...); + if (ret.second) { + markAsUsed(); + // We must use try_emplace here because the key might already exist in copy_ (and we NEVER overwrite copy_). + this->copy_.try_emplace(ret.first->first, std::nullopt); + } + markAsUsed(); return ret; + } + + /** + * Emplace a value into the map, using a hint (the position before the insertion). + * @param hint The hint to use. + * @param args The arguments to build the value for insertion. + * @return An iterator to the inserted value. + */ + template typename boost::unordered_flat_map::const_iterator emplace_hint( + typename boost::unordered_flat_map::const_iterator hint, Args&&... args + ) { + auto ret = this->value_.emplace_hint(hint, std::forward(args)...); + if (ret != this->value_.cend()) { + markAsUsed(); + // We must use try_emplace here because the key might already exist in copy_ (and we NEVER overwrite copy_). + this->copy_.try_emplace(ret->first, std::nullopt); + } + return ret; + } + + /** + * Try emplacing a value into the map, do nothing if key already exists. + * @param key The key to emplace. + * @param args The argument to build the value for emplace (not variadic! it's just one value). + * @return A pair consisting of an iterator to the emplaced value and a + * boolean indicating whether the emplace was successful. + */ + template std::pair::const_iterator, bool> + try_emplace(const Key& key, Args&&... args) { + auto ret = this->value_.try_emplace(key, std::forward(args)...); + if (ret.second) { + markAsUsed(); + // We must use try_emplace here because the key might already exist in copy_ (and we NEVER overwrite copy_). + this->copy_.try_emplace(key, std::nullopt); + } + return ret; + } + + /** + * Try emplacing a value into the map, using move/forward on the key. + * @param key The key to emplace. + * @param args The argument to build the value for emplace (not variadic! it's just one value). + * @return A pair consisting of an iterator to the emplaced value and a + * boolean indicating whether the emplace was successful. + */ + template std::pair::const_iterator, bool> + try_emplace(Key&& key, Args&&... args) { + auto ret = this->value_.try_emplace(std::move(key), std::forward(args)...); + if (ret.second) { + markAsUsed(); + // We must use try_emplace here because the key might already exist in copy_ (and we NEVER overwrite copy_). + this->copy_.try_emplace(key, std::nullopt); + } + return ret; + } + + /** + * Try emplacing a value into the map, using a hint. + * @param hint The hint to use. + * @param key The key to emplace. + * @param args The argument to build the value for emplace (not variadic! it's just one value). + * @return An iterator to the emplaced value. + */ + template typename boost::unordered_flat_map::const_iterator + try_emplace(typename boost::unordered_flat_map::const_iterator hint, const Key& key, Args&&... args) { + // Only copy the value if key already exists (this overload doesn't return + // a pair so we don't know if insertion was successful) + if (!this->value_.contains(key)) { markAsUsed(); this->copy_.try_emplace(key, std::nullopt); } + return this->value_.try_emplace(hint, key, std::forward(args)...); + } + + /** + * Try emplacing a value into the map, using a hint and move/forward. + * @param hint The hint to use. + * @param key The key to emplace. + * @param args The argument to build the value for emplace (not variadic! it's just one value). + * @return An iterator to the emplaced value. + */ + template typename boost::unordered_flat_map::const_iterator + try_emplace(typename boost::unordered_flat_map::const_iterator hint, Key&& key, Args&&... args) { + // Only copy the value if key already exists (this overload doesn't return + // a pair so we don't know if insertion was successful) + if (!this->value_.contains(key)) { markAsUsed(); this->copy_.try_emplace(key, std::nullopt); } + return this->value_.try_emplace(hint, std::move(key), std::forward(args)...); + } + + /** + * Erase a value from the map. Segfaults if value is already non-existant. + * @param pos The position of the value to erase. + * @return An iterator to the next value. + */ + typename boost::unordered_flat_map::const_iterator erase( + typename boost::unordered_flat_map::const_iterator pos + ) { + if (auto itValue = this->value_.find((*pos).first); itValue != this->value_.end()) { + this->copy_.try_emplace((*itValue).first, std::in_place, (*itValue).second); + } + markAsUsed(); return this->value_.erase(pos); // segfaults if itValue == end() (doesn't exist), seems to be intended behaviour from std + } + + /** + * Erase a range of values from the map. Segfaults if any of the values are already non-existant. + * @param first The first position to erase. + * @param last The last position to erase. + * @return An iterator to the next value. + */ + typename boost::unordered_flat_map::const_iterator erase( + typename boost::unordered_flat_map::const_iterator first, + typename boost::unordered_flat_map::const_iterator last + ) { + for (auto it = first; it != last; ++it) { + if (auto itValue = this->value_.find((*it).first); itValue != this->value_.end()) { + this->copy_.try_emplace((*itValue).first, std::in_place, (*itValue).second); + } + } + markAsUsed(); return this->value_.erase(first, last); + } + + /** + * Erase a value from the map, using a key. Does nothing if the key is already non-existant. + * @param key The key of the value to erase. + * @return The number of values erased. + */ + typename boost::unordered_flat_map::size_type erase(const Key& key) { + if (auto itValue = this->value_.find(key); itValue != this->value_.end()) { + this->copy_.try_emplace((*itValue).first, std::in_place, (*itValue).second); + } + markAsUsed(); return this->value_.erase(key); + } + + ///@{ + /** + * Get the value with the given key. + * @param key The key to get the value from. + * @return A reference to the value within the key. + * @throws std::out_of_range if key does not exist in the map. + */ + inline T& at(const Key& key) { + if (auto valueIt = this->value_.find(key); valueIt != this->value_.end()) { + this->copy_.try_emplace(key, std::in_place, (*valueIt).second); + } else { + this->copy_.try_emplace(key, std::nullopt); + } + markAsUsed(); return this->value_.at(key); + } + inline const T& at(const Key& key) const { return this->value_.at(key); } + ///@} + + ///@{ + /** Subscript/indexing operator. Creates the key if it doesn't exist. */ + T& operator[](const Key& key) { + if (auto valueIt = this->value_.find(key); valueIt != this->value_.end()) { + this->copy_.try_emplace(key, std::in_place, valueIt->second); + } else { + this->copy_.try_emplace(key, std::nullopt); + } + markAsUsed(); return this->value_[key]; + } + T& operator[](Key&& key) { + if (auto valueIt = this->value_.find(key); valueIt != this->value_.end()) { + this->copy_.try_emplace(key, std::in_place, valueIt->second); + } else { + this->copy_.try_emplace(key, std::nullopt); + } + markAsUsed(); return this->value_[std::move(key)]; + } + ///@} + + ///@{ + /** Equality operator. Checks only the CURRENT value. */ + inline bool operator==(const boost::unordered_flat_map& other) const { return this->value_ == other; } + inline bool operator==(const SafeUnorderedMap& other) const { return this->value_ == other.get(); } + ///@} + + /// Commit the value. + void commit() override { this->copy_.clear(); this->registered_ = false; } + + /// Revert the value. + void revert() override { + for (const auto& [key, value] : this->copy_) { + if (value == std::nullopt) { + this->value_.erase(key); + } else { + this->value_.insert_or_assign(key, value.value()); + } + } + this->copy_.clear(); this->registered_ = false; + } }; #endif // SAFEUNORDEREDMAP_H diff --git a/src/contract/variables/safevector.h b/src/contract/variables/safevector.h index 1517b335..d828e224 100644 --- a/src/contract/variables/safevector.h +++ b/src/contract/variables/safevector.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,16 +8,19 @@ See the LICENSE.txt file in the project root for more information. #ifndef SAFEVECTOR_H #define SAFEVECTOR_H +#include +#include +#include #include -#include + #include "safebase.h" /** - * Safe wrapper for std::vector. - * This class employs a std::map for temporary storage of changes to the vector, + * Safe wrapper for `std::vector`. + * This class employs a `std::map` for temporary storage of changes to the vector, * ensuring efficient memory usage without necessitating a full vector copy or * initializing an entire vector of nullptrs for each access. - * An std::map is preferred over an std::unordered_map because of its inherent ordering. + * `std::map` is preferred over `boost::unordered_flat_map` due to its inherent ordering. * This allows safe and efficient access to indices within the current size of the vector. * Additionally, ordered iteration over newly accessed keys and prior keys is required. * For instance, with a vector of size 10, accessing indices 3, 5, 7, 10, 11, 12, 13 should @@ -26,365 +29,515 @@ See the LICENSE.txt file in the project root for more information. * @tparam T Defines the type of the vector elements. * @see SafeBase */ - -template -class SafeVector : public SafeBase { +template class SafeVector : public SafeBase { private: - std::vector vector_; ///< The original vector. - mutable std::unique_ptr> tmp_; ///< The temporary map. - mutable uint64_t maxIndex_ = 0; ///< The maximum index of the vector. - mutable bool clear_ = false; ///< Whether the vector should be cleared. - - /// Check the tmp_ variables! - inline void check() const { - if (tmp_ == nullptr) { - tmp_ = std::make_unique>(); - maxIndex_ = vector_.size(); - } - } - - /// Check a index and copy if necessary. - inline void checkIndexAndCopy(const uint64_t& index) const { - this->check(); - if (index >= maxIndex_) { - throw std::out_of_range("Index out of range"); - } - if (tmp_->contains(index)) { - return; + /** + * Enum for partial vector modifying operations, used by the undo structure. + * Full operations are not included since doing any of them disables the + * use of the undo stack from that point until commit/revert. + * NOTE: RESIZE can be either partial or total - resize(0) = clear(), every other size (for now) is considered partial + */ + enum VectorOp { + AT, OPERATOR_BRACKETS, FRONT, BACK, INSERT, EMPLACE, ERASE, INSERT_BULK, ERASE_BULK, + PUSH_BACK, EMPLACE_BACK, POP_BACK, RESIZE_MORE, RESIZE_LESS + }; + + /// Helper alias for the undo operation structure (operation made, in which index, optionally which quantity, and one or more old values). + using UndoOp = std::tuple>; + + std::vector value_; ///< Current ("original") value. + std::unique_ptr> copy_; ///< Full copy of the current value. + std::unique_ptr>> undo_; ///< Undo stack of the current value. + + /// Undo all changes in the undo stack on top of the current value. + void processUndoStack() { + while (!this->undo_->empty()) { + const auto& op = this->undo_->top(); + const auto& [opType, index, quantity, oldVals] = op; + switch (opType) { + case AT: this->value_.at(index) = oldVals[0]; break; + case OPERATOR_BRACKETS: this->value_[index] = oldVals[0]; break; + case FRONT: this->value_.at(0) = oldVals[0]; break; + case BACK: this->value_.at(this->value_.size() - 1) = oldVals[0]; break; + case INSERT: + case EMPLACE: this->value_.erase(this->value_.begin() + index); break; + case ERASE: this->value_.insert(this->value_.begin() + index, oldVals[0]); break; + case INSERT_BULK: + for (std::size_t i = 0; i < quantity; i++) { + this->value_.erase(this->value_.begin() + index); + } + break; + case ERASE_BULK: + for (std::size_t i = 0; i < quantity; i++) { + this->value_.insert(this->value_.begin() + index + i, oldVals[i]); + } + break; + case PUSH_BACK: + case EMPLACE_BACK: this->value_.pop_back(); break; + case POP_BACK: this->value_.push_back(oldVals[0]); break; + // For resize(), treat index as quantity + case RESIZE_MORE: + for (std::size_t i = 0; i < index; i++) this->value_.pop_back(); break; + case RESIZE_LESS: + for (std::size_t i = 0; i < index; i++) this->value_.push_back(oldVals[i]); break; + break; + default: + // Do nothing, for completeness + break; + } + this->undo_->pop(); } - tmp_->emplace(index, vector_[index]); } public: - - /// Default constructor. - SafeVector() : SafeBase(nullptr) {}; - /** * Constructor with owner. * @param owner The owner of the variable. + * @param vec (optional) A vector of T to use during construction. Defaults to an empty vector. */ - explicit SafeVector(DynamicContract* owner) : SafeBase(owner) {}; - - /// SafeVector( size_type count, const T& value ); - SafeVector(std::size_t count, const T& value) { - check(); - for (std::size_t i = 0; i < count; ++i) { - tmp_->emplace(i, value); - ++maxIndex_; - } - } + explicit SafeVector(DynamicContract* owner, const std::vector& vec = {}) + : SafeBase(owner), value_(vec), copy_(nullptr), undo_(nullptr) {} - /// explicit SafeVector( size_type count ); - explicit SafeVector(std::size_t count) { - check(); - for (std::size_t i = 0; i < count; ++i) { - tmp_->emplace(i, T()); - ++maxIndex_; - } + /** + * Empty constructor. + * @param vec (optional) A vector of T to use during construction. Defaults to an empty vector. + */ + explicit SafeVector(const std::vector& vec = {}) : SafeBase(nullptr), value_(vec), copy_(nullptr), undo_(nullptr) {} + + /** + * Constructor with repeating value. + * @param count The number of copies to make. + * @param value The value to copy. + */ + SafeVector(std::size_t count, const T& value) : + SafeBase(nullptr), value_(count, value), copy_(nullptr), undo_(nullptr) {} + + /** + * Constructor with empty repeating value. + * @param count The number of empty values to make. + */ + explicit SafeVector(std::size_t count) : SafeBase(nullptr), value_(count), copy_(nullptr), undo_(nullptr) {} + + /** + * Constructor with iterators. + * @tparam InputIt Any iterator type. + * @param first An iterator to the first value. + * @param last An iterator to the last value. + */ + template SafeVector(InputIt first, InputIt last) + : SafeBase(nullptr), value_(first, last), copy_(nullptr), undo_(nullptr) {} + + /** + * Constructor with initializer list. + * @param init The initializer list to use. + */ + SafeVector(std::initializer_list init) + : SafeBase(nullptr), value_(init), copy_(nullptr), undo_(nullptr) {} + + /// Copy constructor. Only copies the CURRENT value. + SafeVector(const SafeVector& other) : SafeBase(nullptr), value_(other.value_), copy_(nullptr), undo_(nullptr) {} + + /// Get the inner vector (for const functions). + inline const std::vector& get() const { return this->value_; } + + /** + * Replace the contents of the vector with copies of a value. + * @param count The number of copies to make. + * @param value The value to copy. + */ + inline void assign(std::size_t count, const T& value) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique>(this->value_); + markAsUsed(); this->value_.assign(count, value); } - /// template< class InputIt > SafeVector( InputIt first, InputIt last ); - template< class InputIt > - SafeVector(InputIt first, InputIt last) { - check(); - uint64_t i = 0; - for (auto it = first; it != last; ++it, ++i) { - tmp_->emplace(i, *it); - ++maxIndex_; - } + /** + * Replace the contents of the vector with elements from the input range [first, last). + * @tparam InputIt A type of iterator to the element. + * @param first An iterator to the first element. + * @param last An iterator to the last element. + */ + template inline void assign(InputIt first, InputIt last) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique>(this->value_); + markAsUsed(); this->value_.assign(first, last); } - /// SafeVector( const SafeVector& other ); - SafeVector(const SafeVector& other) { - check(); - other.check(); - *tmp_ = *(other.tmp_); - maxIndex_ = other.maxIndex_; + /** + * Replace the contents with elements from an initializer list. + * @param ilist The initializer list to use. + */ + inline void assign(const std::initializer_list& ilist) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique>(this->value_); + markAsUsed(); this->value_.assign(ilist); } - /// SafeVector( std::initializer_list init ); - explicit SafeVector(std::initializer_list init) { - check(); - for (const auto& val : init) { - tmp_->emplace(maxIndex_, val); - ++maxIndex_; + ///@{ + /** + * Access a specified element of the vector. + * @param pos The position of the index to access. + * @return The element at the given index. + * @throws std::out_of_range if pos is bigger than the vector's size. + */ + inline T& at(std::size_t pos) { + T& ret = this->value_.at(pos); + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(VectorOp::AT, pos, 1, std::vector{this->value_.at(pos)}); } + markAsUsed(); return ret; } + inline const T& at(std::size_t pos) const { return this->value_.at(pos); } + ///@} - /// Replaces the contents with count copies of value value. - inline void assign(std::size_t count, const T& value) { - check(); - tmp_->clear(); - for (std::size_t i = 0; i < count; ++i) { - tmp_->emplace(i, value); + ///@{ + /** + * Access a specified element of the vector (without bounds checking). + * @param pos The position of the index to access. + * @return The element at the given index. + */ + inline T& operator[](std::size_t pos) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(VectorOp::OPERATOR_BRACKETS, pos, 1, std::vector{this->value_[pos]}); } - maxIndex_ = count; - clear_ = true; + markAsUsed(); return this->value_[pos]; } + inline const T& operator[](std::size_t pos) const { return this->value_[pos]; } + ///@} - /// Replaces the contents with elements from the input range [first, last). - template - inline void assign(InputIt first, InputIt last) { - check(); - tmp_->clear(); - uint64_t i = 0; - for (auto it = first; it != last; ++it, ++i) { - tmp_->emplace(i, *it); + ///@{ + /** Access the first element of the vector. */ + inline T& front() { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(VectorOp::FRONT, 0, 1, std::vector{this->value_.at(0)}); } - maxIndex_ = i; - clear_ = true; + markAsUsed(); return this->value_.front(); } + inline const T& front() const { return this->value_.front(); } + ///@} - /// Replaces the contents with the elements from the initializer list ilist. - inline void assign(std::initializer_list ilist) { - check(); - tmp_->clear(); - uint64_t i = 0; - for (const auto& val : ilist) { - tmp_->emplace(i, val); - ++i; + ///@{ + /** Access the last element of the vector. */ + inline T& back() { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(VectorOp::BACK, this->value_.size() - 1, 1, std::vector{this->value_.at(this->value_.size() - 1)}); } - maxIndex_ = i; - clear_ = true; + markAsUsed(); return this->value_.back(); } + inline const T& back() const { return this->value_.back(); } + ///@} - /// Access specified element with bounds checking - inline T& at(std::size_t pos) { - checkIndexAndCopy(pos); - markAsUsed(); - return tmp_->at(pos); - } + /// Get a pointer to the underlying array serving as element storage. + inline const T* data() const { return this->value_.data(); } - /// Access specified element with bounds checking (const version) - const T& at(std::size_t pos) const { - checkIndexAndCopy(pos); - return tmp_->at(pos); - } + /// Get an iterator to the beginning of the vector. + inline typename std::vector::const_iterator cbegin() const { return this->value_.cbegin(); } - /// Access specified element - inline T& operator[](std::size_t pos) { - checkIndexAndCopy(pos); - markAsUsed(); - return (*tmp_)[pos]; - } + /// Get an iterator to the end of the vector. + inline typename std::vector::const_iterator cend() const { return this->value_.cend(); } - /// Access specified element (const version) - inline const T& operator[](std::size_t pos) const { - checkIndexAndCopy(pos); - return (*tmp_)[pos]; - } + /// Get a reverse iterator to the beginning of the vector. + inline typename std::vector::const_reverse_iterator crbegin() const { return this->value_.crbegin(); } - /// Return the ORIGINAL vector const begin() - inline std::vector::const_iterator cbegin() const { - return vector_.cbegin(); - } + /// Get a reverse iterator to the end of the vector. + inline typename std::vector::const_reverse_iterator crend() const { return this->value_.crend(); } - /// Return the ORIGINAL vector const end() - inline std::vector::const_iterator cend() const { - return vector_.cend(); - } + /// Check if the vector is empty. + inline bool empty() const { return this->value_.empty(); } - /// Return the ORIGINAL vector const crbegin() - inline std::vector::const_reverse_iterator crbegin() const { - return vector_.crbegin(); - } + /// Get the vector's current size. + inline std::size_t size() const { return this->value_.size(); } - /// Return the ORIGINAL vector const crend() - inline std::vector::const_reverse_iterator crend() const { - return vector_.crend(); - } + /// Get the vector's maximum size. + inline std::size_t max_size() const { return this->value_.max_size(); } + + /** + * Reserve space for a new cap of items in the vector, if the new cap is + * greater than current capacity. + * Does NOT change the vector's size or contents, therefore we don't + * consider it for a copy or undo operation. + * @param new_cap The new cap for the vector. + */ + inline void reserve(std::size_t new_cap) { markAsUsed(); this->value_.reserve(new_cap); } + + /// Get the number of items the vector has currently allocated space for. + inline std::size_t capacity() const { return this->value_.capacity(); } + + /** + * Reduce unused capacity on the vector to fit the current size. + * Does NOT change the vector's size or contents, therefore we don't + * consider it for a copy or undo operation. + */ + inline void shrink_to_fit() { markAsUsed(); this->value_.shrink_to_fit(); } - /// Check if vector is empty - inline bool empty() const { - return (maxIndex_ == 0); + /// Clear the vector. + inline void clear() { + if (this->copy_ == nullptr) this->copy_ = std::make_unique>(this->value_); + markAsUsed(); this->value_.clear(); } - /// Vector size. - inline std::size_t size() const { - check(); - return maxIndex_; + /** + * Insert an element into the vector. + * @param pos The position to insert. + * @param value The element to insert. + * @return An iterator to the element that was inserted. + */ + typename std::vector::const_iterator insert(typename std::vector::const_iterator pos, const T& value) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + std::size_t index = std::distance(this->value_.cbegin(), pos); + this->undo_->emplace(VectorOp::INSERT, index, 1, std::vector()); + } + markAsUsed(); return this->value_.insert(pos, value); } - /// Vector max_size. - inline std::size_t max_size() const { - return std::numeric_limits::max() - 1; + /** + * Insert an element into the vector, using move. + * @param pos The position to insert. + * @param value The element to insert. + * @return An iterator to the element that was inserted. + */ + typename std::vector::const_iterator insert(typename std::vector::const_iterator pos, T&& value) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + std::size_t index = std::distance(this->value_.cbegin(), pos); + this->undo_->emplace(VectorOp::INSERT, index, 1, std::vector()); + } + markAsUsed(); return this->value_.insert(pos, std::move(value)); } - /// Clear vector - inline void clear() { - check(); - markAsUsed(); - tmp_->clear(); - maxIndex_ = 0; - clear_ = true; + /** + * Insert a repeated number of the same element into the vector. + * @param pos The position to insert. + * @param count The number of times to insert. + * @param value The element to insert. + * @return An iterator to the first element that was inserted. + */ + typename std::vector::const_iterator insert(typename std::vector::const_iterator pos, std::size_t count, const T& value) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + std::size_t index = std::distance(this->value_.cbegin(), pos); + this->undo_->emplace(VectorOp::INSERT_BULK, index, count, std::vector()); + } + markAsUsed(); return this->value_.insert(pos, count, value); } - /// Insert element - /// This is not directly 1:1 to std::vector - /// As the temporary cannot return a std::vector::iterator (it is a std::map). - /// We use a uint64_t which is the index of the inserted element. - uint64_t insert(const uint64_t& pos, const T& value) { - check(); - markAsUsed(); - if (pos == maxIndex_) { - tmp_->insert_or_assign(pos, value); - ++maxIndex_; - return pos; - } else if (pos < maxIndex_) { - /// Move all elements from pos to maxIndex_ one position to the right. - /// So we can fit the new element at pos. - for (uint64_t i = maxIndex_; i > pos; --i) { - auto iter = tmp_->find(i - 1); - if (iter != tmp_->end()) { - tmp_->insert_or_assign(i, iter->second); // shift the element, - } else { - tmp_->insert_or_assign(i, vector_[i - 1]); // copy and shift the element from the original vector - } - } - tmp_->insert_or_assign(pos, value); - ++maxIndex_; - return pos; - } else { - throw std::out_of_range("pos out of range"); + /** + * Insert a range of elements into the vector using iterators. + * @param pos The position to insert. + * @param first An iterator to the first value. + * @param last An iterator to the last value. + * @return An iterator to the first element that was inserted. + */ + template requires std::input_iterator typename std::vector::const_iterator insert( + typename std::vector::const_iterator pos, InputIt first, InputIt last + ) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + std::size_t index = std::distance(this->value_.cbegin(), pos); + std::size_t diff = std::distance(first, last); + this->undo_->emplace(VectorOp::INSERT_BULK, index, diff, std::vector()); } + markAsUsed(); return this->value_.insert(pos, first, last); } - /// Erase element - /// Returns the index of the first element following the removed elements. - std::size_t erase(std::size_t pos) { - checkIndexAndCopy(pos); - markAsUsed(); - // Shift elements from the right of pos to fill the gap. - for (std::size_t i = pos; i < maxIndex_ - 1; ++i) { - auto iter = tmp_->find(i + 1); - if (iter != tmp_->end()) { - tmp_->insert_or_assign(i, iter->second); // shift the element - } else { - tmp_->insert_or_assign(i, vector_[i + 1]); // copy and shift the element from the original vector - } + /** + * Insert a list of elements into the vector. + * @param pos The position to insert. + * @param ilist The list of elements to insert. + * @return An iterator to the first element that was inserted. + */ + typename std::vector::const_iterator insert(typename std::vector::const_iterator pos, std::initializer_list ilist) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + std::size_t index = std::distance(this->value_.cbegin(), pos); + this->undo_->emplace(VectorOp::INSERT_BULK, index, ilist.size(), std::vector()); } - // Remove the last element. - tmp_->erase(maxIndex_ - 1); - --maxIndex_; - return pos; + markAsUsed(); return this->value_.insert(pos, ilist); } - /// Erase range of elements - /// Returns the index of the first element following the removed elements. - std::size_t erase(std::size_t first, std::size_t last) { - check(); - markAsUsed(); - if (first > last || last > maxIndex_) { - throw std::out_of_range("Indices out of range"); + /** + * Emplace (construct in-place) an element into the vector. + * @param pos The position to emplace. + * @param args The element to emplace. + * @return An iterator to the element that was emplaced. + */ + template typename std::vector::const_iterator emplace(typename std::vector::const_iterator pos, Args&&... args) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(VectorOp::EMPLACE, std::distance(this->value_.cbegin(), pos), 1, std::vector()); } - // Compute the number of elements to be removed. - std::size_t numToRemove = last - first; - // Shift elements from the right of last to fill the gap. - for (std::size_t i = first; i < maxIndex_ - numToRemove; ++i) { - auto iter = tmp_->find(i + numToRemove); - if (iter != tmp_->end()) { - tmp_->insert_or_assign(i, iter->second); // shift the element - } else { - tmp_->insert_or_assign(i, vector_[i + numToRemove]); // copy and shift the element from the original vector - } + markAsUsed(); return this->value_.emplace(pos, args...); + } + + /** + * Erase an element from the vector. + * @param pos The index of the element to erase. + * @return An iterator to the element after the removed one. + */ + typename std::vector::const_iterator erase(typename std::vector::const_iterator pos) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + std::size_t index = std::distance(this->value_.cbegin(), pos); + this->undo_->emplace(VectorOp::ERASE, index, 1, std::vector{this->value_.at(index)}); } - // Remove the last numToRemove elements. - for (std::size_t i = 0; i < numToRemove; ++i) { - tmp_->erase(maxIndex_ - 1 - i); + markAsUsed(); return this->value_.erase(pos); + } + + /** + * Erase a range of elements from the vector. + * @param first An iterator to the first value. + * @param last An iterator to the last value. + * @return An iterator to the element after the last removed one. + */ + typename std::vector::const_iterator erase( + typename std::vector::const_iterator first, typename std::vector::const_iterator last + ) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + std::size_t index = std::distance(this->value_.cbegin(), first); + std::size_t diff = std::distance(first, last); + std::vector oldVals = std::vector(first, last); + this->undo_->emplace(VectorOp::ERASE_BULK, index, diff, oldVals); } - maxIndex_ -= numToRemove; - return first; + markAsUsed(); return this->value_.erase(first, last); } - /// Appends the given element value to the end of the container. + /** + * Append an element to the end of the vector. + * @param value The value to append. + */ void push_back(const T& value) { - check(); - markAsUsed(); - tmp_->emplace(maxIndex_, value); - ++maxIndex_; + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(VectorOp::PUSH_BACK, 0, 0, std::vector()); + } + markAsUsed(); this->value_.push_back(value); } - /// Emplace element at the end of the container. - void emplace_back(T&& value) { - check(); - markAsUsed(); - tmp_->emplace(maxIndex_, std::move(value)); - ++maxIndex_; + /** + * Append an element to the end of the vector, using move. + * @param value The value to append. + */ + void push_back(T&& value) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(VectorOp::PUSH_BACK, 0, 0, std::vector()); + } + markAsUsed(); this->value_.push_back(std::move(value)); } - /// Removes the last element of the container. + /** + * Emplace an element at the end of the vector. + * @param value The value to emplace. + */ + T& emplace_back(T&& value) { + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(VectorOp::EMPLACE_BACK, 0, 0, std::vector()); + } + markAsUsed(); return this->value_.emplace_back(value); + } + + /// Erase the element at the end of the vector. void pop_back() { - check(); - markAsUsed(); - tmp_->erase(maxIndex_ - 1); - --maxIndex_; + if (this->copy_ == nullptr) { + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + this->undo_->emplace(VectorOp::POP_BACK, 0, 0, std::vector{this->value_.back()}); + } + markAsUsed(); this->value_.pop_back(); } - /// Changes the number of elements stored (default-constructed elements are appended) + /** + * Resize the vector to hold a given number of elements. + * Default-constructed elements are appended. + * @param count The number of items for the new size. + */ void resize(std::size_t count) { - check(); - if (count < maxIndex_) { - for (std::size_t i = count; i < maxIndex_; ++i) { - tmp_->erase(i); - } - } else if (count > maxIndex_) { - for (std::size_t i = maxIndex_; i < count; ++i) { - tmp_->emplace(i, T()); + if (this->copy_ == nullptr) { + if (count == 0) { // Treat as full operation if resize(0), otherwise treat as partial + this->copy_ = std::make_unique>(this->value_); + } else if (count != this->value_.size()) { // Only consider undo if size will actually change + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + VectorOp vecOp; // RESIZE_MORE (will be bigger) or RESIZE_LESS (will be smaller) + std::size_t diff = 0; // Size difference between old and new vector + std::vector vals = {}; // Old values from before the operation + if (count > this->value_.size()) { + vecOp = VectorOp::RESIZE_MORE; + diff = count - this->value_.size(); + } else if (count < this->value_.size()) { + vecOp = VectorOp::RESIZE_LESS; + diff = this->value_.size() - count; + vals = std::vector(this->value_.end() - diff, this->value_.end()); + } + this->undo_->emplace(vecOp, diff, 0, vals); } } - maxIndex_ = count; - markAsUsed(); + markAsUsed(); this->value_.resize(count); } - /// Changes the number of elements stored (new elements are appended and initialized with `value`) + /** + * Resize the vector to hold a given number of elements. + * If new size is bigger, new elements are appended and initialized. + * @param count The number of items for the new size. + * @param value The value to append and initialize. + */ void resize(std::size_t count, const T& value) { - check(); - if (count < maxIndex_) { - for (std::size_t i = count; i < maxIndex_; ++i) { - tmp_->erase(i); - } - } else if (count > maxIndex_) { - for (std::size_t i = maxIndex_; i < count; ++i) { - tmp_->emplace(i, value); + if (this->copy_ == nullptr) { + if (count == 0) { // Treat as full operation if resize(0), otherwise treat as partial + this->copy_ = std::make_unique>(this->value_); + } else if (count != this->value_.size()) { // Only consider undo if size will actually change + if (this->undo_ == nullptr) this->undo_ = std::make_unique>>(); + VectorOp vecOp; // RESIZE_MORE (will be bigger) or RESIZE_LESS (will be smaller) + std::size_t diff = 0; // Size difference between old and new vector + std::vector vals = {}; // Old values from before the operation + if (count > this->value_.size()) { + vecOp = VectorOp::RESIZE_MORE; + diff = count - this->value_.size(); + } else if (count < this->value_.size()) { + vecOp = VectorOp::RESIZE_LESS; + diff = this->value_.size() - count; + vals = std::vector(this->value_.end() - diff, this->value_.end()); + } + this->undo_->emplace(vecOp, diff, 0, vals); } } - maxIndex_ = count; - markAsUsed(); + markAsUsed(); this->value_.resize(count, value); } - /// Commit function. - void commit() override { - check(); - if (clear_) { - vector_.clear(); - clear_ = false; - } - /// Erase difference in size. - if (vector_.size() > maxIndex_) { - vector_.erase(vector_.begin() + maxIndex_, vector_.end()); - } - - for (auto& it : *tmp_) { - if (it.first < vector_.size()) { - vector_[it.first] = it.second; - } else { - vector_.emplace_back(it.second); - } - } - maxIndex_ = vector_.size(); + ///@{ + /** + * Assignment operator. Assigns only the CURRENT value. + * @param other The new value to assign. + */ + inline SafeVector& operator=(const std::vector& other) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique>(this->value_); + markAsUsed(); this->value_ = other; return *this; } - - /// Rollback function. - void revert() const override { - tmp_ = nullptr; - clear_ = false; - maxIndex_ = vector_.size(); + inline SafeVector& operator=(const SafeVector& other) { + if (this->copy_ == nullptr) this->copy_ = std::make_unique>(this->value_); + markAsUsed(); this->value_ = other.get(); return *this; } + ///@} - /// Get the inner vector (for const functions!) - inline const std::vector& get() const { - return vector_; + ///@{ + /** + * Equality operator. Checks only the CURRENT value. + * @param other The value to check against. + */ + inline bool operator==(const std::vector& other) const { return (this->value_ == other); } + inline bool operator==(const SafeVector& other) const { return (this->value_ == other.get()); } + ///@} + + /// Commit the value. + void commit() override { this->copy_ = nullptr; this->undo_ = nullptr; this->registered_ = false; } + + /// Revert the value. + void revert() override { + if (this->copy_ != nullptr) this->value_ = *this->copy_; + if (this->undo_ != nullptr && !this->undo_->empty()) this->processUndoStack(); + this->copy_ = nullptr; this->undo_ = nullptr; this->registered_ = false; } }; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f36af5ef..d38a9438 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,35 +1,19 @@ -if(BUILD_AVALANCHEGO) - set(CORE_HEADERS - ${CMAKE_SOURCE_DIR}/src/core/blockchain.h - # ${CMAKE_SOURCE_DIR}/src/core/snowmanVM.h - ${CMAKE_SOURCE_DIR}/src/core/state.h - ${CMAKE_SOURCE_DIR}/src/core/storage.h - ${CMAKE_SOURCE_DIR}/src/core/rdpos.h - PARENT_SCOPE - ) +set(CORE_HEADERS + ${CMAKE_SOURCE_DIR}/src/core/blockchain.h + ${CMAKE_SOURCE_DIR}/src/core/consensus.h + ${CMAKE_SOURCE_DIR}/src/core/state.h + ${CMAKE_SOURCE_DIR}/src/core/dump.h + ${CMAKE_SOURCE_DIR}/src/core/storage.h + ${CMAKE_SOURCE_DIR}/src/core/rdpos.h + PARENT_SCOPE +) - set(CORE_SOURCES - ${CMAKE_SOURCE_DIR}/src/core/blockchain.cpp - # ${CMAKE_SOURCE_DIR}/src/core/snowmanVM.cpp - ${CMAKE_SOURCE_DIR}/src/core/state.cpp - ${CMAKE_SOURCE_DIR}/src/core/storage.cpp - ${CMAKE_SOURCE_DIR}/src/core/rdpos.cpp - PARENT_SCOPE - ) -else() - set(CORE_HEADERS - ${CMAKE_SOURCE_DIR}/src/core/blockchain.h - ${CMAKE_SOURCE_DIR}/src/core/state.h - ${CMAKE_SOURCE_DIR}/src/core/storage.h - ${CMAKE_SOURCE_DIR}/src/core/rdpos.h - PARENT_SCOPE - ) - - set(CORE_SOURCES - ${CMAKE_SOURCE_DIR}/src/core/blockchain.cpp - ${CMAKE_SOURCE_DIR}/src/core/state.cpp - ${CMAKE_SOURCE_DIR}/src/core/storage.cpp - ${CMAKE_SOURCE_DIR}/src/core/rdpos.cpp - PARENT_SCOPE - ) -endif() +set(CORE_SOURCES + ${CMAKE_SOURCE_DIR}/src/core/blockchain.cpp + ${CMAKE_SOURCE_DIR}/src/core/consensus.cpp + ${CMAKE_SOURCE_DIR}/src/core/state.cpp + ${CMAKE_SOURCE_DIR}/src/core/dump.cpp + ${CMAKE_SOURCE_DIR}/src/core/storage.cpp + ${CMAKE_SOURCE_DIR}/src/core/rdpos.cpp + PARENT_SCOPE +) \ No newline at end of file diff --git a/src/core/blockchain.cpp b/src/core/blockchain.cpp index 011453a9..aafd9029 100644 --- a/src/core/blockchain.cpp +++ b/src/core/blockchain.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,230 +8,147 @@ See the LICENSE.txt file in the project root for more information. #include "blockchain.h" Blockchain::Blockchain(const std::string& blockchainPath) : - options_(std::make_unique(Options::fromFile(blockchainPath))), - db_(std::make_unique(blockchainPath + "/database")), - storage_(std::make_unique(db_, options_)), - rdpos_(std::make_unique(db_, storage_, p2p_, options_, state_)), - state_(std::make_unique(db_, storage_, rdpos_, p2p_, options_)), - p2p_(std::make_unique(boost::asio::ip::address::from_string("127.0.0.1"), rdpos_, options_, storage_, state_)), - http_(std::make_unique(state_, storage_, p2p_, options_)), - syncer_(std::make_unique(*this)) + options_(Options::fromFile(blockchainPath)), + p2p_(options_.getP2PIp(), options_, storage_, state_), + db_(std::get<0>(DumpManager::getBestStateDBPath(options_))), + storage_(p2p_.getLogicalLocation(), options_), + state_(db_, storage_, p2p_, std::get<1>(DumpManager::getBestStateDBPath(options_)), options_), + http_(state_, storage_, p2p_, options_), + syncer_(p2p_, storage_, state_), + consensus_(state_, p2p_, storage_, options_) {} -void Blockchain::start() { p2p_->start(); http_->start(); syncer_->start(); } +void Blockchain::start() { + // Initialize necessary modules + LOGINFOP("Starting BDK Node..."); + this->p2p_.start(); + this->http_.start(); -void Blockchain::stop() { syncer_->stop(); http_->stop(); p2p_->stop(); } + // Connect to all seed nodes from the config and start the discoveryThread. + for (const auto& [ipAddress, port]: this->options_.getDiscoveryNodes()) { + this->p2p_.connectToServer(ipAddress, port); // TODO: optimize port (uint64_t implicit conversion to uint16_t) + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + this->p2p_.startDiscovery(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); -const std::atomic& Blockchain::isSynced() const { return this->syncer_->isSynced(); } + // Do initial sync + this->syncer_.sync(100, 20000000); // up to 100 blocks per request, 20MB limit, default connection timeout & retry count -void Syncer::updateCurrentlyConnectedNodes() { - // Get the list of currently connected nodes - std::vector connectedNodes = blockchain_.p2p_->getSessionsIDs(); - while (connectedNodes.size() < blockchain_.p2p_->minConnections() && !this->stopSyncer_) { - Logger::logToDebug(LogType::INFO, Log::syncer, __func__, - "Waiting for discoveryWorker to connect to more nodes, currently connected to: " - + std::to_string(connectedNodes.size()) - ); - // If we have less than the minimum number of connections, - // wait for a bit for discoveryWorker to kick in and connect to more nodes - std::this_thread::sleep_for(std::chrono::seconds(1)); - connectedNodes = blockchain_.p2p_->getSessionsIDs(); - } + // After Syncing, start the DumpWorker. + this->state_.dumpStartWorker(); - // Update information of already connected nodes - for (const auto& [nodeId, nodeInfo] : this->currentlyConnectedNodes_) { - // If node is not connected, remove it from the list - if (std::find(connectedNodes.begin(), connectedNodes.end(), nodeId) == connectedNodes.end()) { - this->currentlyConnectedNodes_.erase(nodeId); - continue; - } - // If node is connected, update its information - auto newNodeInfo = blockchain_.p2p_->requestNodeInfo(nodeId); - // If node is not responding, remove it from the list - if (newNodeInfo == P2P::NodeInfo()) { - this->currentlyConnectedNodes_.erase(nodeId); - continue; - } - // If node is responding, update its information - this->currentlyConnectedNodes_[nodeId] = newNodeInfo; - } + // if node is a Validator, start the consensus loop + this->consensus_.start(); +} - // Add new nodes to the list - for (const auto& nodeId : connectedNodes) { - if (!this->currentlyConnectedNodes_.contains(nodeId)) { - auto newNodeInfo = blockchain_.p2p_->requestNodeInfo(nodeId); - if (newNodeInfo != P2P::NodeInfo()) { - this->currentlyConnectedNodes_[nodeId] = newNodeInfo; - } - } - } +void Blockchain::stop() { + this->consensus_.stop(); + this->http_.stop(); + this->p2p_.stop(); } -bool Syncer::checkLatestBlock() { return (this->latestBlock_ != this->blockchain_.storage_->latest()); } +bool Syncer::sync(uint64_t blocksPerRequest, uint64_t bytesPerRequestLimit, int waitForPeersSecs, int tries) { + // NOTE: This is a synchronous operation that's (currently) run during note boot only, in the caller (main) thread. + // TODO: Detect out-of-sync after the intial synchronization on node boot and resynchronize. -void Syncer::doSync() { - // TODO: Fully implement Sync - this->latestBlock_ = blockchain_.storage_->latest(); - // Get the list of currently connected nodes and their current height - this->updateCurrentlyConnectedNodes(); - std::pair highestNode = {P2P::NodeID(), 0}; + // Make sure we are requesting at least one block per request. + if (blocksPerRequest == 0) blocksPerRequest = 1; - // Get the highest node. - for (auto& [nodeId, nodeInfo] : this->currentlyConnectedNodes_) { - if (nodeInfo.latestBlockHeight > highestNode.second) { - highestNode = {nodeId, nodeInfo.latestBlockHeight}; - } - } + // Synchronously get the first list of currently connected nodes and their current height + LOGINFOP("Syncing with other nodes in the network..."); + this->p2p_.getNodeConns().forceRefresh(); + std::pair highestNode = {P2P::NodeID(), 0}; - // Sync from the best node. - if (highestNode.second > this->latestBlock_->getNHeight()) { - // TODO: currently we are starting all the nodes from genesis (0) + // Loop downloading blocks until we are synchronized + while (true) { + if (!syncLoop(blocksPerRequest, bytesPerRequestLimit, waitForPeersSecs, tries, highestNode)) break; } - - this->latestBlock_ = blockchain_.storage_->latest(); this->synced_ = true; + LOGINFOP("Synced with the network; my latest block height: " + std::to_string(this->storage_.latest()->getNHeight())); + return true; } -void Syncer::doValidatorBlock() { - // TODO: Improve this somehow. - // Wait until we have enough transactions in the rdpos mempool. - while (this->blockchain_.rdpos_->getMempool().size() < rdPoS::minValidators * 2) { - if (this->stopSyncer_) return; - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - - // Wait until we have at least one transaction in the state mempool. - while (this->blockchain_.state_->getMempoolSize() < 1) { - if (this->stopSyncer_) return; - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - - // Create the block. - if (this->stopSyncer_) return; - auto mempool = this->blockchain_.rdpos_->getMempool(); - auto randomList = this->blockchain_.rdpos_->getRandomList(); - - // Order the transactions in the proper manner. - std::vector randomHashTxs; - std::vector randomnessTxs; - uint64_t i = 1; - while (randomHashTxs.size() != rdPoS::minValidators) { - for (const auto& [txHash, tx] : mempool) { - if (this->stopSyncer_) return; - if (tx.getFrom() == randomList[i] && tx.getFunctor() == Hex::toBytes("0xcfffe746")) { - randomHashTxs.emplace_back(tx); - i++; - break; - } +bool Syncer::syncLoop( + const uint64_t& blocksPerRequest, const uint64_t& bytesPerRequestLimit, + int& waitForPeersSecs, int& tries, + std::pair& highestNode +) { + // P2P is running, so we are getting updated NodeInfos via NodeConns. + // Get the node with the highest block height available for download. + auto connected = this->p2p_.getNodeConns().getConnected(); + if (connected.empty()) { + // No one to download blocks from. + // While we don't exhaust the waiting-for-a-connection timeout, sleep and try again later. + if (waitForPeersSecs-- > 0) { + LOGINFOP("Syncer waiting for peer connections (" + std::to_string(waitForPeersSecs) + "s left) ..."); + std::this_thread::sleep_for(std::chrono::seconds(1)); + return true; } + // We have timed out waiting for peers, so synchronization is complete. + LOGINFOP("Syncer quitting due to no peer connections."); + return false; } - i = 1; - while (randomnessTxs.size() != rdPoS::minValidators) { - for (const auto& [txHash, tx] : mempool) { - if (tx.getFrom() == randomList[i] && tx.getFunctor() == Hex::toBytes("0x6fc5a2d6")) { - randomnessTxs.emplace_back(tx); - i++; - break; - } - } - } - if (this->stopSyncer_) return; - - // Create the block and append to all chains, we can use any storage for latest block. - const std::shared_ptr latestBlock = this->blockchain_.storage_->latest(); - Block block(latestBlock->hash(), latestBlock->getTimestamp(), latestBlock->getNHeight() + 1); - - // Append transactions towards block. - for (const auto& tx: randomHashTxs) block.appendTxValidator(tx); - for (const auto& tx: randomnessTxs) block.appendTxValidator(tx); - if (this->stopSyncer_) return; - - // Add transactions from state, sign, validate and process the block. - this->blockchain_.state_->fillBlockWithTransactions(block); - this->blockchain_.rdpos_->signBlock(block); - if (!this->blockchain_.state_->validateNextBlock(block)) { - Logger::logToDebug(LogType::ERROR, Log::syncer, __func__, "Block is not valid!"); - throw std::runtime_error("Block is not valid!"); - } - if (this->stopSyncer_) return; - Hash latestBlockHash = block.hash(); - this->blockchain_.state_->processNextBlock(std::move(block)); - if (this->blockchain_.storage_->latest()->hash() != latestBlockHash) { - Logger::logToDebug(LogType::ERROR, Log::syncer, __func__, "Block is not valid!"); - throw std::runtime_error("Block is not valid!"); + for (auto& [nodeId, nodeInfo] : connected) { + if (nodeInfo.latestBlockHeight() > highestNode.second) highestNode = {nodeId, nodeInfo.latestBlockHeight()}; } + LOGINFOP("Latest known block height is " + std::to_string(highestNode.second)); - // Broadcast the block through P2P - if (this->stopSyncer_) return; - this->blockchain_.p2p_->broadcastBlock(this->blockchain_.storage_->latest()); -} + auto currentNHeight = this->storage_.latest()->getNHeight(); -void Syncer::doValidatorTx() const { - // There is nothing to do, validatorLoop will wait for the next block. -} + // If synced, quit sync loop. + if (highestNode.second <= currentNHeight) return false; -void Syncer::validatorLoop() { - Logger::logToDebug(LogType::INFO, Log::syncer, __func__, "Starting validator loop."); - Validator me(Secp256k1::toAddress(Secp256k1::toUPub(this->blockchain_.options_->getValidatorPrivKey()))); - this->blockchain_.rdpos_->startrdPoSWorker(); - while (!this->stopSyncer_) { - this->latestBlock_ = this->blockchain_.storage_->latest(); - // Check if validator is within the current validator list. - const auto currentRandomList = this->blockchain_.rdpos_->getRandomList(); - bool isBlockCreator = false; - if (currentRandomList[0] == me) { - isBlockCreator = true; - this->doValidatorBlock(); - } + auto downloadNHeight = currentNHeight + 1; + auto downloadNHeightEnd = downloadNHeight + blocksPerRequest - 1; - if (this->stopSyncer_) return; - if (!isBlockCreator) this->doValidatorTx(); + // NOTE: Possible optimizatons: + // - Parallel download of different blocks or block ranges from multiple nodes + // - Retry slow/failed downloads + // - Deprioritize download from slow/failed nodes - while (!this->checkLatestBlock() && !this->stopSyncer_) { - // Wait for next block to be created. - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - } -} - -void Syncer::nonValidatorLoop() const { - // TODO: Improve tx broadcasting and syncing - while (!this->stopSyncer_) std::this_thread::sleep_for(std::chrono::milliseconds(10)); -} + // Currently, fetch the next batch of block froms a node that is the best node (has the highest block height) + LOGINFOP("Requesting blocks [" + std::to_string(downloadNHeight) + "," + + std::to_string(downloadNHeightEnd) + "] (" + std::to_string(bytesPerRequestLimit) + + " bytes limit) from " + toString(highestNode.first) + ); -bool Syncer::syncerLoop() { - Utils::safePrint("Starting OrbiterSDK Node..."); - Logger::logToDebug(LogType::INFO, Log::syncer, __func__, "Starting syncer loop."); - // Connect to all seed nodes from the config and start the discoveryThread. - auto discoveryNodeList = this->blockchain_.options_->getDiscoveryNodes(); - for (const auto &[ipAddress, port]: discoveryNodeList) { - this->blockchain_.p2p_->connectToServer(ipAddress, port); - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - this->blockchain_.p2p_->startDiscovery(); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + // Request the next block we need from the chosen peer + std::vector result = this->p2p_.requestBlock( + highestNode.first, downloadNHeight, downloadNHeightEnd, bytesPerRequestLimit + ); - // Sync the node with the network. - this->doSync(); - if (this->stopSyncer_) return false; - Utils::safePrint("Synced with the network, starting the node."); - if (this->blockchain_.options_->getIsValidator()) { - this->validatorLoop(); - } else { - this->nonValidatorLoop(); + // If the request failed, retry it (unless we set a finite number of tries and we've just run out of them) + if (result.empty()) { + bool shouldRetry = (tries > 0); + if (shouldRetry) { + tries--; LOGWARNINGP("Blocks request failed (" + std::to_string(tries) + " tries left)"); + } + if (shouldRetry && tries == 0) return false; + LOGWARNINGP("Blocks request failed, restarting sync"); + return true; } - return true; -} -void Syncer::start() { - if (!this->syncerLoopFuture_.valid()) { - this->syncerLoopFuture_ = std::async(std::launch::async, &Syncer::syncerLoop, this); + // Validate and connect the blocks + try { + for (auto& block : result) { + // Blocks in the response must be all a contiguous range + if (block.getNHeight() != downloadNHeight) throw DynamicException( + "Peer sent block with wrong height " + std::to_string(block.getNHeight()) + " instead of " + std::to_string(downloadNHeight) + ); + // This call validates the block first (throws exception if the block invalid). + // Note that the "result" vector's element data is being consumed (moved) by this call. + this->state_.processNextBlock(std::move(block)); + LOGINFOP("Processed block " + std::to_string(downloadNHeight) + " from " + toString(highestNode.first)); + downloadNHeight++; + } + } catch (std::exception &e) { + LOGERROR("Invalid RequestBlock Answer from " + + toString(highestNode.first) + " , error: " + e.what() + " closing session." + ); + this->p2p_.disconnectSession(highestNode.first); } -} -void Syncer::stop() { - this->stopSyncer_ = true; - this->blockchain_.rdpos_->stoprdPoSWorker(); // Stop the rdPoS worker. - if (this->syncerLoopFuture_.valid()) this->syncerLoopFuture_.wait(); + return true; } diff --git a/src/core/blockchain.h b/src/core/blockchain.h index cf23d960..559cf7a3 100644 --- a/src/core/blockchain.h +++ b/src/core/blockchain.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,172 +8,108 @@ See the LICENSE.txt file in the project root for more information. #ifndef BLOCKCHAIN_H #define BLOCKCHAIN_H -#include "storage.h" -#include "rdpos.h" -#include "state.h" -#include "../net/p2p/managerbase.h" #include "../net/http/httpserver.h" -#include "../utils/options.h" -#include "../utils/db.h" -// Forward declarations. -class Syncer; +#include "consensus.h" // state.h -> rdpos.h -> (utils/tx.h -> ecdsa.h -> utils.h -> logger.h), (dump.h -> utils/db.h, storage.h -> options.h) /** - * Master class that represents the blockchain as a whole. - * Contains, and acts as the middleman of, every other part of the core and net protocols. - * Those parts interact with one another by communicating through this class. + * Helper class that syncs the node with other nodes in the network. + * Currently it's *single threaded*, meaning that it doesn't require mutexes. */ -class Blockchain { +class Syncer : public Log::LogicalLocationProvider { private: - const std::unique_ptr options_; ///< Pointer to the options singleton. - const std::unique_ptr db_; ///< Pointer to the database. - const std::unique_ptr storage_; ///< Pointer to the blockchain storage. - const std::unique_ptr state_; ///< Pointer to the blockchain state. - const std::unique_ptr rdpos_; ///< Pointer to the rdPoS object (consensus). - const std::unique_ptr p2p_; ///< Pointer to the P2P connection manager. - const std::unique_ptr http_; ///< Pointer to the HTTP server. - const std::unique_ptr syncer_; ///< Pointer to the blockchain syncer. + P2P::ManagerNormal& p2p_; ///< Reference to the P2P networking engine. + const Storage& storage_; ///< Reference to the blockchain storage. + State& state_; ///< reference to the blockchain state. + std::atomic synced_ = false; ///< Indicates whether or not the syncer is synced. public: /** * Constructor. - * @param blockchainPath Root path of the blockchain. + * @param p2p Reference to the P2P::ManagerNormal object. + * @param storage Reference to the blockchain storage object. + * @param state Reference to the blockchain state object. */ - explicit Blockchain(const std::string& blockchainPath); + explicit Syncer(P2P::ManagerNormal& p2p, const Storage& storage, State& state) : + p2p_(p2p), storage_(storage), state_(state) {} - /// Default destructor. - ~Blockchain() = default; + std::string getLogicalLocation() const override { return p2p_.getLogicalLocation(); } ///< Log instance from P2P /** - * Start the blockchain. - * Initializes P2P, HTTP and Syncer, in this order. + * Synchronize this node to the latest known blocks among all connected peers at the time this method is called. + * @param blocksPerRequest How many blocks (at most) you want to obtain on each requestto the remote node. + * @param bytesPerRequestLimit Maximum byte size of each block download response (will download 1 block minimum). + * @param waitForPeersSecs Seconds to wait for at least one connection to be established (cumulative). + * @param tries If zero, try forever, otherwise try block downloads a set number of times. + * @return `true` if successfully synced, `false` otherwise. */ - void start(); + bool sync( + uint64_t blocksPerRequest, uint64_t bytesPerRequestLimit, + int waitForPeersSecs = 15, int tries = 0 + ); /** - * Stop/shutdown the blockchain. - * Stops Syncer, HTTP and P2P, in this order (reverse order of start()). + * Helper function that does the sync loop. Used exclusively by sync(). + * @param blocksPerRequest How many blocks (at most) you want to obtain on each requestto the remote node. + * @param bytesPerRequestLimit Maximum byte size of each block download response (will download 1 block minimum). + * @param waitForPeersSecs Seconds to wait for at least one connection to be established (cumulative). + * @param tries If zero, try forever, otherwise try block downloads a set number of times. + * @param highestNode The highest known node to use as the target height to end the sync process. + * @return `true` when loop is successful, `false` when loop is aborted. */ - void stop(); - - /// Getter for `options_`. - const std::unique_ptr& getOptions() const { return this->options_; }; - - /// Getter for `db_`. - const std::unique_ptr& getDB() const { return this->db_; }; - - /// Getter for `storage_`. - const std::unique_ptr& getStorage() const { return this->storage_; }; - - /// Getter for `rdpos_`. - const std::unique_ptr& getrdPoS() const { return this->rdpos_; }; - - /// Getter for `state_`. - const std::unique_ptr& getState() const { return this->state_; }; - - /// Getter for `p2p_`. - const std::unique_ptr& getP2P() const { return this->p2p_; }; - - /// Getter for `http_`. - const std::unique_ptr& getHTTP() const { return this->http_; }; - - /// Getter for `syncer_`. - const std::unique_ptr& getSyncer() const { return this->syncer_; }; - - /** - * Check if the blockchain syncer is synced. - * @return `true` if the syncer is synced, `false` otherwise. - */ - const std::atomic& isSynced() const; - - friend class Syncer; + bool syncLoop( + const uint64_t& blocksPerRequest, const uint64_t& bytesPerRequestLimit, + int& waitForPeersSecs, int& tries, + std::pair& highestNode + ); + + ///@{ + /** Getter. */ + const std::atomic& isSynced() const { return this->synced_; } + ///@} }; /** - * Helper class that syncs the node with the network. - * This is where the magic happens between the nodes on the network, as the - * class is responsible for syncing both, broadcasting transactions and also - * creating new blocks if the node is a Validator. - * Currently it's *single threaded*, meaning that it doesn't require mutexes. - * TODO: This could also be responsible for slashing rdPoS if they are not behaving correctly - * TODO: Maybe it is better to move rdPoSWorker to Syncer? + * Master class that represents the blockchain as a whole. + * Contains, and acts as the middleman of, every other part of the core and net protocols. + * Those parts interact with one another by communicating through this class. */ -class Syncer { +class Blockchain : public Log::LogicalLocationProvider { private: - /// Reference to the parent blockchain. - Blockchain& blockchain_; - - /// List of currently connected nodes and their info. - std::unordered_map currentlyConnectedNodes_; - - /// Pointer to the blockchain's latest block. - std::shared_ptr latestBlock_; - - /// Update `currentlyConnectedNodes`. - void updateCurrentlyConnectedNodes(); - - /// Check latest block (used by validatorLoop()). - bool checkLatestBlock(); - - /// Do the syncing. - void doSync(); - - /** - * Create and broadcast a Validator block (called by validatorLoop()). - * If the node is a Validator and it has to create a new block, - * this function will be called, the new block will be created based on the - * current State and rdPoS objects, and then it will be broadcasted. - * @throw std::runtime_error if block is invalid. - */ - void doValidatorBlock(); - - /** - * Wait for a new block (called by validatorLoop()). - * If the node is a Validator, this function will be called to make the - * node wait until it receives a new block. - */ - void doValidatorTx() const; - - /// Routine loop for when the node is a Validator. - void validatorLoop(); - - /// Routine loop for when the node is NOT a Validator. - void nonValidatorLoop() const; - - /// Routine loop for the syncer worker. - bool syncerLoop(); - - /// Future object holding the thread for the syncer loop. - std::future syncerLoopFuture_; - - /// Flag for stopping the syncer. - std::atomic stopSyncer_ = false; - - /// Indicates whether or not the syncer is synced. - std::atomic synced_ = false; + Options options_; ///< Options singleton. + P2P::ManagerNormal p2p_; ///< P2P connection manager. NOTE: must be initialized first due to getLogicalLocation() + const DB db_; ///< Database. + Storage storage_; ///< Blockchain storage. + State state_; ///< Blockchain state. + HTTPServer http_; ///< HTTP server. + Syncer syncer_; ///< Blockchain syncer. + Consensus consensus_; ///< Block and transaction processing. public: /** * Constructor. - * @param blockchain Reference to the parent blockchain. - */ - explicit Syncer(Blockchain& blockchain) : blockchain_(blockchain) {}; - - /** - * Destructor. - * Automatically stops the syncer. + * @param blockchainPath Root path of the blockchain. */ - ~Syncer() { this->stop(); }; - - /// Getter for `synced`. - const std::atomic& isSynced() const { return this->synced_; } - - /// Start the syncer routine loop. - void start(); - - /// Stop the syncer routine loop. - void stop(); + explicit Blockchain(const std::string& blockchainPath); + ~Blockchain() = default; ///< Default destructor. + std::string getLogicalLocation() const override { return p2p_.getLogicalLocation(); } ///< Log instance from P2P + void start(); ///< Start the blockchain. Initializes P2P, HTTP and Syncer, in this order. + void stop(); ///< Stop/shutdown the blockchain. Stops Syncer, HTTP and P2P, in this order (reverse order of start()). + + ///@{ + /** Getter. */ + Options& getOptions() { return this->options_; } + const DB& getDB() const { return this->db_; } + Storage& getStorage() { return this->storage_; } + State& getState() { return this->state_; } + P2P::ManagerNormal& getP2P() { return this->p2p_; } + HTTPServer& getHTTP() { return this->http_; } + Syncer& getSyncer() { return this->syncer_; } + Consensus& getConsensus() { return this->consensus_; } + ///@} + + /// Check if the blockchain is synced. + const std::atomic& isSynced() const { return this->syncer_.isSynced(); } }; #endif // BLOCKCHAIN_H diff --git a/src/core/consensus.cpp b/src/core/consensus.cpp new file mode 100644 index 00000000..4ea74cf8 --- /dev/null +++ b/src/core/consensus.cpp @@ -0,0 +1,529 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "consensus.h" +#include "blockchain.h" +#include "bytes/random.h" + +void Consensus::validatorLoop() { + LOGINFO("Starting validator loop."); + uint64_t loop = this->storage_.latest()->getNHeight() + 1; + Validator me(Secp256k1::toAddress(Secp256k1::toUPub(this->options_.getValidatorPrivKey()))); + std::unordered_map validators; + validators.insert({me, this->options_.getValidatorPrivKey()}); + auto extraValidators = this->options_.getExtraValidators(); + if (extraValidators.empty()) { + // If there are no extra validators, we are done. + LOGINFO("No extra validators found."); + return; + } + for (const auto& validator : this->options_.getExtraValidators()) { + validators.insert({Secp256k1::toAddress(Secp256k1::toUPub(validator)), validator}); + } + while (!this->stopConsensus_) { + Utils::safePrint("Validator: " + me.hex(true).get() + " Loop: " + std::to_string(loop++)); + auto latestBlock = this->storage_.latest(); + auto latestBlockHeight = latestBlock->getNHeight(); + + // Check if validator is within the current validator list. + Utils::safePrint("Getting the current validators list."); + const auto currentRandomList = this->state_.rdposGetRandomList(); + // Check which ones of the validators is the block creator (currentRandomList[0]) + PrivKey blockCreatorPrivKey; + Utils::safePrint("Getting the block creator for block height: " + std::to_string(latestBlockHeight+1)); + for (const auto& [validator, privKey] : validators) { + if (validator == currentRandomList[0]) { + blockCreatorPrivKey = privKey; + break; + } + } + if (!blockCreatorPrivKey) { + Utils::safePrint("Validator not in the current validator list."); + throw DynamicException("Validator not in the current validator list."); + } + // Now, make the list of the validators that are going to be signing the block, based on the size of validators. + auto validatorSetSize = this->options_.getMinValidators(); + // Make sure that the random list is at least +1 the validatorSetSize + if (currentRandomList.size() <= validatorSetSize) { + Utils::safePrint("Random list size is less than the minimum validators."); + throw DynamicException("Random list size is less than the minimum validators."); + } + + std::vector validatorsToSign; + for (uint64_t i = 0; i < validatorSetSize; i++) { + for (const auto& [validator, privKey] : validators) { + if (validator == currentRandomList[i+1]) { + validatorsToSign.push_back(privKey); + break; + } + } + } + if (validatorsToSign.size() != validatorSetSize) { + Utils::safePrint("Validator set size mismatch."); + throw DynamicException("Validator set size mismatch."); + } + + // Now, create the validator transactions + // Validator transactions must be within the validatorToSign order. + Utils::safePrint("Creating validator transactions."); + std::vector hashTxs; + std::vector seedTxs; + for (uint64_t i = 0; i < validatorsToSign.size(); ++i) { + Hash randomness = bytes::random(); + Hash randomHash = Utils::sha3(randomness); + Bytes randomHashBytes = Hex::toBytes("0xcfffe746"); + randomHashBytes.insert(randomHashBytes.end(), randomHash.begin(), randomHash.end()); + hashTxs.emplace_back( + Secp256k1::toAddress(Secp256k1::toUPub(validatorsToSign[i])), + randomHashBytes, + this->options_.getChainID(), + latestBlockHeight + 1, + validatorsToSign[i] + ); + Bytes seedBytes = Hex::toBytes("0x6fc5a2d6"); + seedBytes.insert(seedBytes.end(), randomness.begin(), randomness.end()); + seedTxs.emplace_back( + Secp256k1::toAddress(Secp256k1::toUPub(validatorsToSign[i])), + seedBytes, + this->options_.getChainID(), + latestBlockHeight + 1, + validatorsToSign[i] + ); + } + Utils::safePrint("Validator transactions created."); + // Now, move the trasactions to the final vector (firstly the hashTxs, then the seedTxs) + std::vector validatorTxs; + for (const auto& tx: hashTxs) validatorTxs.emplace_back(tx); + for (const auto& tx: seedTxs) validatorTxs.emplace_back(tx); + if (validatorTxs.size() != validatorSetSize * 2) { + Utils::safePrint("Validator transaction size mismatch, wanted: " + std::to_string(validatorSetSize * 2) + " got: " + std::to_string(validatorTxs.size())); + throw DynamicException("Validator transaction size mismatch."); + } + // Check the ordering, validatorTxs[i] getFrom must be equal to currentRandomList[i] + // Also, validatorTxs[i+validatorSetSize] getFrom must be equal to currentRandomList[i] + for (uint64_t i = 0; i < validatorSetSize; i++) { + if (validatorTxs[i].getFrom() != currentRandomList[i+1]) { + Utils::safePrint("Validator transaction ordering mismatch."); + throw DynamicException("Validator transaction ordering mismatch."); + } + if (validatorTxs[i + validatorSetSize].getFrom() != currentRandomList[i+1]) { + Utils::safePrint("Validator transaction ordering mismatch."); + throw DynamicException("Validator transaction ordering mismatch."); + } + } + + auto waitForTxs = std::chrono::high_resolution_clock::now(); + bool logged = false; + while (this->state_.getMempoolSize() < 1) { + if (!logged) { + logged = true; + Utils::safePrint("Waiting for at least one transaction in the mempool."); + } + if (this->stopConsensus_) return; + std::this_thread::sleep_for(std::chrono::microseconds(10)); + } + + // Finally, create the block + Utils::safePrint("Creating block."); + + // Get a copy of the mempool and current timestamp + auto chainTxs = this->state_.getMempool(); + // We need to filter chainTxs to only allow one transaction per address + // Otherwise we will have a nonce mismatch + std ::unordered_map chainTxsMap; + for (const auto& tx : chainTxs) { + // We need to actually check which tx has the greatest fee spent + auto txIt = chainTxsMap.find(tx.getFrom()); + if (txIt == chainTxsMap.end()) { + chainTxsMap.insert({tx.getFrom(), tx}); + } else { + if (tx.getMaxFeePerGas() > txIt->second.getMaxFeePerGas()) { + chainTxsMap.insert_or_assign(tx.getFrom(), tx); + } + } + } + chainTxs.clear(); + // Now copy the map back to the vector + for (const auto& [addr, tx] : chainTxsMap) { + chainTxs.emplace_back(tx); + } + + uint64_t timestamp = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + + // To create a valid block according to block validation rules, the + // timestamp provided to the new block must be equal or greater (>=) + // than the timestamp of the previous block. + timestamp = std::max(timestamp, latestBlock->getTimestamp()); + + Utils::safePrint("Create a new valid block."); + auto block = FinalizedBlock::createNewValidBlock( + std::move(chainTxs), std::move(validatorTxs), latestBlock->getHash(), + timestamp, latestBlock->getNHeight() + 1, blockCreatorPrivKey + ); + Utils::safePrint("Block created, validating."); + Hash latestBlockHash = block.getHash(); + if ( + BlockValidationStatus bvs = state_.tryProcessNextBlock(std::move(block)); + bvs != BlockValidationStatus::valid + ) { + Utils::safePrint("Block is not valid!"); + throw DynamicException("Block is not valid!"); + } + if (this->stopConsensus_) return; + if (this->storage_.latest()->getHash() != latestBlockHash) { + Utils::safePrint("Block is not valid!"); + throw DynamicException("Block is not valid!"); + } + + // Broadcast the block through P2P + Utils::safePrint("Broadcasting block."); + if (this->stopConsensus_) return; + this->p2p_.getBroadcaster().broadcastBlock(this->storage_.latest()); + + + // Keep looping while we don't reach the latest block + logged = false; + while (latestBlockHeight == this->storage_.latest()->getNHeight() && !this->stopConsensus_) { + if (!logged) { + Utils::safePrint("Waiting for next block to be created."); + Utils::safePrint("Validator: " + me.hex(true).get() + " Waiting for next block to be created."); + logged = true; + } + // Wait for next block to be created. + std::this_thread::sleep_for(std::chrono::microseconds(10)); + } + } +} + +void Consensus::pullerLoop() { + // List of all current existing requests towards other nodes. A list for TxBlock and a list for TxValidator. + std::unordered_map>, SafeHash> txBlockRequests; + while (!this->stopPuller_) { + std::this_thread::sleep_for(std::chrono::milliseconds(25)); + // First, lets get a list of all nodes we are connected to. + auto nodes = this->p2p_.getSessionsIDs(P2P::NodeType::NORMAL_NODE); + // Now, we remove from the requests map all nodes that are not connected anymore. + for (auto it = txBlockRequests.begin(); it != txBlockRequests.end();) { + if (std::find(nodes.begin(), nodes.end(), it->first) == nodes.end()) { + it = txBlockRequests.erase(it); + } else { + ++it; + } + } + + // Check if the latest block is older than 100ms. If yes, attempt to sync blocks. + { + // Get the timestamp of the latest known block + auto latestBlock = this->storage_.latest(); + auto lastBlockTimestamp = latestBlock->getTimestamp(); + // Get current system time + auto now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + // Compute the age of the block in milliseconds + auto blockAge = now - lastBlockTimestamp; + if (blockAge > 100000) { + // We are considered "behind" and need to sync blocks. + // Get connected nodes with their info + auto connected = this->p2p_.getNodeConns().getConnected(); + if (!connected.empty()) { + // Find the node with the highest block height + std::pair highestNode = {P2P::NodeID{}, 0}; + for (auto & [nodeId, nodeInfo] : connected) { + if (nodeInfo.latestBlockHeight() > highestNode.second) { + highestNode = {nodeId, nodeInfo.latestBlockHeight()}; + } + } + auto currentNHeight = latestBlock->getNHeight(); + // If we are not synced + if (highestNode.second > currentNHeight) { + Utils::safePrint("Block is older than 100ms, attempting to sync blocks."); + Utils::safePrint("Highest node with height: " + std::to_string(highestNode.second)); + // For example: + uint64_t blocksPerRequest = 100; + uint64_t bytesPerRequestLimit = 1'000'000; + auto downloadNHeight = currentNHeight + 1; + auto downloadNHeightEnd = downloadNHeight + blocksPerRequest - 1; + + // Request the next range of blocks from the best node + Utils::safePrint("Requesting blocks [" + std::to_string(downloadNHeight) + "," + std::to_string(downloadNHeightEnd) + "]"); + auto result = this->p2p_.requestBlock( + highestNode.first, downloadNHeight, downloadNHeightEnd, bytesPerRequestLimit + ); + Utils::safePrint("Received " + std::to_string(result.size())); + + // If we got blocks, process them + if (!result.empty()) { + try { + for (auto & block : result) { + this->state_.tryProcessNextBlock(std::move(block)); + ++downloadNHeight; + } + } catch (std::exception &e) { + // We actually don't do anything here, because broadcast might have received a block + } + } + } + } else { + // Not connected to any node? just wait for the next loop... + continue; + } + } + } + + // Now, we request transactions from all nodes that we are connected to AND we don't have a request for. + for (const auto& nodeId : nodes) { + if (txBlockRequests.find(nodeId) == txBlockRequests.end()) { + txBlockRequests[nodeId] = std::async(std::launch::async, &P2P::ManagerNormal::requestTxs, &this->p2p_, nodeId); + } + } + // Loop through all the current requests **without blocking**. + for (auto it = txBlockRequests.begin(); it != txBlockRequests.end();) { + auto& future = it->second; + if (future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + auto txList = future.get(); + for (const auto& tx : txList) { + TxBlock txBlock(tx); + this->state_.addTx(std::move(txBlock)); + } + // Remove the request from the list. + it = txBlockRequests.erase(it); + } else { + ++it; + } + } + } + // Make sure to exit all the futures otherwise we will have orphaned threads! + for (auto& [nodeId, future] : txBlockRequests) { + future.wait(); + future.get(); + } +} + + +void Consensus::doValidatorBlock() { + // TODO: Improve this somehow. + // Wait until we are ready to create the block + auto start = std::chrono::high_resolution_clock::now(); + LOGDEBUG("Block creator: waiting for txs"); + uint64_t validatorMempoolSize = 0; + std::unique_ptr lastLog = nullptr; + while (validatorMempoolSize != this->state_.rdposGetMinValidators() * 2 && !this->stopConsensus_) { + if (lastLog == nullptr || *lastLog != validatorMempoolSize) { + lastLog = std::make_unique(validatorMempoolSize); + LOGDEBUG("Block creator has: " + std::to_string(validatorMempoolSize) + " transactions in mempool"); + } + validatorMempoolSize = this->state_.rdposGetMempoolSize(); + std::this_thread::sleep_for(std::chrono::microseconds(10)); + } + LOGDEBUG("Validator ready to create a block"); + + // Wait until we have all required transactions to create the block. + auto waitForTxs = std::chrono::high_resolution_clock::now(); + bool logged = false; + while (this->state_.getMempoolSize() < 1) { + if (!logged) { + logged = true; + LOGDEBUG("Waiting for at least one transaction in the mempool."); + } + if (this->stopConsensus_) return; + std::this_thread::sleep_for(std::chrono::microseconds(10)); + } + + auto creatingBlock = std::chrono::high_resolution_clock::now(); + + // Create the block. + LOGDEBUG("Ordering transactions and creating block"); + if (this->stopConsensus_) return; + auto mempool = this->state_.rdposGetMempool(); + const auto randomList = this->state_.rdposGetRandomList(); + + // Order the transactions in the proper manner. + std::vector randomHashTxs; + std::vector randomnessTxs; + uint64_t i = 1; + while (randomHashTxs.size() != this->state_.rdposGetMinValidators()) { + for (const auto& [txHash, tx] : mempool) { + if (this->stopConsensus_) return; + // 0xcfffe746 == 3489654598 + if (tx.getFrom() == randomList[i] && tx.getFunctor().value == 3489654598) { + randomHashTxs.emplace_back(tx); + i++; + break; + } + } + } + i = 1; + while (randomnessTxs.size() != this->state_.rdposGetMinValidators()) { + for (const auto& [txHash, tx] : mempool) { + // 0x6fc5a2d6 == 1875223254 + if (tx.getFrom() == randomList[i] && tx.getFunctor().value == 1875223254) { + randomnessTxs.emplace_back(tx); + i++; + break; + } + } + } + if (this->stopConsensus_) return; + + // Create the block and append to all chains, we can use any storage for latest block. + const std::shared_ptr latestBlock = this->storage_.latest(); + + // Append all validator transactions to a single vector (Will be moved to the new block) + std::vector validatorTxs; + for (const auto& tx: randomHashTxs) validatorTxs.emplace_back(tx); + for (const auto& tx: randomnessTxs) validatorTxs.emplace_back(tx); + if (this->stopConsensus_) return; + + // Get a copy of the mempool and current timestamp + auto chainTxs = this->state_.getMempool(); + // We need to filter chainTxs to only allow one transaction per address + // Otherwise we will have a nonce mismatch + std::unordered_map chainTxsMap; + for (const auto& tx : chainTxs) { + // We need to actually check which tx has the greatest fee spent + auto txIt = chainTxsMap.find(tx.getFrom()); + if (txIt == chainTxsMap.end()) { + chainTxsMap.insert({tx.getFrom(), tx}); + } else { + if (tx.getMaxFeePerGas() > txIt->second.getMaxFeePerGas()) { + chainTxsMap.insert_or_assign(tx.getFrom(), tx); + } + } + } + chainTxs.clear(); + // Now copy the map back to the vector + for (const auto& [addr, tx] : chainTxsMap) { + chainTxs.emplace_back(tx); + } + + uint64_t timestamp = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + + // To create a valid block according to block validation rules, the + // timestamp provided to the new block must be equal or greater (>=) + // than the timestamp of the previous block. + timestamp = std::max(timestamp, latestBlock->getTimestamp()); + + LOGDEBUG("Create a new valid block."); + auto block = FinalizedBlock::createNewValidBlock( + std::move(chainTxs), std::move(validatorTxs), latestBlock->getHash(), + timestamp, latestBlock->getNHeight() + 1, this->options_.getValidatorPrivKey() + ); + LOGDEBUG("Block created, validating."); + Hash latestBlockHash = block.getHash(); + if ( + BlockValidationStatus bvs = state_.tryProcessNextBlock(std::move(block)); + bvs != BlockValidationStatus::valid + ) { + LOGERROR("Block is not valid!"); + throw DynamicException("Block is not valid!"); + } + if (this->stopConsensus_) return; + if (this->storage_.latest()->getHash() != latestBlockHash) { + LOGERROR("Block is not valid!"); + throw DynamicException("Block is not valid!"); + } + + // Broadcast the block through P2P + LOGDEBUG("Broadcasting block."); + if (this->stopConsensus_) return; + this->p2p_.getBroadcaster().broadcastBlock(this->storage_.latest()); + auto end = std::chrono::high_resolution_clock::now(); + long double duration = std::chrono::duration_cast(end - start).count(); + long double timeToConsensus = std::chrono::duration_cast(waitForTxs - start).count(); + long double timeToTxs = std::chrono::duration_cast(creatingBlock - waitForTxs).count(); + long double timeToBlock = std::chrono::duration_cast(end - creatingBlock).count(); + LOGDEBUG("Block created in: " + std::to_string(duration) + "ms, " + + "Time to consensus: " + std::to_string(timeToConsensus) + "ms, " + + "Time to txs: " + std::to_string(timeToTxs) + "ms, " + + "Time to block: " + std::to_string(timeToBlock) + "ms" + ); +} + +void Consensus::doValidatorTx(const uint64_t& nHeight, const Validator& me) { + Utils::safePrint("Validator: " + me.hex(true).get() + " doValidatorTx nHeight: " + std::to_string(nHeight)); + Hash randomness = bytes::random(); + Hash randomHash = Utils::sha3(randomness); + LOGDEBUG("Creating random Hash transaction"); + Bytes randomHashBytes = Hex::toBytes("0xcfffe746"); + randomHashBytes.insert(randomHashBytes.end(), randomHash.begin(), randomHash.end()); + TxValidator randomHashTx( + me.address(), + randomHashBytes, + this->options_.getChainID(), + nHeight, + this->options_.getValidatorPrivKey() + ); + + Bytes seedBytes = Hex::toBytes("0x6fc5a2d6"); + seedBytes.insert(seedBytes.end(), randomness.begin(), randomness.end()); + TxValidator seedTx( + me.address(), + seedBytes, + this->options_.getChainID(), + nHeight, + this->options_.getValidatorPrivKey() + ); + + // Sanity check if tx is valid + View randomHashTxView(randomHashTx.getData()); + View randomSeedTxView(seedTx.getData()); + if (Utils::sha3(randomSeedTxView.subspan(4)) != Hash(randomHashTxView.subspan(4))) { + LOGDEBUG("RandomHash transaction is not valid!!!"); + return; + } + + // Append to mempool and broadcast the transaction across all nodes. + LOGDEBUG("Broadcasting randomHash transaction"); + this->state_.rdposAddValidatorTx(randomHashTx); + this->p2p_.getBroadcaster().broadcastTxValidator(randomHashTx); + + // Wait until we received all randomHash transactions to broadcast the randomness transaction + LOGDEBUG("Waiting for randomHash transactions to be broadcasted"); + uint64_t validatorMempoolSize = 0; + std::unique_ptr lastLog = nullptr; + while (validatorMempoolSize < this->state_.rdposGetMinValidators() && !this->stopConsensus_) { + if (lastLog == nullptr || *lastLog != validatorMempoolSize) { + lastLog = std::make_unique(validatorMempoolSize); + LOGDEBUG("Validator has: " + std::to_string(validatorMempoolSize) + " transactions in mempool"); + } + validatorMempoolSize = this->state_.rdposGetMempoolSize(); + std::this_thread::sleep_for(std::chrono::microseconds(10)); + } + + LOGDEBUG("Broadcasting random transaction"); + // Append and broadcast the randomness transaction. + this->state_.addValidatorTx(seedTx); + this->p2p_.getBroadcaster().broadcastTxValidator(seedTx); +} + +void Consensus::start() { + if (this->state_.rdposGetIsValidator() && !this->loopFuture_.valid()) { + this->loopFuture_ = std::async(std::launch::async, &Consensus::validatorLoop, this); + } + if (!this->pullFuture_.valid()) { + this->pullFuture_ = std::async(std::launch::async, &Consensus::pullerLoop, this); + } +} + +void Consensus::stop() { + if (this->loopFuture_.valid()) { + Utils::safePrint("Stopping this->loopFuture_"); + this->stopConsensus_ = true; + this->loopFuture_.wait(); + this->loopFuture_.get(); + } + if (this->pullFuture_.valid()) { + Utils::safePrint("Stopping this->pullFuture_"); + this->stopPuller_ = true; + this->pullFuture_.wait(); + this->pullFuture_.get(); + } +} + diff --git a/src/core/consensus.h b/src/core/consensus.h new file mode 100644 index 00000000..884cdd83 --- /dev/null +++ b/src/core/consensus.h @@ -0,0 +1,74 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef CONSENSUS_H +#define CONSENSUS_H + +#include "state.h" // rdpos.h -> utils/tx.h -> ecdsa.h -> utils.h -> strings.h, logger.h, (libs/json.hpp -> boost/unordered/unordered_flat_map.hpp) + +// TODO: tests for Consensus (if necessary) + +/// Class responsible for processing blocks and transactions. +class Consensus : public Log::LogicalLocationProvider { + private: + State& state_; ///< Reference to the State object. + P2P::ManagerNormal& p2p_; ///< Reference to the P2P connection manager. + const Storage& storage_; ///< Reference to the blockchain storage. + const Options& options_; ///< Reference to the Options singleton. + + std::future loopFuture_; ///< Future object holding the thread for the consensus loop. + std::future pullFuture_; ///< Future object to keep pulling transactions from nodes on the network. + std::atomic stopConsensus_ = false; ///< Flag for stopping the consensus processing. + std::atomic stopPuller_ = false; ///< Flag for stopping the puller processing. + + /** + * Create and broadcast a Validator block (called by validatorLoop()). + * If the node is a Validator and it has to create a new block, + * this function will be called, the new block will be created based on the + * current State and rdPoS objects, and then it will be broadcast. + * @throw DynamicException if block is invalid. + */ + void doValidatorBlock(); + + /** + * Wait for a new block (called by validatorLoop()). + * If the node is a Validator, this function will be called to make the + * node wait until it receives a new block. + */ + void doValidatorTx(const uint64_t& nHeight, const Validator& me); + + /** + * Entry function for the puller thread (keeps requesting transactions from the network). + * @return `true` when done running. + */ + void pullerLoop(); + + public: + /** + * Constructor. + * @param state Reference to the State object. + * @param p2p Reference to the P2P connection manager. + * @param storage Reference to the blockchain storage. + * @param options Reference to the Options singleton. + */ + explicit Consensus(State& state, P2P::ManagerNormal& p2p, const Storage& storage, const Options& options) : + state_(state), p2p_(p2p), storage_(storage), options_(options) {} + + std::string getLogicalLocation() const override { return p2p_.getLogicalLocation(); } ///< Log instance from P2P + + /** + * Entry function for the worker thread (runs the workerLoop() function). + * @return `true` when done running. + */ + bool workerLoop(); + void validatorLoop(); ///< Routine loop for when the node is a Validator. + + void start(); ///< Start the consensus loop. Should only be called after node is synced. + void stop(); ///< Stop the consensus loop. +}; + +#endif // CONSENSUS_H diff --git a/src/core/dump.cpp b/src/core/dump.cpp new file mode 100644 index 00000000..21807174 --- /dev/null +++ b/src/core/dump.cpp @@ -0,0 +1,139 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#include "dump.h" + +DumpManager::DumpManager( + const Storage& storage, const Options& options, std::shared_mutex& stateMutex +) : options_(options), storage_(storage), stateMutex_(stateMutex) {} + +void DumpManager::pushBack(Dumpable* dumpable) { + // Check if latest Dumpable* is the same as the one we trying to append + if (!this->dumpables_.empty() && this->dumpables_.back() == dumpable) return; + dumpables_.push_back(dumpable); +} + +std::vector DumpManager::dumpToBatch(unsigned int threadOffset, unsigned int threadItems) const { + std::vector ret; + for (auto i = threadOffset; i < (threadOffset + threadItems); i++) { + ret.emplace_back(this->dumpables_[i]->dump()); + } + return ret; +} + +std::pair, uint64_t> DumpManager::dumpState() const { + std::pair, uint64_t> ret; + auto& [batches, blockHeight] = ret; + { + // state mutex lock + std::unique_lock lock(stateMutex_); + // We can only safely get the nHeight that we are dumping after **uniquely locking** the state (making ASBOLUTELY sure that no new blocks + // or state changes are happening) + blockHeight = storage_.latest()->getNHeight(); + // Emplace DBBatch operations + LOGDEBUG("Emplace DBBatch operations"); + + const auto nThreads = std::thread::hardware_concurrency(); + auto requiredOffset = this->dumpables_.size() / nThreads; + auto remaining = (this->dumpables_.size() - (requiredOffset * nThreads)); + auto currentOffset = 0; + std::vector>> futures(nThreads); + std::vector> outputs(nThreads); + Utils::safePrint("this->dumpables_.size() = " + std::to_string(this->dumpables_.size())); + Utils::safePrint("nThreads = " + std::to_string(nThreads)); + Utils::safePrint("requiredOffset = " + std::to_string(requiredOffset)); + Utils::safePrint("remaining = " + std::to_string(remaining)); + + for (decltype(futures)::size_type i = 0; i < nThreads; ++i) { + auto nItems = requiredOffset; + if (remaining != 0) { + /// Add a extra job if the division was not perfect and we have remainings + ++nItems; + --remaining; + } + futures[i] = std::async(&DumpManager::dumpToBatch, this, currentOffset, nItems); + currentOffset += nItems; + } + // get futures output (wait thread, implicit) + for (auto i = 0; i < nThreads; ++i) + outputs[i] = futures[i].get(); + + // emplace futures return into batches + for (auto i = 0; i < nThreads; ++i) + for (auto j = 0; j < outputs[i].size(); ++j) + batches.emplace_back(outputs[i][j]); + } + return ret; +} + +std::tuple DumpManager::dumpToDB() const { + std::tuple ret; + auto& [dumpedBlockHeight, serializeTime, dumpTime] = ret; + auto now = std::chrono::system_clock::now(); + Utils::safePrint("Dumping state to DB..."); + auto toDump = this->dumpState(); + serializeTime = std::chrono::duration_cast(std::chrono::system_clock::now() - now).count(); + Utils::safePrint("Dumping state at height " + std::to_string(toDump.second) + " took " + std::to_string(serializeTime) + "ms"); + const auto& [batches, blockHeight] = toDump; + std::string dbName = options_.getRootPath() + "/stateDb/" + std::to_string(blockHeight); + dumpedBlockHeight = blockHeight; + Utils::safePrint("Dumping the new state at height " + std::to_string(blockHeight) + " to " + dbName); + now = std::chrono::system_clock::now(); + DB stateDb(dbName, true); // Compressed + Utils::safePrint("Total Batches to process: " + std::to_string(batches.size())); + for (uint64_t i = 0; i < batches.size(); i++) { + Utils::safePrint("Processing batch: " + std::to_string(i) + " of " + std::to_string(batches.size())); + stateDb.putBatch(batches[i]); + } + dumpTime = std::chrono::duration_cast(std::chrono::system_clock::now() - now).count(); + if (stateDb.close()) + Utils::safePrint("State dumped at height " + std::to_string(blockHeight) + " took " + std::to_string(dumpTime) + "ms"); + + return ret; +} + +DumpWorker::DumpWorker(const Options& options, const Storage& storage, DumpManager& dumpManager) + : options_(options), storage_(storage), dumpManager_(dumpManager) +{ + LOGXTRACE("DumpWorker Started."); +} + +DumpWorker::~DumpWorker() { + stopWorker(); + LOGXTRACE("DumpWorker Stopped."); +} + +bool DumpWorker::workerLoop() { + uint64_t latestBlock = this->storage_.currentChainSize(); + while (!this->stopWorker_) { + if (latestBlock + this->options_.getStateDumpTrigger() < this->storage_.currentChainSize()) { + LOGDEBUG("Current size >= " + std::to_string(this->options_.getStateDumpTrigger())); + dumpManager_.dumpToDB(); + latestBlock = this->storage_.currentChainSize(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + return true; +} + +void DumpWorker::startWorker() +{ + if (!this->workerFuture_.valid()) { + this->workerFuture_ = std::async(std::launch::async, + &DumpWorker::workerLoop, + this); + } +} + +void DumpWorker::stopWorker() +{ + if (this->workerFuture_.valid()) { + this->stopWorker_ = true; + this->workerFuture_.wait(); + this->workerFuture_.get(); + } +} diff --git a/src/core/dump.h b/src/core/dump.h new file mode 100644 index 00000000..0faffe83 --- /dev/null +++ b/src/core/dump.h @@ -0,0 +1,141 @@ +/* +Copyright (c) [2023-2024] [AppLayer Developers] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef DUMP_H +#define DUMP_H + +#include + +#include "storage.h" // utils/db.h, ... -> utils.h -> libs/json.hpp -> functional, vector + +/// Abstraction of a dumpable object (an object that can be dumped to the database). +class Dumpable { + public: + /** + * Pure virtual function to be implemented. + * The function should dump implemented by the methods that are dumpable. + */ + virtual DBBatch dump() const = 0; +}; + +/// Class that manages dumping to the database. Used to store dumpable objects in memory. +class DumpManager : public Log::LogicalLocationProvider { + private: + const Options& options_; ///< Reference to the options object. + const Storage& storage_; ///< Reference to the storage object + std::shared_mutex& stateMutex_; ///< Mutex for managing read/write access to the state object. + std::vector dumpables_; ///< List of Dumpable objects. + + /** + * Auxiliary function used by async calls that processes a little slice of dumps in a separate thread. + * @param threadOffset Offset for the dumpables list. + * @param threadItems How many items to dump from the dumpables list. + * @return A list of DBBatch dump operations. + */ + std::vector dumpToBatch(unsigned int threadOffset, unsigned int threadItems) const; + + public: + /** + * Constructor. + * @param storage Reference to the Storage object. + * @param options Reference to the Options singleton. + * @param stateMutex Reference to the state mutex. + */ + DumpManager(const Storage& storage, const Options& options, std::shared_mutex& stateMutex); + + /// Log instance from Storage. + std::string getLogicalLocation() const override { return storage_.getLogicalLocation(); } + + /** + * Register a Dumpable object into the list. + * @param dumpable Pointer to the Dumpable object to be registered. + */ + void pushBack(Dumpable* dumpable); + + /** + * Call dump functions contained in the dumpable list. + * @returns A vector of DBBatch objects and the nHeight of the last block. + */ + std::pair, uint64_t> dumpState() const; + + /// Dump the state to DB. + /// Returns 0 - Block Height, 1 - Time taken to serialize, 2 - Time taken to dump to DB + std::tuple dumpToDB() const; + + /// Get the size of the dupables list. + size_t size() const { return this->dumpables_.size(); } + + /** + * Get the best state DB patch. + * @param options the options object + * @return a pair of the best state DB patch and the nHeight of the last block. + */ + static std::pair getBestStateDBPath(const Options& options) { + std::filesystem::path stateDbRootFolder = options.getRootPath() + "/stateDb/"; + // Each state DB patch is named with the block height. + // Therefore, we need to list all the directories in the stateDbRootFolder and return + // the one with the highest block height using std::filesystem::directory_iterator. + uint64_t bestHeight = 0; + std::string bestPath; + if (!std::filesystem::exists(stateDbRootFolder)) { + // If the state DB folder does not exist, return stateDbRootFolder + "0" + return std::make_pair(stateDbRootFolder.string() + "0", 0); + } + + for (const auto& entry : std::filesystem::directory_iterator(stateDbRootFolder)) { + std::string path = entry.path().string(); + // Get the block height from the path + uint64_t height = std::stoull(path.substr(path.find_last_of('/') + 1)); + if (height > bestHeight) { + bestHeight = height; + bestPath = path; + } + } + if (bestPath.empty()) { + // If there are no state DB patches, return stateDbRootFolder + "0" + return std::make_pair(stateDbRootFolder.string() + "0", 0); + } + return std::make_pair(bestPath, bestHeight); + } +}; + +/// Helper class for the database dumper's worker thread. +class DumpWorker : public Log::LogicalLocationProvider { + private: + const Options& options_; ///< Reference to the Options singleton. + const Storage& storage_; ///< Reference to the Storage object. + DumpManager& dumpManager_; ///< Reference to the DumpManager object. + std::atomic stopWorker_ = false; ///< Flag for stopping the worker thread. + std::future workerFuture_; ///< Future object for the worker thread, used to wait for the thread to finish. + std::atomic canDump_ = false; ///< Flag for knowing if the worker is ready to dump. + + /** + * Entry function for the worker thread (runs the workerLoop() function). + * @return `true` when done running. + */ + bool workerLoop(); + + public: + /** + * Constructor. Automatically starts the worker thread. + * @param options Reference to the Options singleton. + * @param storage Reference to the Storage object. + * @param dumpManager Reference to the DumpManager object. + */ + DumpWorker(const Options& options, const Storage& storage, DumpManager& dumpManager); + + /// Destructor. Automatically stops the worker thread if it's still running. + ~DumpWorker(); + + /// Log instance from Storage. + std::string getLogicalLocation() const override { return storage_.getLogicalLocation(); } + + void startWorker(); ///< Start `workerFuture_` and `workerLoop()`. + void stopWorker(); ///< Stop `workerFuture_` and `workerLoop()`. +}; + +#endif // DUMP_H diff --git a/src/core/rdpos.cpp b/src/core/rdpos.cpp index 1c625e23..94f006e8 100644 --- a/src/core/rdpos.cpp +++ b/src/core/rdpos.cpp @@ -1,100 +1,68 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. */ #include "rdpos.h" -#include "storage.h" -#include "state.h" -#include "../contract/contractmanager.h" -#include "../utils/block.h" -rdPoS::rdPoS(const std::unique_ptr& db, - const std::unique_ptr& storage, - const std::unique_ptr& p2p, - const std::unique_ptr& options, - const std::unique_ptr& state) : - BaseContract("rdPoS", ProtocolContractAddresses.at("rdPoS"), Address(), options->getChainID(), db), - storage_(storage), - p2p_(p2p), - options_(options), - state_(state), - worker_(std::make_unique(*this)), - validatorKey_(options->getValidatorPrivKey()), +#include "../utils/uintconv.h" + +rdPoS::rdPoS( + const DB& db, DumpManager& dumpManager, const Storage& storage, + P2P::ManagerNormal& p2p, const Options& options +) : BaseContract("rdPoS", ProtocolContractAddresses.at("rdPoS"), Address(), options.getChainID()), + options_(options), storage_(storage), p2p_(p2p), + validatorKey_(options.getValidatorPrivKey()), isValidator_((this->validatorKey_) ? true : false), - randomGen_(Hash()) + randomGen_(Hash()), minValidators_(options.getMinValidators()) { - // Initialize blockchain. - std::unique_lock lock(this->mutex_); - Logger::logToDebug(LogType::INFO, Log::rdPoS, __func__, "Initializing rdPoS."); - initializeBlockchain(); - /** - * Load information from DB, stored as following: + * Initialize blockchain and load information from DB, stored as following: * DBPrefix::rdPoS -> rdPoS mapping (addresses) * DBPrefix::rdPoS -> misc: used for randomness currently. * Order doesn't matter, Validators are stored in a set (sorted by default). */ - auto validatorsDb = db->getBatch(DBPrefix::rdPoS); - if (validatorsDb.empty()) { + LOGINFO("Initializing rdPoS."); + if (auto validatorsDb = db.getBatch(DBPrefix::rdPoS); validatorsDb.empty()) { // No rdPoS in DB, this should have been initialized by Storage. - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, "No rdPoS in DB, cannot proceed."); - throw std::runtime_error("No rdPoS in DB."); - } - Logger::logToDebug(LogType::INFO, Log::rdPoS, __func__, "Found " + std::to_string(validatorsDb.size()) + " rdPoS in DB"); - // TODO: check if no index is missing from DB. - for (const auto& validator : validatorsDb) { - this->validators_.insert(Validator(Address(validator.value))); + LOGINFO("No rdPoS in DB, initializing chain with Options."); + for (const auto& address : this->options_.getGenesisValidators()) { + this->validators_.insert(Validator(address)); + } + } else { + LOGINFO("Found " + std::to_string(validatorsDb.size()) + " rdPoS in DB"); + // TODO: check if no index is missing from DB. + for (const auto& validator : validatorsDb) { + this->validators_.insert(Validator(Address(validator.value))); + } } // Load latest randomness from DB, populate and shuffle the random list. - this->bestRandomSeed_ = storage->latest()->getBlockRandomness(); + this->bestRandomSeed_ = storage_.latest()->getBlockRandomness(); randomGen_.setSeed(this->bestRandomSeed_); this->randomList_ = std::vector(this->validators_.begin(), this->validators_.end()); randomGen_.shuffle(randomList_); + // Register itself at dump management + dumpManager.pushBack(this); } -rdPoS::~rdPoS() { - this->stoprdPoSWorker(); - std::unique_lock lock(this->mutex_); - DBBatch validatorsBatch; - Logger::logToDebug(LogType::INFO, Log::rdPoS, __func__, "Descontructing rdPoS, saving to DB."); - // Save rdPoS to DB. - uint64_t index = 0; - for (const auto &validator : this->validators_) { - validatorsBatch.push_back(Utils::uint64ToBytes(index), validator.get(), DBPrefix::rdPoS); - index++; - } - this->db_->putBatch(validatorsBatch); -} +rdPoS::~rdPoS() {} -bool rdPoS::validateBlock(const Block& block) const { - std::lock_guard lock(this->mutex_); - auto latestBlock = this->storage_->latest(); - // Check if block signature matches randomList[0] - if (!block.isFinalized()) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - "Block is not finalized, cannot be validated. latest nHeight: " - + std::to_string(latestBlock->getNHeight()) - + " Block nHeight: " + std::to_string(block.getNHeight()) - ); - return false; - } +bool rdPoS::validateBlock(const FinalizedBlock& block) const { + auto latestBlock = this->storage_.latest(); if (Secp256k1::toAddress(block.getValidatorPubKey()) != randomList_[0]) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - "Block signature does not match randomList[0]. latest nHeight: " + LOGERROR("Block signature does not match randomList[0]. latest nHeight: " + std::to_string(latestBlock->getNHeight()) + " Block nHeight: " + std::to_string(block.getNHeight()) ); return false; } - if (block.getTxValidators().size() != rdPoS::minValidators * 2) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - "Block contains invalid number of TxValidator transactions. latest nHeight: " + if (block.getTxValidators().size() != this->minValidators_ * 2) { + LOGERROR("Block contains invalid number of TxValidator transactions. latest nHeight: " + std::to_string(latestBlock->getNHeight()) + " Block nHeight: " + std::to_string(block.getNHeight()) ); @@ -104,8 +72,7 @@ bool rdPoS::validateBlock(const Block& block) const { // Check if all transactions are of the same block height for (const auto& tx : block.getTxValidators()) { if (tx.getNHeight() != block.getNHeight()) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - "TxValidator transaction is not of the same block height. tx nHeight: " + LOGERROR("TxValidator transaction is not of the same block height. tx nHeight: " + std::to_string(tx.getNHeight()) + " Block nHeight: " + std::to_string(block.getNHeight())); return false; @@ -121,110 +88,97 @@ bool rdPoS::validateBlock(const Block& block) const { * The remaining 4 (minValidators) transactions from should also match randomList[1] to randomList[5] (minValidators +1) * The remaining 4 (minValidators) transactions should be random transactions. (0x6fc5a2d6), which contains the seed itself. */ - std::unordered_map txHashToSeedMap; // Tx randomHash -> Tx random - for (uint64_t i = 0; i < rdPoS::minValidators; i++) { + boost::unordered_flat_map txHashToSeedMap; // Tx randomHash -> Tx random + for (uint64_t i = 0; i < this->minValidators_; i++) { if (Validator(block.getTxValidators()[i].getFrom()) != randomList_[i+1]) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - "TxValidator randomHash " + std::to_string(i) + " is not ordered correctly." + LOGERROR("TxValidator randomHash " + std::to_string(i) + " is not ordered correctly." + "Expected: " + randomList_[i+1].hex().get() + " Got: " + block.getTxValidators()[i].getFrom().hex().get() ); return false; } - if (Validator(block.getTxValidators()[i + rdPoS::minValidators].getFrom()) != randomList_[i+1]) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - "TxValidator random " + std::to_string(i) + " is not ordered correctly." + if (Validator(block.getTxValidators()[i + this->minValidators_].getFrom()) != randomList_[i+1]) { + LOGERROR("TxValidator random " + std::to_string(i) + " is not ordered correctly." + "Expected: " + randomList_[i+1].hex().get() + " Got: " + block.getTxValidators()[i].getFrom().hex().get() ); return false; } - txHashToSeedMap.emplace(block.getTxValidators()[i],block.getTxValidators()[i + rdPoS::minValidators]); + txHashToSeedMap.emplace(block.getTxValidators()[i],block.getTxValidators()[i + this->minValidators_]); } - if (txHashToSeedMap.size() != rdPoS::minValidators) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, "txHashToSeedMap doesn't match minValidator size."); + if (txHashToSeedMap.size() != this->minValidators_) { + LOGERROR("txHashToSeedMap doesn't match minValidator size."); return false; } // Check the transactions within the block, we should have every transaction within the txHashToSeed map. - for (auto const& [hashTx, seedTx] : txHashToSeedMap) { - TxValidatorFunction hashTxFunction = rdPoS::getTxValidatorFunction(hashTx); - TxValidatorFunction seedTxFunction = rdPoS::getTxValidatorFunction(seedTx); - // Check if hash tx is invalid by itself. - if (hashTxFunction == TxValidatorFunction::INVALID) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - std::string("TxValidator ") + hashTx.hash().hex().get() + " is invalid." - ); - return false; - } - if (seedTxFunction == TxValidatorFunction::INVALID) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - std::string("TxValidator ") + seedTx.hash().hex().get() + " is invalid." - ); - return false; - } - // Check if senders match. - if (hashTx.getFrom() != seedTx.getFrom()) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - std::string("TxValidator sender ") + seedTx.hash().hex().get() - + " does not match TxValidator sender " + hashTx.hash().hex().get() - ); - return false; - } - // Check if the left sided transaction is a randomHash transaction. - if (hashTxFunction != TxValidatorFunction::RANDOMHASH) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - std::string("TxValidator ") + hashTx.hash().hex().get() + " is not a randomHash transaction." - ); - return false; - } - // Check if the right sided transaction is a random transaction. - if (seedTxFunction != TxValidatorFunction::RANDOMSEED) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - std::string("TxValidator ") + seedTx.hash().hex().get() + " is not a random transaction." - ); - return false; - } - // Check if the randomHash transaction matches the random transaction. - BytesArrView hashTxData = hashTx.getData(); - BytesArrView seedTxData = seedTx.getData(); - BytesArrView hash = hashTxData.subspan(4); - BytesArrView random = seedTxData.subspan(4); + for (const auto& [hashTx, seedTx] : txHashToSeedMap) { + if (!this->validateBlockTxSanityCheck(hashTx, seedTx)) return false; + } + return true; +} - // Size sanity check, should be 32 bytes. - if (hash.size() != 32) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - std::string("TxValidator ") + hashTx.hash().hex().get() + " (hash) is not 32 bytes." - ); - return false; - } +bool rdPoS::validateBlockTxSanityCheck(const TxValidator& hashTx, const TxValidator& seedTx) const { + TxValidatorFunction hashTxFunction = rdPoS::getTxValidatorFunction(hashTx); + TxValidatorFunction seedTxFunction = rdPoS::getTxValidatorFunction(seedTx); - if (random.size() != 32) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - std::string("TxValidator ") + seedTx.hash().hex().get() + " (random) is not 32 bytes." - ); - return false; - } + // Check if hash tx is invalid by itself. + if (hashTxFunction == TxValidatorFunction::INVALID) { + LOGERROR(std::string("TxValidator ") + hashTx.hash().hex().get() + " is invalid."); + return false; + } + if (seedTxFunction == TxValidatorFunction::INVALID) { + LOGERROR(std::string("TxValidator ") + seedTx.hash().hex().get() + " is invalid."); + return false; + } - if (Utils::sha3(random) != hash) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - std::string("TxValidator ") + seedTx.hash().hex().get() - + " does not match TxValidator " + hashTx.hash().hex().get() + " randomness" - ); - return false; - } + // Check if senders match. + if (hashTx.getFrom() != seedTx.getFrom()) { + LOGERROR(std::string("TxValidator sender ") + seedTx.hash().hex().get() + + " does not match TxValidator sender " + hashTx.hash().hex().get() + ); + return false; + } + + // Check if the left sided transaction is a randomHash transaction. + if (hashTxFunction != TxValidatorFunction::RANDOMHASH) { + LOGERROR(std::string("TxValidator ") + hashTx.hash().hex().get() + " is not a randomHash transaction."); + return false; + } + + // Check if the right sided transaction is a random transaction. + if (seedTxFunction != TxValidatorFunction::RANDOMSEED) { + LOGERROR(std::string("TxValidator ") + seedTx.hash().hex().get() + " is not a random transaction."); + return false; + } + + // Check if the randomHash transaction matches the random transaction. + View hashTxData = hashTx.getData(); + View seedTxData = seedTx.getData(); + View hash = hashTxData.subspan(4); + View random = seedTxData.subspan(4); + + // Size sanity check, should be 32 bytes. + if (hash.size() != 32) { + LOGERROR(std::string("TxValidator ") + hashTx.hash().hex().get() + " (hash) is not 32 bytes."); + return false; + } + if (random.size() != 32) { + LOGERROR(std::string("TxValidator ") + seedTx.hash().hex().get() + " (random) is not 32 bytes."); + return false; + } + if (Utils::sha3(random) != Hash(hash)) { + LOGERROR(std::string("TxValidator ") + seedTx.hash().hex().get() + + " does not match TxValidator " + hashTx.hash().hex().get() + " randomness" + ); + return false; } return true; } -Hash rdPoS::processBlock(const Block& block) { - std::unique_lock lock(this->mutex_); - if (!block.isFinalized()) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, "Block is not finalized."); - throw std::runtime_error("Block is not finalized."); - } - validatorMempool_.clear(); +Hash rdPoS::processBlock(const FinalizedBlock& block) { + this->validatorMempool_.clear(); this->randomList_ = std::vector(this->validators_.begin(), this->validators_.end()); this->bestRandomSeed_ = block.getBlockRandomness(); this->randomGen_.setSeed(this->bestRandomSeed_); @@ -232,43 +186,31 @@ Hash rdPoS::processBlock(const Block& block) { return this->bestRandomSeed_; } -void rdPoS::signBlock(Block &block) { - uint64_t newTimestamp = std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch() - ).count(); - block.finalize(this->validatorKey_, newTimestamp); - this->worker_->blockCreated(); -} - -bool rdPoS::addValidatorTx(const TxValidator& tx) { - std::unique_lock lock(this->mutex_); +TxStatus rdPoS::addValidatorTx(const TxValidator& tx) { if (this->validatorMempool_.contains(tx.hash())) { - Logger::logToDebug(LogType::INFO, Log::rdPoS, __func__, "TxValidator already exists in mempool."); - return true; + LOGTRACE("TxValidator already exists in mempool."); + return TxStatus::ValidExisting; } - if (tx.getNHeight() != this->storage_->latest()->getNHeight() + 1) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - "TxValidator is not for the next block. Expected: " - + std::to_string(this->storage_->latest()->getNHeight() + 1) + if (tx.getNHeight() != this->storage_.latest()->getNHeight() + 1) { + LOGERROR("TxValidator is not for the next block. Expected: " + + std::to_string(this->storage_.latest()->getNHeight() + 1) + " Got: " + std::to_string(tx.getNHeight()) ); - return false; + return TxStatus::InvalidUnexpected; } // Check if sender is a validator and can participate in this rdPoS round (check from existance in randomList) bool participates = false; - for (uint64_t i = 1; i < rdPoS::minValidators + 1; ++i) { + for (uint64_t i = 1; i < this->minValidators_ + 1; i++) { if (Validator(tx.getFrom()) == this->randomList_[i]) { participates = true; break; } } if (!participates) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, - "TxValidator sender is not a validator or is not participating in this rdPoS round." - ); - return false; + LOGERROR("TxValidator sender is not a validator or is not participating in this rdPoS round."); + return TxStatus::InvalidUnexpected; } // Do not allow duplicate transactions for the same function, we only have two functions (2 TxValidator per validator per block) @@ -276,32 +218,24 @@ bool rdPoS::addValidatorTx(const TxValidator& tx) { for (auto const& [key, value] : this->validatorMempool_) { if (value.getFrom() == tx.getFrom()) txs.push_back(value); } + if (txs.empty()) { // No transactions from this sender yet, add it. this->validatorMempool_.emplace(tx.hash(), tx); - return true; - } else if (txs.size() == 1) { // We already have one transaction from this sender, check if it is the same function. + return TxStatus::ValidNew; + } + + if (txs.size() == 1) { // We already have one transaction from this sender, check if it is the same function. if (txs[0].getFunctor() == tx.getFunctor()) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, "TxValidator sender already has a transaction for this function."); - return false; + LOGERROR("TxValidator sender already has a transaction for this function."); + return TxStatus::InvalidRedundant; } this->validatorMempool_.emplace(tx.hash(), tx); - } else { // We already have two transactions from this sender, it is the max we can have per validator. - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, "TxValidator sender already has two transactions."); - return false; + return TxStatus::ValidNew; } - return true; -} - -void rdPoS::initializeBlockchain() const { - auto validatorsDb = db_->getBatch(DBPrefix::rdPoS); - if (validatorsDb.empty()) { - Logger::logToDebug(LogType::INFO, Log::rdPoS,__func__, "No rdPoS in DB, initializing."); - // Use the genesis validators from Options, OPTIONS JSON FILE VALIDATOR ARRAY ORDER **MATTERS** - for (uint64_t i = 0; i < this->options_->getGenesisValidators().size(); ++i) { - this->db_->put(Utils::uint64ToBytes(i), this->options_->getGenesisValidators()[i].get(), DBPrefix::rdPoS); - } - } + // We already have two transactions from this sender, it is the max we can have per validator. + LOGERROR("TxValidator sender already has two transactions."); + return TxStatus::InvalidRedundant; } Hash rdPoS::parseTxSeedList(const std::vector& txs) { @@ -316,15 +250,11 @@ Hash rdPoS::parseTxSeedList(const std::vector& txs) { return Utils::sha3(seed); } -const std::atomic& rdPoS::canCreateBlock() const { - return this->worker_->getCanCreateBlock(); -} - rdPoS::TxValidatorFunction rdPoS::getTxValidatorFunction(const TxValidator &tx) { - constexpr Functor randomHashHash(Bytes{0xcf, 0xff, 0xe7, 0x46}); - constexpr Functor randomSeedHash(Bytes{0x6f, 0xc5, 0xa2, 0xd6}); + constexpr Functor randomHashHash{3489654598}; + constexpr Functor randomSeedHash{1875223254}; if (tx.getData().size() != 36) { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, "TxValidator data size is not 36 bytes."); + SLOGERROR("TxValidator data size is not 36 bytes."); // Both RandomHash and RandomSeed are 32 bytes, so if the data size is not 36 bytes, it is invalid. return TxValidatorFunction::INVALID; } @@ -334,188 +264,21 @@ rdPoS::TxValidatorFunction rdPoS::getTxValidatorFunction(const TxValidator &tx) } else if (functionABI == randomSeedHash) { return TxValidatorFunction::RANDOMSEED; } else { - Logger::logToDebug(LogType::ERROR, Log::rdPoS, __func__, "TxValidator function ABI is not recognized."); + SLOGERROR("TxValidator function ABI is not recognized: " + std::to_string(functionABI.value) + " tx Data: " + Hex::fromBytes(tx.getData()).get()); return TxValidatorFunction::INVALID; } } -void rdPoS::startrdPoSWorker() { this->worker_->start(); } - -void rdPoS::stoprdPoSWorker() { this->worker_->stop(); } - -bool rdPoSWorker::checkLatestBlock() { - if (this->latestBlock_ == nullptr) { - this->latestBlock_ = this->rdpos_.storage_->latest(); - return false; - } - if (this->latestBlock_ != this->rdpos_.storage_->latest()) return true; - return false; -} - -bool rdPoSWorker::workerLoop() { - Validator me(Secp256k1::toAddress(Secp256k1::toUPub(this->rdpos_.validatorKey_))); - this->latestBlock_ = this->rdpos_.storage_->latest(); - while (!this->stopWorker_) { - // Check if we are the validator required for signing the block. - bool isBlockCreator = false; - // Scope for unique_lock. - { - std::unique_lock checkValidatorsList(this->rdpos_.mutex_); - if (me == this->rdpos_.randomList_[0]) { - isBlockCreator = true; - checkValidatorsList.unlock(); - doBlockCreation(); - } - - // Check if we are one of the rdPoS that need to create random transactions. - if (!isBlockCreator) { - for (uint64_t i = 1; i <= rdPoS::minValidators; i++) { - if (me == this->rdpos_.randomList_[i]) { - checkValidatorsList.unlock(); - doTxCreation(this->latestBlock_->getNHeight() + 1, me); - } - } - } - } - - // After processing everything. wait until the new block is appended to the chain. - while (!this->checkLatestBlock() && !this->stopWorker_) { - Logger::logToDebug(LogType::INFO, Log::rdPoS, __func__, - "Waiting for new block to be appended to the chain. (Height: " - + std::to_string(this->latestBlock_->getNHeight()) + ")" + " latest height: " - + std::to_string(this->rdpos_.storage_->latest()->getNHeight()) - ); - Logger::logToDebug(LogType::INFO, Log::rdPoS, __func__, - "Currently has " + std::to_string(this->rdpos_.validatorMempool_.size()) - + " transactions in mempool." - ); - std::unique_lock mempoolSizeLock(this->rdpos_.mutex_); - uint64_t mempoolSize = this->rdpos_.validatorMempool_.size(); - if (mempoolSize < rdPoS::minValidators) { // Always try to fill the mempool to 8 transactions - mempoolSizeLock.unlock(); - // Try to get more transactions from other nodes within the network - auto connectedNodesList = this->rdpos_.p2p_->getSessionsIDs(); - for (auto const& nodeId : connectedNodesList) { - if (this->checkLatestBlock() || this->stopWorker_) break; - auto txList = this->rdpos_.p2p_->requestValidatorTxs(nodeId); - if (this->checkLatestBlock() || this->stopWorker_) break; - for (auto const& tx : txList) this->rdpos_.state_->addValidatorTx(tx); - } - } else { - mempoolSizeLock.unlock(); - } - std::this_thread::sleep_for(std::chrono::milliseconds(25)); - } - // Update latest block if necessary. - if (isBlockCreator) this->canCreateBlock_ = false; - this->latestBlock_ = this->rdpos_.storage_->latest(); - } - return true; -} - -void rdPoSWorker::doBlockCreation() { - // TODO: add requesting transactions to other nodes when mempool is not filled up - Logger::logToDebug(LogType::INFO, Log::rdPoS, __func__, "Block creator: waiting for txs"); - uint64_t validatorMempoolSize = 0; - while (validatorMempoolSize != rdPoS::minValidators * 2 && !this->stopWorker_) - { - Logger::logToDebug(LogType::INFO, Log::rdPoS, __func__, - "Block creator has: " + std::to_string(validatorMempoolSize) + " transactions in mempool" - ); - // Scope for lock. - { - std::unique_lock mempoolSizeLock(this->rdpos_.mutex_); - validatorMempoolSize = this->rdpos_.validatorMempool_.size(); - } - // Try to get more transactions from other nodes within the network - auto connectedNodesList = this->rdpos_.p2p_->getSessionsIDs(); - for (auto const& nodeId : connectedNodesList) { - auto txList = this->rdpos_.p2p_->requestValidatorTxs(nodeId); - if (this->stopWorker_) return; - for (auto const& tx : txList) this->rdpos_.state_->addValidatorTx(tx); - } - std::this_thread::sleep_for(std::chrono::milliseconds(25)); - } - Logger::logToDebug(LogType::INFO, Log::rdPoS, __func__, "Validator ready to create a block"); - // After processing everything, we can let everybody know that we are ready to create a block - this->canCreateBlock_ = true; -} - -void rdPoSWorker::doTxCreation(const uint64_t& nHeight, const Validator& me) { - Hash randomness = Hash::random(); - Hash randomHash = Utils::sha3(randomness.get()); - Logger::logToDebug(LogType::INFO, Log::rdPoS, __func__, "Creating random Hash transaction"); - Bytes randomHashBytes = Hex::toBytes("0xcfffe746"); - randomHashBytes.insert(randomHashBytes.end(), randomHash.get().begin(), randomHash.get().end()); - TxValidator randomHashTx( - me.address(), - randomHashBytes, - this->rdpos_.options_->getChainID(), - nHeight, - this->rdpos_.validatorKey_ - ); - - Bytes seedBytes = Hex::toBytes("0x6fc5a2d6"); - seedBytes.insert(seedBytes.end(), randomness.get().begin(), randomness.get().end()); - TxValidator seedTx( - me.address(), - seedBytes, - this->rdpos_.options_->getChainID(), - nHeight, - this->rdpos_.validatorKey_ - ); - - // Sanity check if tx is valid - BytesArrView randomHashTxView(randomHashTx.getData()); - BytesArrView randomSeedTxView(seedTx.getData()); - if (Utils::sha3(randomSeedTxView.subspan(4)) != randomHashTxView.subspan(4)) { - Logger::logToDebug(LogType::INFO, Log::rdPoS, __func__, "RandomHash transaction is not valid!!!"); - return; - } - - // Append to mempool and broadcast the transaction across all nodes. - Logger::logToDebug(LogType::INFO, Log::rdPoS, __func__, "Broadcasting randomHash transaction"); - this->rdpos_.state_->addValidatorTx(randomHashTx); - this->rdpos_.p2p_->broadcastTxValidator(randomHashTx); - - // Wait until we received all randomHash transactions to broadcast the randomness transaction - Logger::logToDebug(LogType::INFO, Log::rdPoS, __func__, "Waiting for randomHash transactions to be broadcasted"); - uint64_t validatorMempoolSize = 0; - while (validatorMempoolSize < rdPoS::minValidators && !this->stopWorker_) { - Logger::logToDebug(LogType::INFO, Log::rdPoS, __func__, - "Validator has: " + std::to_string(validatorMempoolSize) + " transactions in mempool" - ); - // Scope for lock - { - std::unique_lock mempoolSizeLock(this->rdpos_.mutex_); - validatorMempoolSize = this->rdpos_.validatorMempool_.size(); - } - // Try to get more transactions from other nodes within the network - auto connectedNodesList = this->rdpos_.p2p_->getSessionsIDs(); - for (auto const& nodeId : connectedNodesList) { - if (this->stopWorker_) return; - auto txList = this->rdpos_.p2p_->requestValidatorTxs(nodeId); - for (auto const& tx : txList) this->rdpos_.state_->addValidatorTx(tx); - } - std::this_thread::sleep_for(std::chrono::milliseconds(25)); - } - - Logger::logToDebug(LogType::INFO, Log::rdPoS, __func__, "Broadcasting random transaction"); - // Append and broadcast the randomness transaction. - this->rdpos_.state_->addValidatorTx(seedTx); - this->rdpos_.p2p_->broadcastTxValidator(seedTx); -} -void rdPoSWorker::start() { - if (this->rdpos_.isValidator_ && !this->workerFuture_.valid()) { - this->workerFuture_ = std::async(std::launch::async, &rdPoSWorker::workerLoop, this); - } -} - -void rdPoSWorker::stop() { - if (this->workerFuture_.valid()) { - this->stopWorker_ = true; - this->workerFuture_.wait(); - this->workerFuture_.get(); +DBBatch rdPoS::dump() const +{ + DBBatch dbBatch; + LOGDEBUG("Create batch operations."); + // index + uint64_t i = 0; + // add batch operations + for (const auto &validator : this->validators_) { + dbBatch.push_back(UintConv::uint64ToBytes(i), validator, DBPrefix::rdPoS); + i++; } + return dbBatch; } - diff --git a/src/core/rdpos.h b/src/core/rdpos.h index 3d0feb39..10c4c64a 100644 --- a/src/core/rdpos.h +++ b/src/core/rdpos.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,27 +8,34 @@ See the LICENSE.txt file in the project root for more information. #ifndef RDPOS_H #define RDPOS_H -#include "../contract/contract.h" -#include "../utils/strings.h" -#include "../utils/tx.h" -#include "../utils/safehash.h" -#include "../utils/randomgen.h" -#include "../utils/options.h" -#include "../net/p2p/managernormal.h" - -#include -#include #include +#include "../utils/tx.h" // ecdsa.h -> utils.h -> strings.h, (libs/json.hpp -> boost/unordered/unordered_flat_map.hpp) + +#include "../contract/contract.h" // dump.h -> storage.h -> safehash.h, randomgen.h, options.h + +#include "../net/p2p/managernormal.h" + // Forward declarations. -class rdPoSWorker; +class rdPoS; class Storage; -class Block; -class State; // "0x6fc5a2d6" -> Function for random tx // "0xcfffe746" -> Function for random hash tx +/// Enum for labeling transaction status. +enum class TxStatus { + ValidNew, + ValidExisting, + InvalidNonce, // Tx only + InvalidBalance, // Tx only + InvalidUnexpected, // ValidatorTx only + InvalidDuplicate, // ValidatorTx only + InvalidRedundant // ValidatorTx only +}; + +inline bool isTxStatusValid(const TxStatus& txStatus) { return txStatus <= TxStatus::ValidExisting; } + /** * Abstraction of a validator, same as Address but different type. * Responsible for creating/signing/validating blocks. @@ -38,157 +45,98 @@ class Validator : public Address { /// Constructor. Validator(const Address& add) : Address(add) {} - /// Copy constructor. - Validator(const Validator& other) : Address(other.data_) {} - - /** - * Getter for the address. - * @return The address. - */ - const Address address() const { return Address(this->data_); } - - /// Copy assignment operator. - Validator& operator=(const Validator& other) { - this->data_ = other.data_; - return *this; - } + /// Getter for the address. + Address address() const { return Address(*this); } }; /// Abstraction of the %rdPoS (Random Deterministic Proof of Stake) consensus algorithm. -class rdPoS : public BaseContract { +class rdPoS : public BaseContract, public Log::LogicalLocationProvider { private: - /// Pointer to the blockchain's storage. - const std::unique_ptr& storage_; - - /// Pointer to the P2P Manager (for sending/requesting TxValidators from other nodes). - const std::unique_ptr& p2p_; - - /// Pointer to the blockchain state. - const std::unique_ptr& state_; - - /// Pointer to the options singleton. - const std::unique_ptr& options_; - - /// Pointer to the worker object. - const std::unique_ptr worker_; - - /// Ordered list of rdPoS validators. - std::set validators_; - - /// Shuffled version of the validator list, used at block creation/signing. - std::vector randomList_; - - /// Mempool for validator transactions. - std::unordered_map validatorMempool_; - - /// Private key for operating a validator. - const PrivKey validatorKey_; - - /// Indicated whether this node is a Validator or not. - const bool isValidator_ = false; - - /// Randomness generator (for use in seeding). - RandomGen randomGen_; - - /// Best randomness seed (taken from the last block). - Hash bestRandomSeed_; - - /// Mutex for managing read/write access to the class members. - mutable std::shared_mutex mutex_; - - /** - * Initializes the blockchain with the default information for rdPoS. - * Called by the constructor if no previous blockchain is found. - */ - void initializeBlockchain() const; + const Options& options_; ///< Reference to the options singleton. + const Storage& storage_; ///< Reference to the blockchain's storage. + P2P::ManagerNormal& p2p_; ///< Reference to the P2P Manager (for sending/requesting TxValidators from other nodes). + std::set validators_; ///< Ordered list of rdPoS validators. + std::vector randomList_; ///< Shuffled version of the validator list, used at block creation/signing. + boost::unordered_flat_map validatorMempool_; ///< Mempool for validator transactions. + const PrivKey validatorKey_; ///< Private key for operating a validator. + const bool isValidator_ = false; ///< Indicates whether node is a Validator. + RandomGen randomGen_; ///< Randomness generator (for use in seeding). + Hash bestRandomSeed_; ///< Best randomness seed (taken from the last block). + const uint32_t minValidators_; ///< Minimum required number of Validators for creating and signing blocks. + + /** + * Helper function that does a sanity check on a given pair of Validator txs. + * Used exclusively by validateBlock(). + * @param hashTx A Validator's hash tx. + * @param seedTx A Validator's seed tx. + * @return `true` if tx data is valid, `false` otherwise. + */ + bool validateBlockTxSanityCheck(const TxValidator& hashTx, const TxValidator& seedTx) const; public: /// Enum for Validator transaction functions. - enum TxValidatorFunction { INVALID, RANDOMHASH, RANDOMSEED }; + enum class TxValidatorFunction { INVALID, RANDOMHASH, RANDOMSEED }; + + /// Enum for transaction types. + enum class TxType { addValidator, removeValidator, randomHash, randomSeed }; /** * Constructor. - * @param db Pointer to the database. - * @param storage Pointer to the blockchain's storage. - * @param p2p Pointer to the P2P connection manager. - * @param options Pointer to the options singleton. - * @param state Pointer to the blockchain's state. - * @throw std::runtime_error if there are no Validators registered in the database. + * @param db Reference to the database. + * @param manager Reference to the database dumping manager. + * @param storage Reference to the blockchain's storage. + * @param p2p Reference to the P2P connection manager. + * @param options Reference to the options singleton. + * @throw DynamicException if there are no Validators registered in the database. */ - rdPoS( - const std::unique_ptr& db, const std::unique_ptr& storage, - const std::unique_ptr& p2p, - const std::unique_ptr& options, const std::unique_ptr& state - ); + rdPoS(const DB& db, DumpManager& manager, const Storage& storage, P2P::ManagerNormal& p2p, const Options& options); - /// Destructor. - ~rdPoS() override; + ~rdPoS() override; ///< Destructor. - /// Enum for transaction types. - enum TxType { addValidator, removeValidator, randomHash, randomSeed }; - - /// Minimum number of required Validators for creating and signing blocks. - static const uint32_t minValidators = 4; - - /// Getter for `validators`. Not a reference because the inner set can be changed. - const std::set getValidators() const { std::shared_lock lock(this->mutex_); return validators_; } - - /// Getter for `randomList`. Not a reference because the inner vector can be changed. - const std::vector getRandomList() const { std::shared_lock lock(this->mutex_); return randomList_; } - - /// Getter for `mempool`. Not a reference because the inner map can be changed. - const std::unordered_map getMempool() const { std::shared_lock lock(this->mutex_); return validatorMempool_; } + std::string getLogicalLocation() const override { return p2p_.getLogicalLocation(); } ///< Log instance from P2P - /// Getter for `bestRandomSeed`. - const Hash getBestRandomSeed() const { std::shared_lock lock(this->mutex_); return this->bestRandomSeed_; } - - /// Getter for `isValidator`. - const bool getIsValidator() const { return this->isValidator_; } - - /// Getter for `validatorKey`, converted to an uncompressed public key. - const UPubKey getValidatorUPubKey() const { return Secp256k1::toUPub(this->validatorKey_); } + ///@{ + /** Getter. */ + std::set getValidators() const { return this->validators_; } + std::vector getRandomList() const { return this->randomList_; } + const boost::unordered_flat_map getMempool() const { return this->validatorMempool_; } + size_t getMempoolSize() const { return this->validatorMempool_.size(); } + const Hash& getBestRandomSeed() const { return this->bestRandomSeed_; } + bool getIsValidator() const { return this->isValidator_; } + UPubKey getValidatorUPubKey() const { return Secp256k1::toUPub(this->validatorKey_); } + const uint32_t& getMinValidators() const { return this->minValidators_; } + ///@} /** * Check if a given Address is a Validator. * @param add The address to check. - * @return `true` if address is in the validator list, `false` otherwise. + * @return `true` if address is in the Validator list, `false` otherwise. */ - const bool isValidatorAddress(const Address& add) const { std::shared_lock lock(this->mutex_); return validators_.contains(Validator(add)); } - - /// Clear the mempool. - void clearMempool() { std::unique_lock lock(this->mutex_); this->validatorMempool_.clear(); } + bool isValidatorAddress(const Address& add) const { return validators_.contains(Validator(add)); } /** * Validate a block. * @param block The block to validate. * @return `true` if the block is properly validated, `false` otherwise. */ - bool validateBlock(const Block& block) const; + bool validateBlock(const FinalizedBlock& block) const; /** - * Process a block. - * Should be called from State, after a block is validated and before it is added to Storage. + * Process a block. Should be called from State, after a block is validated but before it is added to Storage. * @param block The block to process. * @return The new randomness seed to be used for the next block. - * @throw std::runtime_error if block is not finalized. - */ - Hash processBlock(const Block& block); - - /** - * Sign a block using the Validator's private key. - * @param block The block to sign. + * @throw DynamicException if block is not finalized. */ - void signBlock(Block& block); + Hash processBlock(const FinalizedBlock& block); /** * Add a Validator transaction to the mempool. * Should ONLY be called by the State, as it locks the current state mutex, - * not allowing a race condition of adding transactions that are not for - * the current block height. + * not allowing a race condition of adding transactions that are not for the current block height. * @param tx The transaction to add. * @return `true` if the transaction was added, `false` if invalid otherwise. */ - bool addValidatorTx(const TxValidator& tx); + TxStatus addValidatorTx(const TxValidator& tx); /** * Parse a Validator transaction list. @@ -206,99 +154,13 @@ class rdPoS : public BaseContract { static TxValidatorFunction getTxValidatorFunction(const TxValidator& tx); /** - * Check if a block can be created by rdPoSWorker. - * @return `true` if a block can be created, `false` otherwise. - */ - const std::atomic& canCreateBlock() const; - - /// Start the rdPoSWorker. - void startrdPoSWorker(); - - /// Stop the rdPoSWorker. - void stoprdPoSWorker(); - - /// Worker class is a friend. - friend rdPoSWorker; -}; - -/** - * Worker class for rdPoS. - * This separates the class from the %rdPoS operation which runs the %rdPoS consensus. - */ -class rdPoSWorker { - private: - /// Reference to the parent rdPoS object. - rdPoS& rdpos_; - - /// Flag for stopping the worker thread. - std::atomic stopWorker_ = false; - - /** - * Future object for the worker thread. - * Used to wait for the thread to finish after stopWorker_ is set to true. - */ - std::future workerFuture_; - - /// Flag for knowing if the worker is ready to create a block. - std::atomic canCreateBlock_ = false; - - /// Pointer to the latest block. - std::shared_ptr latestBlock_; - - /** - * Check if the latest block has updated. - * Does NOT update latestBlock per se, this is done by workerLoop(). - * @return `true` if latest block has been updated, `false` otherwise. - */ - bool checkLatestBlock(); - - /** - * Entry function for the worker thread (runs the workerLoop() function). - * TODO: document return - */ - bool workerLoop(); - - /** - * Wait for transactions to be added to the mempool and create a block by rdPoS consesus. - * Called by workerLoop(). - * TODO: this function should call State or Blockchain to let them know that we are ready to create a block. - */ - void doBlockCreation(); - - /** - * Create a transaction by rdPoS consensus and broadcast it to the network. - * @param nHeight The block height for the transaction. - * @param me The Validator that will create the transaction. - */ - void doTxCreation(const uint64_t& nHeight, const Validator& me); - - public: - /** - * Constructor. - * @param rdpos Reference to the parent rdPoS object. - */ - explicit rdPoSWorker(rdPoS& rdpos) : rdpos_(rdpos) {} - - /** - * Destructor. - * Automatically stops the worker thread if it's still running. - */ - ~rdPoSWorker() { this->stop(); } - - /// Getter for `canCreateBlock_`. - const std::atomic& getCanCreateBlock() const { return this->canCreateBlock_; } - - /// Setter for `canCreateBlock_`. - void blockCreated() { this->canCreateBlock_ = false; } - - /** - * Start workerFuture_ and workerLoop. - * Should only be called after node is synced. + * Clear the mempool + * Used by tests */ - void start(); + void clearMempool() { this->validatorMempool_.clear(); } - /// Stop workerFuture_ and workerLoop. - void stop(); + /// Dump overriden function. + DBBatch dump() const override; }; #endif // RDPOS_H diff --git a/src/core/snowmanVM.cpp b/src/core/snowmanVM.cpp deleted file mode 100644 index d8f3ad0b..00000000 --- a/src/core/snowmanVM.cpp +++ /dev/null @@ -1,373 +0,0 @@ -/* -Copyright (c) [2023-2024] [Sparq Network] - -This software is distributed under the MIT License. -See the LICENSE.txt file in the project root for more information. -*/ - -#include "snowmanVM.h" - -void SnowmanVM::initialize( - const vm::InitializeRequest* request, vm::InitializeResponse* reply -) { - // TODO: this->initialized does not exist here - //if (this->initialized) { - // Utils::logToDebug(Log::snowmanVM, __func__, "Already initialized"); - // throw std::runtime_error(std::string(__func__) + ": " + std::string("Already initialized")); - //} - //this->initialized = true; - - // Get the required init params from AvalancheGo - std::string jsonRequest; - google::protobuf::util::JsonOptions options; - google::protobuf::util::MessageToJsonString(*request, &jsonRequest, options); - this->initParams_.networkId = request->network_id(); - this->initParams_.subnetId = request->subnet_id(); - this->initParams_.chainId = request->chain_id(); - this->initParams_.nodeId = request->node_id(); - this->initParams_.xChainId = request->x_chain_id(); - this->initParams_.avaxAssetId = request->avax_asset_id(); - this->initParams_.genesisBytes = request->genesis_bytes(); - this->initParams_.upgradeBytes = request->upgrade_bytes(); - this->initParams_.configBytes = request->config_bytes(); - for (int i = 0; i < request->db_servers_size(); i++) { - auto db_server = request->db_servers(i); - this->initParams_.dbServers.emplace_back(db_server.server_addr(), db_server.version()); - } - this->initParams_.gRPCServerAddress = request->server_addr(); - - // Initialize pointers to other parts of the program - //this->db = std::make_shared(this->initParams_.nodeId); // TODO: DB pointer does not exist here - //this->state = std::make_shared(this->db); // TODO: State pointer does not exist here - - // Read the config file and parse the latest block to answer AvalancheGo - json config = Utils::readConfigFile(); - const std::shared_ptr latest = this->storage_->latest(); - reply->set_last_accepted_id(latest->getBlockHash().get()); - reply->set_last_accepted_parent_id(latest->getPrevBlockHash().get()); - reply->set_height(latest->getNHeight()); - reply->set_bytes(latest->serializeToBytes(false)); - auto timestamp = reply->mutable_timestamp(); - timestamp->set_seconds(latest->getTimestamp() / 1000000000); - timestamp->set_nanos(latest->getTimestamp() % 1000000000); - - // Initialize P2P, rdPoS and HTTP server - // TODO: P2P pointer does not exist here - //this->p2p = std::make_shared( - // boost::asio::ip::address::from_string("127.0.0.1"), - // config["p2pport"].get(), 2, this->storage_, *this - //); - //this->p2p->startServer(); - std::this_thread::sleep_for(std::chrono::seconds(2)); - for (auto i : config["seedNodes"]) { - std::vector seedNode; - boost::split(seedNode, i.get(), boost::is_any_of(":")); - //this->p2p->connectToServer( - // boost::asio::ip::address::from_string(seedNode[0]), std::stoi(seedNode[1]) - //); - } - - // TODO: rdPoS pointer does not exist here - //this->rdpos = std::make_shared( - // this->db, this->storage_, this->p2p, - // ContractAddresses::BlockManager, - // Address("0x0000000000000000000000000000000000000000", true), - // (config.contains("validatorPrivKey")) - // ? Hash(Utils::hexToBytes(config["validatorPrivKey"].get())) : "" - //); - - // TODO: HTTP pointer does not exist here - //this->http = std::make_unique(*this, config["rpcport"].get()); - //std::thread httpThread = std::thread([&]{this->http->run();}); - //httpThread.detach(); - std::string jsonReply; - google::protobuf::util::MessageToJsonString(*reply, &jsonReply, options); - Utils::logToFile(jsonReply); -} - -bool SnowmanVM::parseBlock( - ServerContext* context, const std::string& blockBytes, vm::ParseBlockResponse* reply -) { - try { - // Check if block already exists on chain head or chain tip - std::shared_ptr block = std::make_shared(blockBytes, false); - Hash hash = block->getBlockHash(); - bool onStorage = this->storage_->exists(hash); - bool onMempool = this->blockExists(hash); - if (onStorage || onMempool) { - const std::shared_ptr block = (onStorage) - ? this->storage_->getBlock(hash) : this->getBlock(hash); - reply->set_id(block->getBlockHash().get()); - reply->set_parent_id(block->getPrevBlockHash().get()); - reply->set_status(BlockStatus::Accepted); - reply->set_height(block->getNHeight()); - auto timestamp = reply->mutable_timestamp(); - timestamp->set_seconds(block->getTimestamp() / 1000000000); - timestamp->set_nanos(block->getTimestamp() % 1000000000); - Utils::logToDebug(Log::snowmanVM, __func__, - std::string("Block ") + std::to_string(block->getNHeight()) - + "already exists, returning Accepted" - ); - return true; - } - - // Build block, parse it and get latest accepted as reference, by AvalancheGo's process: - // https://github.com/ava-labs/avalanchego/blob/master/vms/README.md#processing-blocks - const std::shared_ptr latest = this->storage_->latest(); - reply->set_id(block->getBlockHash().get()); - reply->set_parent_id(block->getPrevBlockHash().get()); - reply->set_height(block->getNHeight()); - if (block->getNHeight() <= latest->getNHeight()) { - reply->set_status(BlockStatus::Rejected); - Utils::logToDebug(Log::snowmanVM, __func__, - std::string("Block: ") + Hex::fromBytes(block->getBlockHash().get()).get() - + "(" + std::to_string(block->getNHeight()) + ") is lower than latest (" - + std::to_string(latest->getNHeight()) + "), returning Rejected" - ); - } else if (block->getNHeight() > latest->getNHeight()) { - // We don't know anything about a future block, so we just say we are processing it - reply->set_status(BlockStatus::Processing); - Utils::logToDebug(Log::snowmanVM, __func__, - std::string("Block: ") + Hex::fromBytes(block->getBlockHash().get()).get() - + "(" + std::to_string(block->getNHeight()) + ") is higher than latest (" - + std::to_string(latest->getNHeight()) + "), returning Processing" - ); - //this->rdpos->processBlock(block); // TODO: rdPoS pointer doesn't exist here - } - auto timestamp = reply->mutable_timestamp(); - timestamp->set_seconds(block->getTimestamp() / 1000000000); - timestamp->set_nanos(block->getTimestamp() % 1000000000); - Utils::logToDebug(Log::snowmanVM, __func__, "Block is valid"); - } catch (std::exception &e) { - Utils::logToDebug(Log::snowmanVM, __func__, - std::string("Error parsing block") + e.what() - ); - return false; - } - return true; -} - -void SnowmanVM::setState(const vm::SetStateRequest* request, vm::SetStateResponse* reply) { - Utils::logToDebug(Log::snowmanVM, __func__, - std::string("Setting State to: ") + std::to_string(request->state()) - ); - // TODO: rdPoS pointer does not exist here - //if (request->state() == 3) this->rdpos->startValidatorThread(); // NormalOp - const std::shared_ptr bestBlock = this->storage_->latest(); - reply->set_last_accepted_id(bestBlock->getBlockHash().get()); - reply->set_last_accepted_parent_id(bestBlock->getPrevBlockHash().get()); - reply->set_height(bestBlock->getNHeight()); - reply->set_bytes(bestBlock->serializeToBytes(false)); - auto timestamp = reply->mutable_timestamp(); - timestamp->set_seconds(bestBlock->getTimestamp() / 1000000000); - timestamp->set_nanos(bestBlock->getTimestamp() % 1000000000); -} - -bool SnowmanVM::blockRequest(ServerContext* context, vm::BuildBlockResponse* reply) { - //std::shared_ptr newBlock = this->state->createNewBlock(); // TODO: State pointer doesn't exist here - std::shared_ptr newBlock = nullptr; - if (newBlock == nullptr) { - Utils::logToDebug(Log::snowmanVM, __func__, "Could not create new block"); - return false; - } - Utils::logToDebug(Log::snowmanVM, __func__, "Trying to answer AvalancheGo"); - Utils::logToDebug(Log::snowmanVM, __func__, std::string("New block created: ") - + Hex::fromBytes(newBlock->getBlockHash().get()).get() - ); - reply->set_id(newBlock->getBlockHash().get()); - reply->set_parent_id(newBlock->getPrevBlockHash().get()); - reply->set_height(newBlock->getNHeight()); - reply->set_bytes(newBlock->serializeToBytes(false)); - auto timestamp = reply->mutable_timestamp(); - timestamp->set_seconds(newBlock->getTimestamp() / 1000000000); - timestamp->set_nanos(newBlock->getTimestamp() % 1000000000); - Utils::logToDebug(Log::snowmanVM, __func__, "New block broadcast but not enforced"); - return true; -} - -void SnowmanVM::getBlock( - ServerContext* context, const vm::GetBlockRequest* request, vm::GetBlockResponse* reply -) { - Hash hash(request->id()); - if (this->storage_->exists(hash)) { - const std::shared_ptr block = this->storage_->getBlock(hash); - reply->set_parent_id(block->getPrevBlockHash().get()); - reply->set_bytes(block->serializeToBytes(false)); - reply->set_status(BlockStatus::Accepted); - reply->set_height(block->getNHeight()); - auto timestamp = reply->mutable_timestamp(); - timestamp->set_seconds(block->getTimestamp() / 1000000000); - timestamp->set_nanos(block->getTimestamp() % 1000000000); - Utils::logToDebug(Log::snowmanVM, __func__, - "Block found in chain: " + Hex::fromBytes(block->serializeToBytes(false)).get() - ); - } else if (this->blockExists(hash)) { - auto block = this->getBlock(hash); - reply->set_parent_id(block->getPrevBlockHash().get()); - reply->set_bytes(block->serializeToBytes(false)); - reply->set_status(this->getBlockStatus(hash)); - reply->set_height(block->getNHeight()); - auto timestamp = reply->mutable_timestamp(); - timestamp->set_seconds(block->getTimestamp() / 1000000000); - timestamp->set_nanos(block->getTimestamp() % 1000000000); - Utils::logToDebug(Log::snowmanVM, __func__, - "Block found in mempool: " + Hex::fromBytes(block->serializeToBytes(false)).get() - ); - } else { - reply->set_status(BlockStatus::Unknown); - reply->set_err(2); // https://github.com/ava-labs/avalanchego/blob/559ce151a6b6f28d8115e0189627d8deaf00d9fb/vms/rpcchainvm/errors.go#L21 - Utils::logToDebug(Log::snowmanVM, __func__, - "Block " + Hex::fromBytes(request->id()).get() + " does not exist" - ); - } -} - -bool SnowmanVM::getAncestors( - ServerContext* context, const vm::GetAncestorsRequest* request, vm::GetAncestorsResponse* reply -) { - Hash hash(request->blk_id()); - if (!this->storage_->exists(hash)) return false; - Utils::logToDebug(Log::snowmanVM, __func__, - std::string("Getting ancestors of block ") + Hex::fromBytes(hash.get()).get() - + " with depth of " + std::to_string(request->max_blocks_num()) + " up to " - + std::to_string(request->max_blocks_size()) + " bytes and/or for " - + std::to_string(request->max_blocks_retrival_time()) + " nanosseconds" - ); - const std::shared_ptr head = this->storage_->getBlock(hash); - const std::shared_ptr best = this->storage_->latest(); - uint64_t depth = request->max_blocks_num(); - uint64_t maxSize = request->max_blocks_size(); // In bytes - uint64_t maxTime = request->max_blocks_retrival_time(); // In nanosseconds - - // Depth can be actually higher than chain height, so we set it to chain height - if (depth > best->getNHeight()) { - Utils::logToDebug(Log::snowmanVM, __func__, - "Depth is higher than chain height, setting depth to chain height" - ); - depth = best->getNHeight(); - } - auto timeStart = std::chrono::system_clock::now(); - for ( - uint64_t index = (head->getNHeight()); - index >= (head->getNHeight() - depth) && index <= head->getNHeight(); - index-- - ) { - const std::shared_ptr block = this->storage_->getBlock(index); - reply->add_blks_bytes(block->serializeToBytes(false)); - auto timeEnd = std::chrono::system_clock::now(); - auto timeDiff = std::chrono::duration_cast(timeEnd - timeStart).count(); - if (reply->blks_bytes().size() > maxSize || timeDiff > maxTime) { - Utils::logToDebug(Log::snowmanVM, __func__, "Max block byte size reached or time ran out"); - return false; - } - } - Utils::logToDebug(Log::snowmanVM, __func__, "Ancestors found, replying back"); - return true; -} - -void SnowmanVM::setPreference(ServerContext* context, const vm::SetPreferenceRequest* request) { - this->setPreferredBlockHash(Hash(request->id())); -} - -const BlockStatus SnowmanVM::getBlockStatus(const Hash& hash) { - BlockStatus ret = BlockStatus::Unknown; - this->lock_.lock(); - if (this->cachedBlockStatus_.count(hash) > 0) { - ret = this->cachedBlockStatus_.find(hash)->second; - } - this->lock_.unlock(); - return ret; -} - -void SnowmanVM::setBlockStatus(const Hash& hash, const BlockStatus& status) { - this->lock_.lock(); - this->cachedBlockStatus_[hash] = status; - this->lock_.unlock(); -} - -const std::shared_ptr SnowmanVM::verifyBlock(const std::string bytes) { - // Check if block can be attached to top of the chain, if so add it to processing - const std::shared_ptr block = std::make_shared(bytes, false); - //if (!this->state->validateNewBlock(block)) return nullptr; // TODO: State pointer doesn't exist here - //this->rdpos->processBlock(block); // TODO: rdPoS pointer doesn't exist here - return this->getBlock(block->getBlockHash()); -} - -bool SnowmanVM::acceptBlock(const Hash& hash) { - this->lock_.lock(); - auto it = this->mempool_.find(hash); - if (it == this->mempool_.end()) { - Utils::logToDebug(Log::snowmanVM, __func__, "Block not found"); - this->lock_.unlock(); - return false; - } - if (it->second.unique()) { - Utils::logToDebug(Log::snowmanVM, __func__, "Block is unique, moving to processNewBlock"); - //this->state->processNewBlock(std::move(it->second)); // TODO: State pointer doesn't exist here - this->mempool_.erase(hash); - } else { - // We have to create a copy tof the block to process it - Utils::logToDebug(Log::snowmanVM, __func__, "Block is not unique, copying to processNewBlock"); - std::shared_ptr block = std::make_shared(*it->second); - //this->state->processNewBlock(std::move(block)); // TODO: State pointer doesn't exist here - } - this->cachedBlockStatus_[hash] = BlockStatus::Accepted; - this->lock_.unlock(); - return true; -} - -void SnowmanVM::rejectBlock(const Hash& hash) { - this->lock_.lock(); - this->mempool_.erase(hash); - this->cachedBlockStatus_[hash] = BlockStatus::Rejected; - this->lock_.unlock(); -} - -bool SnowmanVM::blockExists(const Hash& hash) { - this->lock_.lock(); - bool ret = (this->mempool_.count(hash) > 0); - this->lock_.unlock(); - return ret; -} - -bool SnowmanVM::blockIsProcessing(const Hash& hash) { - bool ret = false; - this->lock_.lock(); - if (this->cachedBlockStatus_.count(hash) > 0) { - ret = (this->cachedBlockStatus_.find(hash)->second == BlockStatus::Processing); - } - this->lock_.unlock(); - return ret; -} - -const std::shared_ptr SnowmanVM::getBlock(const Hash& hash) { - this->lock_.lock(); - auto it = this->mempool_.find(hash); - std::shared_ptr ret = (it != this->mempool_.end()) ? it->second : nullptr; - this->lock_.unlock(); - return ret; -} - -void SnowmanVM::connectNode(const std::string& id) { - this->connectedNodesLock_.lock(); - Utils::logToDebug(Log::snowmanVM, __func__, - "Connecting node: " + Hex::fromBytes(id).get() - ); - this->connectedNodes_.emplace_back(id); - this->connectedNodesLock_.unlock(); -} - -void SnowmanVM::disconnectNode(const std::string& id) { - this->connectedNodesLock_.lock(); - for (uint64_t i = 0; i < this->connectedNodes_.size(); i++) { - if (this->connectedNodes_[i] == id) { - Utils::logToDebug(Log::snowmanVM, __func__, - "Disconnecting node: " + Hex::fromBytes(id).get() - ); - this->connectedNodes_.erase(this->connectedNodes_.begin() + i); - break; - } - } - this->connectedNodesLock_.unlock(); -} - diff --git a/src/core/snowmanVM.h b/src/core/snowmanVM.h deleted file mode 100644 index d677fc9e..00000000 --- a/src/core/snowmanVM.h +++ /dev/null @@ -1,234 +0,0 @@ -/* -Copyright (c) [2023-2024] [Sparq Network] - -This software is distributed under the MIT License. -See the LICENSE.txt file in the project root for more information. -*/ - -#ifndef SNOWMANVM_H -#define SNOWMANVM_H - -#include -#include -#include - -#include "../utils/block.h" -#include "../utils/db.h" -#include "../utils/utils.h" - -#include "../net/grpcclient.h" -#include "../net/grpcserver.h" - -#include "storage.h" - -using grpc::ServerContext; - -// Forward declarations. -class Block; - -/** - * Internal struct that contains data for initializing the SnowmanVM. - * TODO: Ava Labs never answered us on what some of those do. - */ -struct InitializeRequest { - uint32_t networkId; ///< ID of the network to connect to. - std::string subnetId; ///< ID of the subnet to connect to. - std::string chainId; ///< ID of the chain to connect to. - std::string nodeId; ///< ID of the node to connect to. - std::string xChainId; ///< See todo. - std::string avaxAssetId; ///< See todo. - std::string genesisBytes; ///< See todo. - std::string upgradeBytes; ///< See todo. - std::string configBytes; ///< See todo. - std::vector dbServers; ///< Array of database server to connect to. - std::string gRPCServerAddress; ///< gRPC server address to connect to. -}; - -/// Enum for block status. -enum BlockStatus { Unknown, Processing, Rejected, Accepted }; - -/** - * Abstraction of AvalancheGo's %SnowmanVM protocol. - * See [Ava Labs' docs](https://github.com/ava-labs/avalanchego/tree/master/vms#readme) for more details. - */ -class SnowmanVM { - private: - /// Struct with initialization request data from AvalancheGo. - InitializeRequest initParams_; - - /// List of all connected nodes. - std::vector connectedNodes_; - - /// Mutex for managing read/write access to connectedNodes_. - std::mutex connectedNodesLock_; - - /// Preferred block hash. Set by SetPreference from gRPC. - Hash preferredBlockHash_; - - /// mempool for blocks from AvalancheGo's network. Lookup is made by block hash. - std::unordered_map, SafeHash> mempool_; - - /// Cached block status. Lookup is made by block hash. - std::unordered_map cachedBlockStatus_; - - /// Mutex for managing read/write access to the class members. - mutable std::mutex lock_; - - /// Pointer to the blockchain history. - const std::shared_ptr storage_; - - /// Pointer to the gRPC server. - const std::shared_ptr grpcServer_; - - /// Pointer to the gRPC client. - const std::shared_ptr grpcClient_; - - public: - /** - * Constructor. - * @param storage Pointer to the blockchain history. - * @param grpcServer Pointer to the gRPC server. - * @param grpcClient Pointer to the gRPC client. - */ - SnowmanVM( - const std::shared_ptr& storage, - const std::shared_ptr& grpcServer, - const std::shared_ptr& grpcClient - ) : storage_(storage), grpcServer_(grpcServer), grpcClient_(grpcClient) {} - - /// Getter for `preferredBlockHash_`. - const Hash& getpreferredBlockHash_() { return this->preferredBlockHash_; } - - /// Setter for `preferredBlockHash_`. - void setpreferredBlockHash_(const Hash& hash) { this->preferredBlockHash_ = hash; } - - /** - * Initialize the %SnowmanVM services. - * Called by gRPCServer. - * The initialization request is made by the AvalancheGo Daemon. - * See vm.proto for more information. - * Throws if called when the services are already initialized. - */ - void initialize( - const vm::InitializeRequest* request, vm::InitializeResponse* reply - ); - - /** - * Parse a given block and push it to the blockchain if required. - * Called by gRPCClient. - * @return `true` if the block was parsed successfully, `false` otherwise. - */ - bool parseBlock( - ServerContext* context, const std::string& blockBytes, vm::ParseBlockResponse* reply - ); - - /** - * Set the state of the %SnowmanVM. - * Called by `initialize()` if no info is found on the database. - * For more info about the SetState request, see vm.proto and - * https://github.com/ava-labs/avalanchego/blob/master/snow/engine/snowman/bootstrap/bootstrapper.go#L111 - */ - void setState(const vm::SetStateRequest* request, vm::SetStateResponse* reply); - - /** - * Request a block to be created. - * Called by gRPCServer. - * @return `true` if request is successful, `false` otherwise. - */ - bool blockRequest(ServerContext* context, vm::BuildBlockResponse* reply); - - /** - * Get a block that was requested. - * Called by gRPCServer. - */ - void getBlock( - ServerContext* context, const vm::GetBlockRequest* request, vm::GetBlockResponse* reply - ); - - /** - * Get the ancestors of a block. - * Called by gRPCServer. - * @return `true` on success, `false` on failure. - */ - bool getAncestors( - ServerContext* context, const vm::GetAncestorsRequest* request, vm::GetAncestorsResponse* reply - ); - - /** - * Set the preferred block for acceptance/continuing the chain. - * Called by gRPCServer. - */ - void setPreference(ServerContext* context, const vm::SetPreferenceRequest* request); - - /** - * Get a block's status in the mempool_. - * @param hash The block hash to get the status from. - * @return The current block's status. - */ - const BlockStatus getBlockStatus(const Hash& hash); - - /** - * Set a block's status in the mempool_. - * @param hash The block hash to set the status to. - * @param status The status to set. - */ - void setBlockStatus(const Hash& hash, const BlockStatus& status); - - /** - * Request a block to be verified. - * Called by gRPCServer. - * @param bytes The raw block bytes. - * @return A pointer to the block, or `nullptr` in case of error. - */ - const std::shared_ptr verifyBlock(const std::string bytes); - - /** - * Accept a block. - * Called by gRPCServer. - * @param hash The block hash to accept. - * @return `true` if block was accepted, `false` otherwise. - */ - bool acceptBlock(const Hash& hash); - - /** - * Reject a block. - * Called by gRPCServer. - * @param hash The block hash to reject. - */ - void rejectBlock(const Hash& hash); - - /** - * Check if a block exists in the mempool_. - * @param hash The block hash to check. - * @return `true` if the block exists, `false` otherwise. - */ - bool blockExists(const Hash& hash); - - /** - * Check if a block has the "Processing" status (no consensus reached yet). - * @param hash The block hash to check. - * @return `true` if block is processing, `false` otherwise. - */ - bool blockIsProcessing(const Hash& hash); - - /** - * Get a block from the mempool_ by its hash. - * @param hash The block hash to get. - * @return The found block, or `nullptr` if block is not found. - */ - const std::shared_ptr getBlock(const Hash& hash); - - /** - * Connect to a given node. - * @param id The node's 20-byte hex ID. - */ - void connectNode(const std::string& id); - - /** - * Disconnect from a given node. - * @param id The node's 20-byte hex ID. - */ - void disconnectNode(const std::string& id); -}; - -#endif // SNOWMANVM_H diff --git a/src/core/state.cpp b/src/core/state.cpp index 596c6a30..1b326d75 100644 --- a/src/core/state.cpp +++ b/src/core/state.cpp @@ -1,168 +1,309 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. */ +#include + #include "state.h" +#include "../contract/contracthost.h" // contractmanager.h + +#include "../utils/uintconv.h" +#include "bytes/random.h" +#include "../net/http/jsonrpc/error.h" + State::State( - const std::unique_ptr& db, - const std::unique_ptr& storage, - const std::unique_ptr& rdpos, - const std::unique_ptr& p2pManager, - const std::unique_ptr& options -) : db_(db), storage_(storage), rdpos_(rdpos), p2pManager_(p2pManager), options_(options), -contractManager_(std::make_unique(db, this, rdpos, options)) + const DB& db, + Storage& storage, + P2P::ManagerNormal& p2pManager, + const uint64_t& snapshotHeight, + const Options& options +) : vm_(evmc_create_evmone()), + options_(options), + storage_(storage), + dumpManager_(storage_, options_, this->stateMutex_), + dumpWorker_(options_, storage_, dumpManager_), + p2pManager_(p2pManager), + rdpos_(db, dumpManager_, storage, p2pManager, options), + blockObservers_(vm_, dumpManager_, storage_, contracts_, accounts_, vmStorage_, options_) { std::unique_lock lock(this->stateMutex_); - auto accountsFromDB = db->getBatch(DBPrefix::nativeAccounts); - if (accountsFromDB.empty()) { - for (const auto& [account, balance] : options_->getGenesisBalances()) { - // Initialize all accounts within options genesis balances. - Bytes value = Utils::uintToBytes(Utils::bytesRequired(balance)); - Utils::appendBytes(value,Utils::uintToBytes(balance)); - value.insert(value.end(), 0x00); - db->put(account.get(), value, DBPrefix::nativeAccounts); - } - accountsFromDB = db->getBatch(DBPrefix::nativeAccounts); + if (snapshotHeight != 0) { + Utils::safePrint("Loading state from snapshot height: " + std::to_string(snapshotHeight)); } + auto now = std::chrono::system_clock::now(); - for (const auto& dbEntry : accountsFromDB) { - BytesArrView data(dbEntry.value); - if (dbEntry.key.size() != 20) { - Logger::logToDebug(LogType::ERROR, Log::state, __func__, "Error when loading State from DB, address from DB size mismatch"); - throw std::runtime_error("Error when loading State from DB, address from DB size mismatch"); + if (auto accountsFromDB = db.getBatch(DBPrefix::nativeAccounts); accountsFromDB.empty()) { + if (snapshotHeight != 0) { + throw DynamicException("Snapshot height is higher than 0, but no accounts found in DB"); } - uint8_t balanceSize = Utils::fromBigEndian(data.subspan(0,1)); - if (data.size() + 1 < data.size()) { - Logger::logToDebug(LogType::ERROR, Log::state, __func__, "Error when loading State from DB, value from DB doesn't size mismatch on balanceSize"); - throw std::runtime_error("Error when loading State from DB, value from DB size mismatch on balanceSize"); + for (const auto& [addr, balance] : options_.getGenesisBalances()) { + this->accounts_[addr]->balance = balance; } + // Also append the ContractManager account + auto& contractManagerAcc = *this->accounts_[ProtocolContractAddresses.at("ContractManager")]; + contractManagerAcc.nonce = 1; + contractManagerAcc.contractType = ContractType::CPP; + } else { + for (const auto& dbEntry : accountsFromDB) { + this->accounts_.emplace(Address(dbEntry.key), dbEntry.value); + } + } + + // Load all the EVM Storage Slot/keys from the DB + for (const auto& dbEntry : db.getBatch(DBPrefix::vmStorage)) { + Address addr(dbEntry.key | std::views::take(ADDRESS_SIZE)); + Hash hash(dbEntry.key | std::views::drop(ADDRESS_SIZE)); + + this->vmStorage_.emplace( + StorageKeyView(addr, hash), + dbEntry.value); + } + + auto latestBlock = this->storage_.latest(); + auto snapshotBlock = this->storage_.getBlock(snapshotHeight); + if (snapshotBlock == nullptr) { + throw DynamicException("Snapshot block not found!?"); + } + ContractGlobals::coinbase_ = Secp256k1::toAddress(snapshotBlock->getValidatorPubKey()); + ContractGlobals::blockHash_ = snapshotBlock->getHash(); + ContractGlobals::blockHeight_ = snapshotBlock->getNHeight(); + ContractGlobals::blockTimestamp_ = snapshotBlock->getTimestamp(); + // Insert the contract manager into the contracts_ map. + this->contracts_[ProtocolContractAddresses.at("ContractManager")] = std::make_unique( + db, this->contracts_, this->dumpManager_ ,this->blockObservers_, this->options_ + ); - uint256_t balance = Utils::fromBigEndian(data.subspan(1, balanceSize)); - uint8_t nonceSize = Utils::fromBigEndian(data.subspan(1 + balanceSize, 1)); + // State sanity check, lets check if all found contracts in the accounts_ map really have code or are C++ contracts + for (const auto& [addr, acc] : this->accounts_) contractSanityCheck(addr, *acc); - if (2 + balanceSize + nonceSize != data.size()) { - Logger::logToDebug(LogType::ERROR, Log::state, __func__, "Error when loading State from DB, value from DB doesn't size mismatch on nonceSize"); - throw std::runtime_error("Error when loading State from DB, value from DB size mismatch on nonceSize"); + if (snapshotHeight > this->storage_.latest()->getNHeight()) { + LOGERROR("Snapshot height is higher than latest block, we can't load State! Crashing the program"); + throw DynamicException("Snapshot height is higher than latest block, we can't load State!"); + } + + // For each nHeight from snapshotHeight + 1 to latestBlock->getNHeight() + // We need to process the block and update the state + // We can't call processNextBlock here, as it will place the block again on the storage + Utils::safePrint("Got latest block height: " + std::to_string(latestBlock->getNHeight())); + std::unique_ptr reindexedTxs = std::make_unique(); + for (uint64_t nHeight = snapshotHeight + 1; nHeight <= latestBlock->getNHeight(); nHeight++) { + auto block = this->storage_.getBlock(nHeight); + ContractGlobals::coinbase_ = Secp256k1::toAddress(block->getValidatorPubKey()); + ContractGlobals::blockHash_ = block->getHash(); + ContractGlobals::blockHeight_ = block->getNHeight(); + ContractGlobals::blockTimestamp_ = block->getTimestamp(); + if (this->options_.getIndexingMode() == IndexingMode::RPC || this->options_.getIndexingMode() == IndexingMode::RPC_TRACE) { + this->storage_.reindexTransactions(*block, *reindexedTxs); + } + if (reindexedTxs->getPuts().size() > 25000) { + this->storage_.dumpToDisk(*reindexedTxs); + reindexedTxs = std::make_unique(); + LOGINFOP("Reindexing transactions at block " + block->getHash().hex().get() + " at height " + std::to_string(nHeight)); } - uint64_t nonce = Utils::fromBigEndian(data.subspan(2 + balanceSize, nonceSize)); + LOGINFOP("Processing block " + block->getHash().hex().get() + " at height " + std::to_string(nHeight)); + // Update contract globals based on (now) latest block + const Hash blockHash = block->getHash(); + ContractGlobals::coinbase_ = Secp256k1::toAddress(block->getValidatorPubKey()); + ContractGlobals::blockHash_ = blockHash; + ContractGlobals::blockHeight_ = block->getNHeight(); + ContractGlobals::blockTimestamp_ = block->getTimestamp(); + + // Process transactions of the block within the current state + uint64_t txIndex = 0; + for (const auto& tx : block->getTxs()) { + this->processTransaction(tx, blockHash, txIndex, block->getBlockRandomness()); + txIndex++; + } + blockObservers_.notify(*block); + // Process rdPoS State + this->rdpos_.processBlock(*block); + } + if (reindexedTxs->getPuts().size() > 0) { + LOGINFOP("Reindexing remaining transactions"); + this->storage_.dumpToDisk(*reindexedTxs); + } + this->dumpManager_.pushBack(this); + auto end = std::chrono::system_clock::now(); + double elapsed = std::chrono::duration_cast(end - now).count(); + double elapsedInSeconds = elapsed / 1000; + Utils::safePrint("State loaded in " + std::to_string(elapsedInSeconds) + " seconds"); - this->accounts_.insert({Address(dbEntry.key), Account(std::move(balance), std::move(nonce))}); +} + +State::~State() { evmc_destroy(this->vm_); } + +void State::contractSanityCheck(const Address& addr, const Account& acc) { + switch (acc.contractType) { + case ContractType::CPP: { + if (this->contracts_.find(addr) == this->contracts_.end()) { + LOGERROR("Contract " + addr.hex().get() + " is marked as C++ contract but doesn't have code"); + throw DynamicException("Contract " + addr.hex().get() + " is marked as C++ contract but doesn't have code"); + } + break; + } + case ContractType::EVM: { + if (acc.code.empty()) { + LOGERROR("Contract " + addr.hex().get() + " is marked as EVM contract but doesn't have code"); + throw DynamicException("Contract " + addr.hex().get() + " is marked as EVM contract but doesn't have code"); + } + break; + } + case ContractType::NOT_A_CONTRACT: { + if (!acc.code.empty()) { + LOGERROR("Contract " + addr.hex().get() + " is marked as not a contract but has code"); + throw DynamicException("Contract " + addr.hex().get() + " is marked as not a contract but has code"); + } + break; + } + default: + // TODO: this is a placeholder, contract types should be revised. + // Also we can NOT remove NOT_A_CONTRACT for now because tests will complain about it. + throw DynamicException("Invalid contract type"); + break; } - auto latestBlock = this->storage_->latest(); - this->contractManager_->updateContractGlobals(Secp256k1::toAddress(latestBlock->getValidatorPubKey()), latestBlock->hash(), latestBlock->getNHeight(), latestBlock->getTimestamp()); } -State::~State() { +DBBatch State::dump() const { // DB is stored as following // Under the DBPrefix::nativeAccounts // Each key == Address - // Each Value == Balance + uint256_t (not exact bytes) - // Value == 1 Byte (Balance Size) + N Bytes (Balance) + 1 Byte (Nonce Size) + N Bytes (Nonce). - // Max size for Value = 32 Bytes, Max Size for Nonce = 8 Bytes. - // If the nonce equals to 0, it will be *empty* - DBBatch accountsBatch; - std::unique_lock lock(this->stateMutex_); + // Each Value == Account.serialize() + DBBatch stateBatch; for (const auto& [address, account] : this->accounts_) { - // Serialize Balance. - Bytes serializedBytes; - if (account.balance == 0) { - serializedBytes = Bytes(1, 0x00); - } else { - serializedBytes = Utils::uintToBytes(Utils::bytesRequired(account.balance)); - Utils::appendBytes(serializedBytes, Utils::uintToBytes(account.balance)); - } - // Serialize Account. - if (account.nonce == 0) { - Utils::appendBytes(serializedBytes, Bytes(1, 0x00)); - } else { - Utils::appendBytes(serializedBytes, Utils::uintToBytes(Utils::bytesRequired(account.nonce))); - Utils::appendBytes(serializedBytes, Utils::uintToBytes(account.nonce)); - } - accountsBatch.push_back(address.get(), serializedBytes, DBPrefix::nativeAccounts); + stateBatch.push_back(address, account->serialize(), DBPrefix::nativeAccounts); + } + // There is also the need to dump the vmStorage_ map + for (const auto& [storageKey, storageValue] : this->vmStorage_) { + const auto key = Utils::makeBytes(bytes::join(storageKey.first, storageKey.second)); + stateBatch.push_back(key, storageValue, DBPrefix::vmStorage); } - this->db_->putBatch(accountsBatch); + return stateBatch; } -TxInvalid State::validateTransactionInternal(const TxBlock& tx) const { +TxStatus State::validateTransactionInternal(const TxBlock& tx) const { /** * Rules for a transaction to be accepted within the current state: * Transaction value + txFee (gas * gasPrice) needs to be lower than account balance * Transaction nonce must match account nonce */ - /// Verify if transaction already exists within the mempool, if on mempool, it has been validated previously. + // Verify if transaction already exists within the mempool, if on mempool, it has been validated previously. if (this->mempool_.contains(tx.hash())) { - Logger::logToDebug(LogType::INFO, Log::state, __func__, "Transaction: " + tx.hash().hex().get() + " already in mempool"); - return TxInvalid::NotInvalid; + LOGTRACE("Transaction: " + tx.hash().hex().get() + " already in mempool"); + return TxStatus::ValidExisting; } auto accountIt = this->accounts_.find(tx.getFrom()); if (accountIt == this->accounts_.end()) { - Logger::logToDebug(LogType::ERROR, Log::state, __func__, "Account doesn't exist (0 balance and 0 nonce)"); - return TxInvalid::InvalidBalance; - } - const auto& accBalance = accountIt->second.balance; - const auto& accNonce = accountIt->second.nonce; - uint256_t txWithFees = tx.getValue() + (tx.getGasLimit() * tx.getMaxFeePerGas()); - if (txWithFees > accBalance) { - Logger::logToDebug(LogType::ERROR, Log::state, __func__, - "Transaction sender: " + tx.getFrom().hex().get() + " doesn't have balance to send transaction" - + " expected: " + txWithFees.str() + " has: " + accBalance.str()); - return TxInvalid::InvalidBalance; - } - // TODO: The blockchain is able to store higher nonce transactions until they are valid - // Handle this case. + LOGERROR("Account doesn't exist (0 balance and 0 nonce)"); + return TxStatus::InvalidBalance; + } + const auto& accBalance = accountIt->second->balance; + const auto& accNonce = accountIt->second->nonce; + if ( + uint256_t txWithFees = tx.getValue() + (tx.getGasLimit() * tx.getMaxFeePerGas()); + txWithFees > accBalance + ) { + LOGERROR("Transaction sender: " + tx.getFrom().hex().get() + + " doesn't have balance to send transaction" + + " expected: " + txWithFees.str() + " has: " + accBalance.str() + ); + return TxStatus::InvalidBalance; + } + // TODO: The blockchain is able to store higher nonce transactions until they are valid. Handle this case. if (accNonce != tx.getNonce()) { - Logger::logToDebug(LogType::ERROR, Log::state, __func__, "Transaction: " + tx.hash().hex().get() + " nonce mismatch, expected: " + std::to_string(accNonce) - + " got: " + tx.getNonce().str()); - return TxInvalid::InvalidNonce; + LOGERROR("Transaction: " + tx.hash().hex().get() + + " nonce mismatch, expected: " + std::to_string(accNonce) + + " got: " + tx.getNonce().str() + ); + return TxStatus::InvalidNonce; } - - - - return TxInvalid::NotInvalid; + return TxStatus::ValidNew; } -void State::processTransaction(const TxBlock& tx, const Hash& blockHash, const uint64_t& txIndex) { +void State::processTransaction( + const TxBlock& tx, const Hash& blockHash, const uint64_t& txIndex, const Hash& randomnessHash +) { // Lock is already called by processNextBlock. // processNextBlock already calls validateTransaction in every tx, // as it calls validateNextBlock as a sanity check. - auto accountIt = this->accounts_.find(tx.getFrom()); - auto& balance = accountIt->second.balance; - auto& nonce = accountIt->second.nonce; + Account& accountFrom = *this->accounts_[tx.getFrom()]; + auto& fromNonce = accountFrom.nonce; + auto& fromBalance = accountFrom.balance; + if (fromBalance < (tx.getValue() + tx.getGasLimit() * tx.getMaxFeePerGas())) { + LOGERROR("Transaction sender: " + tx.getFrom().hex().get() + " doesn't have balance to send transaction"); + throw DynamicException("Transaction sender doesn't have balance to send transaction"); + return; + } + if (fromNonce != tx.getNonce()) { + LOGERROR("Transaction: " + tx.hash().hex().get() + " nonce mismatch, expected: " + + std::to_string(fromNonce) + " got: " + tx.getNonce().str() + ); + throw DynamicException("Transaction nonce mismatch"); + return; + } + + Gas gas(uint64_t(tx.getGasLimit())); + TxAdditionalData txData{.hash = tx.hash()}; + try { - uint256_t txValueWithFees = tx.getValue() + ( - tx.getGasLimit() * tx.getMaxFeePerGas() - ); // This needs to change with payable contract functions - balance -= txValueWithFees; - this->accounts_[tx.getTo()].balance += tx.getValue(); - if (this->contractManager_->isContractCall(tx)) { - Utils::safePrint(std::string("Processing transaction call txid: ") + tx.hash().hex().get()); - if (this->contractManager_->isPayable(tx.txToCallInfo())) this->processingPayable_ = true; - this->contractManager_->callContract(tx, blockHash, txIndex); - this->processingPayable_ = false; - } + const Hash randomSeed(UintConv::uint256ToBytes((static_cast(randomnessHash) + txIndex))); + + ExecutionContext context = ExecutionContext::Builder{} + .storage(this->vmStorage_) + .accounts(this->accounts_) + .contracts(this->contracts_) + .blockHash(blockHash) + .txHash(tx.hash()) + .txOrigin(tx.getFrom()) + .blockCoinbase(ContractGlobals::getCoinbase()) + .txIndex(txIndex) + .blockNumber(ContractGlobals::getBlockHeight()) + .blockTimestamp(ContractGlobals::getBlockTimestamp()) + .blockGasLimit(10'000'000) + .txGasPrice(tx.getMaxFeePerGas()) + .chainId(this->options_.getChainID()) + .build(); + + ContractHost host( + this->vm_, + this->dumpManager_, + this->storage_, + randomSeed, + context, + &blockObservers_); + + std::visit([&] (auto&& msg) { + if constexpr (concepts::CreateMessage) { + txData.contractAddress = host.execute(std::forward(msg)); + } else { + host.execute(std::forward(msg)); + } + }, tx.toMessage(gas)); + + txData.succeeded = true; } catch (const std::exception& e) { - Logger::logToDebug(LogType::ERROR, Log::state, __func__, - "Transaction: " + tx.hash().hex().get() + " failed to process, reason: " + e.what() - ); - if(this->processingPayable_) { - balance += tx.getValue(); - this->accounts_[tx.getTo()].balance -= tx.getValue(); - this->processingPayable_ = false; - } - balance += tx.getValue(); + txData.succeeded = false; + LOGERRORP("Transaction: " + tx.hash().hex().get() + " failed to process, reason: " + e.what()); + } + + ++fromNonce; + txData.gasUsed = uint64_t(tx.getGasLimit() - uint256_t(gas)); + + if (storage_.getIndexingMode() != IndexingMode::DISABLED) { + storage_.putTxAdditionalData(txData); } - nonce++; + + fromBalance -= (txData.gasUsed * tx.getMaxFeePerGas()); } -void State::refreshMempool(const Block& block) { - /// No need to lock mutex as function caller (this->processNextBlock) already lock mutex. - /// Remove all transactions within the block that exists on the unordered_map. +void State::refreshMempool(const FinalizedBlock& block) { + // No need to lock mutex as function caller (this->processNextBlock) already lock mutex. + // Remove all transactions within the block that exists on the unordered_map. for (const auto& tx : block.getTxs()) { const auto it = this->mempool_.find(tx.hash()); if (it != this->mempool_.end()) { @@ -170,46 +311,45 @@ void State::refreshMempool(const Block& block) { } } - /// Copy mempool over + // Copy mempool over auto mempoolCopy = this->mempool_; this->mempool_.clear(); - /// Verify if the transactions within the old mempool - /// not added to the block are valid given the current state + // Verify if the transactions within the old mempool + // not added to the block are valid given the current state for (const auto& [hash, tx] : mempoolCopy) { - /// Calls internal function which doesn't lock mutex. - if (!this->validateTransactionInternal(tx)) { + // Calls internal function which doesn't lock mutex. + if (isTxStatusValid(this->validateTransactionInternal(tx))) { this->mempool_.insert({hash, tx}); } } } -const uint256_t State::getNativeBalance(const Address &addr) const { +uint256_t State::getNativeBalance(const Address &addr) const { std::shared_lock lock(this->stateMutex_); auto it = this->accounts_.find(addr); if (it == this->accounts_.end()) return 0; - return it->second.balance; + return it->second->balance; } - -const uint64_t State::getNativeNonce(const Address& addr) const { +uint64_t State::getNativeNonce(const Address& addr) const { std::shared_lock lock(this->stateMutex_); auto it = this->accounts_.find(addr); if (it == this->accounts_.end()) return 0; - return it->second.nonce; + return it->second->nonce; } -const std::unordered_map State::getAccounts() const { +std::vector State::getMempool() const { std::shared_lock lock(this->stateMutex_); - return this->accounts_; -} - -const std::unordered_map State::getMempool() const { - std::shared_lock lock(this->stateMutex_); - return this->mempool_; + std::vector mempoolCopy; + mempoolCopy.reserve(this->mempool_.size()); + for (const auto& [hash, tx] : this->mempool_) { + mempoolCopy.emplace_back(tx); + } + return mempoolCopy; } -bool State::validateNextBlock(const Block& block) const { +BlockValidationStatus State::validateNextBlockInternal(const FinalizedBlock& block) const { /** * Rules for a block to be accepted within the current state * Block nHeight must match latest nHeight + 1 @@ -219,103 +359,115 @@ bool State::validateNextBlock(const Block& block) const { * All transactions within Block are valid (does not return false on validateTransaction) * Block constructor already checks if merkle roots within a block are valid. */ - - auto latestBlock = this->storage_->latest(); + auto latestBlock = this->storage_.latest(); if (block.getNHeight() != latestBlock->getNHeight() + 1) { - Logger::logToDebug(LogType::ERROR, Log::state, __func__, "Block nHeight doesn't match, expected " - + std::to_string(latestBlock->getNHeight() + 1) + " got " + std::to_string(block.getNHeight())); - return false; + LOGERROR("Block nHeight doesn't match, expected " + std::to_string(latestBlock->getNHeight() + 1) + + " got " + std::to_string(block.getNHeight()) + ); + return BlockValidationStatus::invalidWrongHeight; } - if (block.getPrevBlockHash() != latestBlock->hash()) { - Logger::logToDebug(LogType::ERROR, Log::state, __func__, "Block prevBlockHash doesn't match, expected " + - latestBlock->hash().hex().get() + " got: " + block.getPrevBlockHash().hex().get()); - return false; + if (block.getPrevBlockHash() != latestBlock->getHash()) { + LOGERROR("Block prevBlockHash doesn't match, expected " + latestBlock->getHash().hex().get() + + " got: " + block.getPrevBlockHash().hex().get() + ); + return BlockValidationStatus::invalidErroneous; } if (latestBlock->getTimestamp() > block.getTimestamp()) { - Logger::logToDebug(LogType::ERROR, Log::state, __func__, "Block timestamp is lower than latest block, expected higher than " + std::to_string(latestBlock->getTimestamp()) - + " got " + std::to_string(block.getTimestamp())); - return false; + LOGERROR("Block timestamp is lower than latest block, expected higher than " + + std::to_string(latestBlock->getTimestamp()) + " got " + std::to_string(block.getTimestamp()) + ); + return BlockValidationStatus::invalidErroneous; } - if (!this->rdpos_->validateBlock(block)) { - Logger::logToDebug(LogType::ERROR, Log::state, __func__, "Invalid rdPoS in block"); - return false; + if (!this->rdpos_.validateBlock(block)) { + LOGERROR("Invalid rdPoS in block"); + return BlockValidationStatus::invalidErroneous; } - std::shared_lock verifyingBlockTxs(this->stateMutex_); for (const auto& tx : block.getTxs()) { - if (this->validateTransactionInternal(tx)) { - Logger::logToDebug(LogType::ERROR, Log::state, __func__, "Transaction " + tx.hash().hex().get() + " within block is invalid"); - return false; + if (!isTxStatusValid(this->validateTransactionInternal(tx))) { + LOGERROR("Transaction " + tx.hash().hex().get() + " within block is invalid"); + return BlockValidationStatus::invalidErroneous; } } - Logger::logToDebug(LogType::INFO, Log::state, __func__, "Block " + block.hash().hex().get() + " is valid. (Sanity Check Passed)"); - return true; + LOGTRACE("Block " + block.getHash().hex().get() + " is valid. (Sanity Check Passed)"); + return BlockValidationStatus::valid; } -void State::processNextBlock(Block&& block) { - // Sanity check - if it passes, the block is valid and will be processed - if (!this->validateNextBlock(block)) { - Logger::logToDebug(LogType::ERROR, Log::state, __func__, - "Sanity check failed - blockchain is trying to append a invalid block, throwing" - ); - throw std::runtime_error("Invalid block detected during processNextBlock sanity check"); +bool State::validateNextBlock(const FinalizedBlock& block) const { + std::shared_lock lock(this->stateMutex_); + return validateNextBlockInternal(block) == BlockValidationStatus::valid; +} + +void State::processNextBlock(FinalizedBlock&& block) { + if (tryProcessNextBlock(std::move(block)) != BlockValidationStatus::valid) { + LOGERROR("Sanity check failed - blockchain is trying to append a invalid block, throwing"); + throw DynamicException("Invalid block detected during processNextBlock sanity check"); } +} +BlockValidationStatus State::tryProcessNextBlock(FinalizedBlock&& block) { std::unique_lock lock(this->stateMutex_); + // Sanity check - if it passes, the block is valid and will be processed + BlockValidationStatus vStatus = this->validateNextBlockInternal(block); + if (vStatus != BlockValidationStatus::valid) { + return vStatus; + } + // Update contract globals based on (now) latest block - const Hash blockHash = block.hash(); - this->contractManager_->updateContractGlobals(Secp256k1::toAddress(block.getValidatorPubKey()), blockHash, block.getNHeight(), block.getTimestamp()); + const Hash blockHash = block.getHash(); + ContractGlobals::coinbase_ = Secp256k1::toAddress(block.getValidatorPubKey()); + ContractGlobals::blockHash_ = blockHash; + ContractGlobals::blockHeight_ = block.getNHeight(); + ContractGlobals::blockTimestamp_ = block.getTimestamp(); // Process transactions of the block within the current state uint64_t txIndex = 0; for (auto const& tx : block.getTxs()) { - this->processTransaction(tx, blockHash, txIndex); + this->processTransaction(tx, blockHash, txIndex, block.getBlockRandomness()); txIndex++; } // Process rdPoS State - this->rdpos_->processBlock(block); + this->rdpos_.processBlock(block); // Refresh the mempool based on the block transactions this->refreshMempool(block); - Logger::logToDebug(LogType::INFO, Log::state, __func__, "Block " + block.hash().hex().get() + " processed successfully.) block bytes: " + Hex::fromBytes(block.serializeBlock()).get()); - Utils::safePrint("Block: " + block.hash().hex().get() + " height: " + std::to_string(block.getNHeight()) + " was added to the blockchain"); + LOGINFO("Block " + block.getHash().hex().get() + " processed successfully."); + Utils::safePrint("Block: " + block.getHash().hex().get() + " height: " + std::to_string(block.getNHeight()) + " was added to the blockchain"); for (const auto& tx : block.getTxs()) { Utils::safePrint("Transaction: " + tx.hash().hex().get() + " was accepted in the blockchain"); } - // Move block to storage - this->storage_->pushBack(std::move(block)); -} + blockObservers_.notify(block); -void State::fillBlockWithTransactions(Block& block) const { - std::shared_lock lock(this->stateMutex_); - for (const auto& [hash, tx] : this->mempool_) block.appendTx(tx); + // Move block to storage + this->storage_.pushBlock(std::move(block)); + return vStatus; // BlockValidationStatus::valid } -TxInvalid State::validateTransaction(const TxBlock& tx) const { +TxStatus State::validateTransaction(const TxBlock& tx) const { std::shared_lock lock(this->stateMutex_); return this->validateTransactionInternal(tx); } -TxInvalid State::addTx(TxBlock&& tx) { - auto TxInvalid = this->validateTransaction(tx); - if (TxInvalid) return TxInvalid; +TxStatus State::addTx(TxBlock&& tx) { + const auto txResult = this->validateTransaction(tx); + if (txResult != TxStatus::ValidNew) return txResult; std::unique_lock lock(this->stateMutex_); auto txHash = tx.hash(); this->mempool_.insert({txHash, std::move(tx)}); - Utils::safePrint("Transaction: " + tx.hash().hex().get() + " was added to the mempool"); - return TxInvalid; + LOGTRACE("Transaction: " + txHash.hex().get() + " was added to the mempool"); + return txResult; // should be TxStatus::ValidNew } -bool State::addValidatorTx(const TxValidator& tx) { +TxStatus State::addValidatorTx(const TxValidator& tx) { std::unique_lock lock(this->stateMutex_); - return this->rdpos_->addValidatorTx(tx); + return this->rdpos_.addValidatorTx(tx); } bool State::isTxInMempool(const Hash& txHash) const { @@ -332,66 +484,157 @@ std::unique_ptr State::getTxFromMempool(const Hash &txHash) const { void State::addBalance(const Address& addr) { std::unique_lock lock(this->stateMutex_); - this->accounts_[addr].balance += uint256_t("1000000000000000000000"); + this->accounts_[addr]->balance += uint256_t("1000000000000000000000"); } -Bytes State::ethCall(const ethCallInfo& callInfo) { - std::shared_lock lock(this->stateMutex_); - auto &address = std::get<1>(callInfo); - if (this->contractManager_->isContractAddress(address)) { - return this->contractManager_->callContract(callInfo); - } else { +Bytes State::ethCall(EncodedStaticCallMessage& msg) { + // We actually need to lock uniquely here + // As the contract host will modify (reverting in the end) the state. + std::unique_lock lock(this->stateMutex_); + const auto& accIt = this->accounts_.find(msg.to()); + if (accIt == this->accounts_.end()) { return {}; } + const auto& acc = accIt->second; + try { + if (acc->isContract()) { + ExecutionContext context = ExecutionContext::Builder{} + .storage(this->vmStorage_) + .accounts(this->accounts_) + .contracts(this->contracts_) + .blockHash(Hash()) + .txHash(Hash()) + .txOrigin(msg.from()) + .blockCoinbase(ContractGlobals::getCoinbase()) + .txIndex(0) + .blockNumber(ContractGlobals::getBlockHeight()) + .blockTimestamp(ContractGlobals::getBlockTimestamp()) + .blockGasLimit(10'000'000) + .txGasPrice(0) + .chainId(this->options_.getChainID()) + .build(); + + // As we are simulating, the randomSeed can be anything + const Hash randomSeed = bytes::random(); + + return ContractHost( + this->vm_, + this->dumpManager_, + this->storage_, + randomSeed, + context + ).execute(msg); + } else { + return {}; + } + } catch (VMExecutionError& e) { + throw; + } catch (std::exception& e) { + throw VMExecutionError(-32603, std::string("Internal error: ") + e.what(), Bytes()); + } } -bool State::estimateGas(const ethCallInfo& callInfo) { - std::shared_lock lock(this->stateMutex_); - const auto& [from, to, gasLimit, gasPrice, value, functor, data] = callInfo; - - // Check balance/gasLimit/gasPrice if available. - if (from && value) { - uint256_t totalGas = 0; - if (gasLimit && gasPrice) { - totalGas = gasLimit * gasPrice; +int64_t State::estimateGas(EncodedMessageVariant msg) { + std::unique_lock lock(this->stateMutex_); + auto latestBlock = this->storage_.latest(); + try { + std::unique_ptr context; + const EncodedCallMessage* callMessage = std::get_if(&msg); + const EncodedCreateMessage* createMessage = nullptr; + if (callMessage) { + context = ExecutionContext::Builder{} + .storage(this->vmStorage_) + .accounts(this->accounts_) + .contracts(this->contracts_) + .blockHash(latestBlock->getHash()) + .txHash(Hash()) + .txOrigin(callMessage->from()) + .blockCoinbase(Secp256k1::toAddress(latestBlock->getValidatorPubKey())) + .txIndex(0) + .blockNumber(latestBlock->getNHeight()) + .blockTimestamp(latestBlock->getTimestamp()) + .blockGasLimit(10'000'000) + .txGasPrice(0) + .chainId(this->options_.getChainID()) + .buildPtr(); + } else { + createMessage = std::get_if(&msg); + if (createMessage == nullptr) { + throw DynamicException("Invalid message type for gas estimation"); + } + context = ExecutionContext::Builder{} + .storage(this->vmStorage_) + .accounts(this->accounts_) + .contracts(this->contracts_) + .blockHash(latestBlock->getHash()) + .txHash(Hash()) + .txOrigin(createMessage->from()) + .blockCoinbase(Secp256k1::toAddress(latestBlock->getValidatorPubKey())) + .txIndex(0) + .blockNumber(latestBlock->getNHeight()) + .blockTimestamp(latestBlock->getTimestamp()) + .blockGasLimit(10'000'000) + .txGasPrice(0) + .chainId(this->options_.getChainID()) + .buildPtr(); } - auto it = this->accounts_.find(from); - if (it == this->accounts_.end()) return false; - if (it->second.balance < value + totalGas) return false; - } - if (this->contractManager_->isContractAddress(to)) { - Utils::safePrint("Estimating gas from state..."); - this->contractManager_->validateCallContractWithTx(callInfo); + const Hash randomSeed = bytes::random(); + ContractHost host( + this->vm_, + this->dumpManager_, + this->storage_, + randomSeed, + *context + ); + return std::visit([&host] (auto&& msg) { + const Gas& gas = msg.gas(); + const int64_t initialGas(gas); + host.simulate(std::forward(msg)); + return int64_t((initialGas - int64_t(gas)) * 1.15); + }, std::move(msg)); + } catch (VMExecutionError& e) { + throw; + } catch (std::exception& e) { + throw VMExecutionError(-32603, std::string("Internal error: ") + e.what(), Bytes()); } - - return true; -} - -void State::processContractPayable(const std::unordered_map& payableMap) { - if (!this->processingPayable_) throw std::runtime_error( - "Uh oh, contracts are going haywire! Cannot change State while not processing a payable contract." - ); - for (const auto& [address, amount] : payableMap) this->accounts_[address].balance = amount; } -std::vector> State::getContracts() const { +std::vector> State::getCppContracts() const { std::shared_lock lock(this->stateMutex_); - return this->contractManager_->getContracts(); + std::vector> contracts; + for (const auto& [address, contract] : this->contracts_) { + contracts.emplace_back(contract->getContractName(), address); + } + return contracts; } -const std::vector State::getEvents( - const uint64_t& fromBlock, const uint64_t& toBlock, - const Address& address, const std::vector& topics -) const { +std::vector
State::getEvmContracts() const { std::shared_lock lock(this->stateMutex_); - return this->contractManager_->getEvents(fromBlock, toBlock, address, topics); + std::vector
contracts; + for (const auto& [addr, acc] : this->accounts_) { + if (acc->contractType == ContractType::EVM) contracts.emplace_back(addr); + } + return contracts; } -const std::vector State::getEvents( - const Hash& txHash, const uint64_t& blockIndex, const uint64_t& txIndex -) const { +Bytes State::getContractCode(const Address &addr) const { std::shared_lock lock(this->stateMutex_); - return this->contractManager_->getEvents(txHash, blockIndex, txIndex); + auto it = this->accounts_.find(addr); + if (it == this->accounts_.end()) { + return {}; + } + // If its a PRECOMPILE contract, we need to return "PrecompileContract-CONTRACTNAME" + // yes, inside a Bytes object, not a string object. + if (it->second->contractType == ContractType::CPP) { + auto contractIt = this->contracts_.find(addr); + if (contractIt == this->contracts_.end()) { + return {}; + } + std::string precompileContract = "PrecompileContract-"; + precompileContract.append(contractIt->second->getContractName()); + return {precompileContract.begin(), precompileContract.end()}; + } + return it->second->code; } diff --git a/src/core/state.h b/src/core/state.h index cd6b99cf..b931af7b 100644 --- a/src/core/state.h +++ b/src/core/state.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -9,57 +9,50 @@ See the LICENSE.txt file in the project root for more information. #define STATE_H #include "../contract/contract.h" -#include "../contract/contractmanager.h" -#include "../utils/utils.h" -#include "../utils/db.h" -#include "storage.h" -#include "rdpos.h" -// TODO: We could possibly change the bool functions -// into a enum function, to be able to properly return each error case -// We need this in order to slash invalid rdPoS blocks. -/// Enum for labeling transaction validity. -enum TxInvalid { NotInvalid, InvalidNonce, InvalidBalance }; - -/** - * Abstraction of the blockchain's state. - * Responsible for maintaining the current blockchain state at the current block. - */ -class State { - private: - /// Pointer to the database. - const std::unique_ptr& db_; - - /// Pointer to the blockchain's storage. - const std::unique_ptr& storage_; - - /// Pointer to the rdPoS object. - const std::unique_ptr& rdpos_; - - /// Pointer to the P2P connection manager. - const std::unique_ptr &p2pManager_; - - /// Pointer to the options singleton. - const std::unique_ptr& options_; - - /// Pointer to the contract manager. - const std::unique_ptr contractManager_; - - /// Map with information about blockchain accounts (Address -> Account). - std::unordered_map accounts_; - - /// TxBlock mempool. - std::unordered_map mempool_; - - /// Mutex for managing read/write access to the state object. - mutable std::shared_mutex stateMutex_; +#include "rdpos.h" // set, boost/unordered/unordered_flat_map.hpp +#include "dump.h" // utils/db.h, storage.h -> utils/randomgen.h -> utils.h -> logger.h, (strings.h -> evmc/evmc.hpp), (libs/json.hpp -> boost/unordered/unordered_flat_map.hpp) +#include "contract/blockobservers.h" + +// TODO: We could possibly change the bool functions into an enum function, +// to be able to properly return each error case. We need this in order to slash invalid rdPoS blocks. + +/// Next-block validation status codes. +enum class BlockValidationStatus { valid, invalidWrongHeight, invalidErroneous }; + +/// Abstraction of the blockchain's current state at the current block. +class State : public Dumpable, public Log::LogicalLocationProvider { + protected: // TODO: those shouldn't be protected, plz refactor someday + mutable std::shared_mutex stateMutex_; ///< Mutex for managing read/write access to the state object. + evmc_vm* vm_; ///< Pointer to the EVMC VM. + const Options& options_; ///< Reference to the options singleton. + Storage& storage_; ///< Reference to the blockchain's storage. + DumpManager dumpManager_; ///< The Dump Worker object + DumpWorker dumpWorker_; ///< Dump Manager object + P2P::ManagerNormal& p2pManager_; ///< Reference to the P2P connection manager. + rdPoS rdpos_; ///< rdPoS object (consensus). + boost::unordered_flat_map, SafeHash, SafeCompare> contracts_; ///< Map with information about blockchain contracts (Address -> Contract). + boost::unordered_flat_map vmStorage_; ///< Map with the storage of the EVM. + boost::unordered_flat_map, SafeHash, SafeCompare> accounts_; ///< Map with information about blockchain accounts (Address -> Account). + boost::unordered_flat_map mempool_; ///< TxBlock mempool. + BlockObservers blockObservers_; /** * Verify if a transaction can be accepted within the current state. * @param tx The transaction to check. * @return An enum telling if the block is invalid or not. */ - TxInvalid validateTransactionInternal(const TxBlock& tx) const; + TxStatus validateTransactionInternal(const TxBlock& tx) const; + + /** + * Validate the next block given the current state and its transactions. Does NOT update the state. + * The block will be rejected if there are invalid transactions in it + * (e.g. invalid signature, insufficient balance, etc.). + * NOTE: This method does not perform synchronization. + * @param block The block to validate. + * @return A status code from BlockValidationStatus. + */ + BlockValidationStatus validateNextBlockInternal(const FinalizedBlock& block) const; /** * Process a transaction within a block. Called by processNextBlock(). @@ -67,117 +60,146 @@ class State { * @param tx The transaction to process. * @param blockHash The hash of the block being processed. * @param txIndex The index of the transaction inside the block that is being processed. + * @param randomnessHash The hash of the previous block's randomness seed. */ - void processTransaction(const TxBlock& tx, const Hash& blockHash, const uint64_t& txIndex); + void processTransaction(const TxBlock& tx, const Hash& blockHash, const uint64_t& txIndex, const Hash& randomnessHash); /** - * Update the mempool, removing transactions that are in the given block, - * and leaving only valid transactions in it. - * Called by processNewBlock(), used to filter the current mempool based - * on transactions that have been accepted on the block, and verify if - * transactions on the mempool are valid given the new state after processing - * the block itself. + * Update the mempool, remove transactions that are in the given block, and leave only valid transactions in it. + * Called by processNewBlock(), used to filter the current mempool based on transactions that have been + * accepted on the block, and verify if transactions on the mempool are valid given the new state after + * processing the block itself. * @param block The block to use for pruning transactions from the mempool. */ - void refreshMempool(const Block& block); + void refreshMempool(const FinalizedBlock& block); - /// Flag indicating whether the state is currently processing a payable contract function - bool processingPayable_ = false; + /** + * Helper function that does a sanity check on all contracts in the accounts_ map. + * Used exclusively by the constructor. + * @param addr The address of the contract. + * @param acc The account tied to the contract. + * @throw DynamicException if any contract does not have code, or if an + * address that isn't a contract has any code. + */ + void contractSanityCheck(const Address& addr, const Account& acc); public: /** * Constructor. * @param db Pointer to the database. * @param storage Pointer to the blockchain's storage. - * @param rdpos Pointer to the rdPoS object. * @param p2pManager Pointer to the P2P connection manager. + * @param snapshotHeight The block height to start from. * @param options Pointer to the options singleton. - * @throw std::runtime_error on any database size mismatch. + * @throw DynamicException on any database size mismatch. */ - State( - const std::unique_ptr& db, - const std::unique_ptr& storage, - const std::unique_ptr& rdpos, - const std::unique_ptr& p2pManager, - const std::unique_ptr& options - ); - - /// Destructor. - ~State(); + State(const DB& db, Storage& storage, P2P::ManagerNormal& p2pManager, const uint64_t& snapshotHeight, const Options& options); + + ~State(); ///< Destructor. + + std::string getLogicalLocation() const override { return p2pManager_.getLogicalLocation(); } ///< Log instance from P2P + + // ---------------------------------------------------------------------- + // RDPOS WRAPPER FUNCTIONS + // ---------------------------------------------------------------------- + + ///@{ + /** Wrapper for the respective rdPoS function. */ + std::set rdposGetValidators() const { std::shared_lock lock(this->stateMutex_); return this->rdpos_.getValidators(); } + std::vector rdposGetRandomList() const { std::shared_lock lock(this->stateMutex_); return this->rdpos_.getRandomList(); } + size_t rdposGetMempoolSize() const { std::shared_lock lock(this->stateMutex_); return this->rdpos_.getMempoolSize(); } + const boost::unordered_flat_map rdposGetMempool() const { std::shared_lock lock(this->stateMutex_); return this->rdpos_.getMempool(); } + const Hash& rdposGetBestRandomSeed() const { std::shared_lock lock(this->stateMutex_); return this->rdpos_.getBestRandomSeed(); } + bool rdposGetIsValidator() const { std::shared_lock lock(this->stateMutex_); return this->rdpos_.getIsValidator(); } + const uint32_t& rdposGetMinValidators() const { std::shared_lock lock(this->stateMutex_); return this->rdpos_.getMinValidators(); } + void rdposClearMempool() { std::unique_lock lock(this->stateMutex_); return this->rdpos_.clearMempool(); } + bool rdposValidateBlock(const FinalizedBlock& block) const { std::shared_lock lock(this->stateMutex_); return this->rdpos_.validateBlock(block); } + Hash rdposProcessBlock(const FinalizedBlock& block) { std::unique_lock lock(this->stateMutex_); return this->rdpos_.processBlock(block); } + TxStatus rdposAddValidatorTx(const TxValidator& tx) { std::unique_lock lock(this->stateMutex_); return this->rdpos_.addValidatorTx(tx); } + void dumpStartWorker() { this->dumpWorker_.startWorker(); } + void dumpStopWorker() { this->dumpWorker_.stopWorker(); } + size_t getDumpManagerSize() const { std::shared_lock lock(this->stateMutex_); return this->dumpManager_.size(); } + // Returns the block height of the dump and the time it took to serialize and dump to DB. + std::tuple saveToDB() const { return this->dumpManager_.dumpToDB(); } + ///@} + + // ---------------------------------------------------------------------- + // STATE FUNCTIONS + // ---------------------------------------------------------------------- /** * Get the native balance of an account in the state. * @param addr The address of the account to check. * @return The native account balance of the given address. */ - const uint256_t getNativeBalance(const Address& addr) const; + uint256_t getNativeBalance(const Address& addr) const; /** * Get the native nonce of an account in the state. * @param addr The address of the account to check. * @return The native account nonce of the given address. */ - const uint64_t getNativeNonce(const Address& addr) const; - - /// Getter for `accounts`. Returns a copy. - const std::unordered_map getAccounts() const; + uint64_t getNativeNonce(const Address& addr) const; - /// Getter for `mempool`. Returns a copy. - const std::unordered_map getMempool() const; + /** + * Get a copy of the mempool (as a vector). + * @return A vector with all transactions in the mempool. + */ + std::vector getMempool() const; /// Get the mempool's current size. - inline const size_t getMempoolSize() const { + inline size_t getMempoolSize() const { std::shared_lock lock (this->stateMutex_); return this->mempool_.size(); } /** - * Validate the next block given the current state and its transactions. - * Does NOT update the state. + * Validate the next block given the current state and its transactions. Does NOT update the state. * The block will be rejected if there are invalid transactions in it * (e.g. invalid signature, insufficient balance, etc.). * @param block The block to validate. * @return `true` if the block is validated successfully, `false` otherwise. */ - bool validateNextBlock(const Block& block) const; + bool validateNextBlock(const FinalizedBlock& block) const; /** - * Process the next block given current state from the network. - * DOES update the state. + * Process the next block given current state from the network. DOES update the state. * Appends block to Storage after processing. * @param block The block to process. - * @throw std::runtime_error if block is invalid. + * @throw DynamicException if block is invalid. */ - void processNextBlock(Block&& block); + void processNextBlock(FinalizedBlock&& block); /** - * Fill a block with all transactions currently in the mempool. - * DOES NOT FINALIZE THE BLOCK. - * @param block The block to fill. + * Process the next block given current state from the network. DOES update the state. + * Appends block to Storage after processing. + * Does not throw an exception in case of block validation error. + * @param block The block to process. + * @return A status code from BlockValidationStatus. */ - void fillBlockWithTransactions(Block& block) const; + BlockValidationStatus tryProcessNextBlock(FinalizedBlock&& block); /** * Verify if a transaction can be accepted within the current state. - * Calls validateTransactionInternal(), but locking the mutex in a shared manner. + * Calls validateTransactionInternal(), but locks the mutex in a shared manner. * @param tx The transaction to verify. * @return An enum telling if the transaction is valid or not. */ - TxInvalid validateTransaction(const TxBlock& tx) const; + TxStatus validateTransaction(const TxBlock& tx) const; /** * Add a transaction to the mempool, if valid. * @param tx The transaction to add. * @return An enum telling if the transaction is valid or not. */ - TxInvalid addTx(TxBlock&& tx); + TxStatus addTx(TxBlock&& tx); /** * Add a Validator transaction to the rdPoS mempool, if valid. * @param tx The transaction to add. * @return `true` if transaction is valid, `false` otherwise. */ - bool addValidatorTx(const TxValidator& tx); + TxStatus addValidatorTx(const TxValidator& tx); /** * Check if a transaction is in the mempool. @@ -197,6 +219,8 @@ class State { */ std::unique_ptr getTxFromMempool(const Hash& txHash) const; + // TODO: remember this function is for testing purposes only, + // it should probably be removed at some point before definitive release. /** * Add balance to a given account. * Used through HTTP RPC to add balance to a given address @@ -213,56 +237,36 @@ class State { * @param callInfo Tuple with info about the call (from, to, gasLimit, gasPrice, value, data). * @return The return of the called function as a data string. */ - Bytes ethCall(const ethCallInfo& callInfo); + Bytes ethCall(EncodedStaticCallMessage& msg); /** * Estimate gas for callInfo in RPC. * Doesn't really "estimate" gas, but rather tells if the transaction is valid or not. * @param callInfo Tuple with info about the call (from, to, gasLimit, gasPrice, value, data). - * @return `true` if the call is valid, `false` otherwise. + * @return The used gas limit of the transaction. */ - bool estimateGas(const ethCallInfo& callInfo); + int64_t estimateGas(EncodedMessageVariant msg); - /** - * Update the State's account balances after a contract call. Called by ContractManager. - * @param payableMap A map of the accounts to update and their respective new balances. - * @throw std::runtime_error on an attempt to change State while not processing a payable contract. - */ - void processContractPayable(const std::unordered_map& payableMap); + /// Get a list of the C++ contract addresses and names. + std::vector> getCppContracts() const; - /// Get a list of contract addresses and names. - std::vector> getContracts() const; + /// Get a list of Addresss which are EVM contracts. + std::vector
getEvmContracts() const; - /** - * Get all the events emitted under the given inputs. - * Parameters are defined when calling "eth_getLogs" on an HTTP request - * (directly from the http/jsonrpc submodules, through handle_request() on httpparser). - * They're supposed to be all "optional" at that point, but here they're - * all required, even if all of them turn out to be empty. - * @param fromBlock The initial block height to look for. - * @param toBlock The final block height to look for. - * @param address The address to look for. Defaults to empty (look for all available addresses). - * @param topics The topics to filter by. Defaults to empty (look for all available topics). - * @return A list of matching events. - */ - const std::vector getEvents( - const uint64_t& fromBlock, const uint64_t& toBlock, - const Address& address = Address(), const std::vector& topics = {} - ) const; + DBBatch dump() const final; ///< State dumping function. + + /// Get the pending transactions from the mempool. + auto getPendingTxs() const { + std::shared_lock lock(this->stateMutex_); + return this->mempool_; + } /** - * Overload of getEvents() for transaction receipts. - * @param txHash The hash of the transaction to look for events. - * @param blockIndex The height of the block to look for events. - * @param txIndex The index of the transaction to look for events. - * @return A list of matching events. + * Get the code section of a given contract. + * @param addr The address of the contract. + * @return The code section as a raw bytes string. */ - const std::vector getEvents( - const Hash& txHash, const uint64_t& blockIndex, const uint64_t& txIndex - ) const; - - /// the Manager Interface cannot use getNativeBalance. as it will call a lock with the mutex. - friend class ContractManagerInterface; + Bytes getContractCode(const Address& addr) const; }; #endif // STATE_H diff --git a/src/core/storage.cpp b/src/core/storage.cpp index e904a2b7..7d3865d6 100644 --- a/src/core/storage.cpp +++ b/src/core/storage.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -7,437 +7,287 @@ See the LICENSE.txt file in the project root for more information. #include "storage.h" -Storage::Storage(const std::unique_ptr& db, const std::unique_ptr& options) : db_(db), options_(options) { - Logger::logToDebug(LogType::INFO, Log::storage, __func__, "Loading blockchain from DB"); +#include "../utils/strconv.h" +#include "../utils/uintconv.h" +bool Storage::topicsMatch(const Event& event, const std::vector& topics) { + if (topics.empty()) return true; // No topic filter applied + const std::vector& eventTopics = event.getTopics(); + if (eventTopics.size() < topics.size()) return false; + for (size_t i = 0; i < topics.size(); i++) if (topics.at(i) != eventTopics[i]) return false; + return true; +} + +void Storage::storeBlock(DB& db, const FinalizedBlock& block, bool indexingEnabled) { + DBBatch batch; + batch.push_back(block.getHash(), block.serializeBlock(), DBPrefix::blocks); + batch.push_back(UintConv::uint64ToBytes(block.getNHeight()), block.getHash(), DBPrefix::heightToBlock); + + if (indexingEnabled) { + const auto& Txs = block.getTxs(); + for (uint32_t i = 0; i < Txs.size(); i++) { + const auto& TxHash = Txs[i].hash(); + const FixedBytes<44> value( + bytes::join(block.getHash(), UintConv::uint32ToBytes(i), UintConv::uint64ToBytes(block.getNHeight())) + ); + batch.push_back(TxHash, value, DBPrefix::txToBlock); + } + } + db.putBatch(batch); +} + +void Storage::reindexTransactions(const FinalizedBlock& block, DBBatch& batch) { + const auto& Txs = block.getTxs(); + for (uint32_t i = 0; i < Txs.size(); i++) { + const auto& TxHash = Txs[i].hash(); + const FixedBytes<44> value( + bytes::join(block.getHash(), UintConv::uint32ToBytes(i), UintConv::uint64ToBytes(block.getNHeight())) + ); + batch.push_back(TxHash, value, DBPrefix::txToBlock); + } +} +void Storage::dumpToDisk(DBBatch &batch) { + blocksDb_.putBatch(batch); +} + +Storage::Storage(std::string instanceIdStr, const Options& options) + : blocksDb_(options.getRootPath() + "/blocksDb/"), // Uncompressed + eventsDb_(options.getRootPath() + "/newEventsDb/"), + options_(options), instanceIdStr_(std::move(instanceIdStr)) +{ // Initialize the blockchain if latest block doesn't exist. + LOGINFO("Loading blockchain from DB"); initializeBlockchain(); // Get the latest block from the database - Logger::logToDebug(LogType::INFO, Log::storage, __func__, "Loading latest block"); - auto blockBytes = this->db_->get(Utils::stringToBytes("latest"), DBPrefix::blocks); - Block latest(blockBytes, this->options_->getChainID()); - uint64_t depth = latest.getNHeight(); - Logger::logToDebug(LogType::INFO, Log::storage, __func__, - std::string("Got latest block: ") + latest.hash().hex().get() - + std::string(" - height ") + std::to_string(depth) - ); + LOGINFO("Loading latest block"); + const Bytes latestBlockHash = blocksDb_.getLastByPrefix(DBPrefix::heightToBlock); + if (latestBlockHash.empty()) throw DynamicException("Latest block hash not found in DB"); - std::unique_lock lock(this->chainLock_); - - // Parse block mappings (hash -> height / height -> hash) from DB - Logger::logToDebug(LogType::INFO, Log::storage, __func__, "Parsing block mappings"); - std::vector maps = this->db_->getBatch(DBPrefix::blockHeightMaps); - for (DBEntry& map : maps) { - // TODO: Check if a block is missing. - // Might be interesting to change DB::getBatch to return a map instead of a vector - Logger::logToDebug(LogType::DEBUG, Log::storage, __func__, std::string(": ") - + std::to_string(Utils::bytesToUint64(map.key)) - + std::string(", hash ") + Hash(map.value).hex().get() - ); - this->blockHashByHeight_.insert({Utils::bytesToUint64(map.key),Hash(map.value)}); - this->blockHeightByHash_.insert({Hash(map.value), Utils::bytesToUint64(map.key)}); - } + const Bytes latestBlockBytes = blocksDb_.get(latestBlockHash, DBPrefix::blocks); + if (latestBlockBytes.empty()) throw DynamicException("Latest block bytes not found in DB"); - // Append up to 500 most recent blocks from DB to chain - Logger::logToDebug(LogType::INFO, Log::storage, __func__, "Appending recent blocks"); - for (uint64_t i = 0; i <= 500 && i <= depth; i++) { - Logger::logToDebug(LogType::DEBUG, Log::storage, __func__, - std::string("Height: ") + std::to_string(depth - i) + ", Hash: " - + this->blockHashByHeight_[depth - i].hex().get() - ); - Block block(this->db_->get(this->blockHashByHeight_[depth - i].get(), DBPrefix::blocks), this->options_->getChainID()); - this->pushFrontInternal(std::move(block)); - } + latest_ = std::make_shared(FinalizedBlock::fromBytes(latestBlockBytes, this->options_.getChainID())); + LOGINFO("Latest block successfully loaded"); - Logger::logToDebug(LogType::INFO, Log::storage, __func__, "Blockchain successfully loaded"); -} + // If the legacy events folder exists, migrate them to the new folder + std::filesystem::path legacyEventsPath(options.getRootPath() + "/eventsDb/"); + if (std::filesystem::exists(legacyEventsPath)) { + Utils::safePrint("Detected legacy event DB, migrating to new DB"); + DB legacyEvents(legacyEventsPath); -Storage::~Storage() { - DBBatch batchedOperations; - std::shared_ptr latest; - { - std::unique_lock lock(this->chainLock_); - latest = this->chain_.back(); - while (!this->chain_.empty()) { - // Batch block to be saved to the database. - // We can't call this->popBack() because of the mutex - std::shared_ptr block = this->chain_.front(); - batchedOperations.push_back(block->hash().get(), block->serializeBlock(), DBPrefix::blocks); - batchedOperations.push_back(Utils::uint64ToBytes(block->getNHeight()), block->hash().get(), DBPrefix::blockHeightMaps); - - // Batch txs to be saved to the database and delete them from the mappings - auto Txs = block->getTxs(); - for (uint32_t i = 0; i < Txs.size(); i++) { - const auto TxHash = Txs[i].hash(); - Bytes value = block->hash().asBytes(); - value.reserve(value.size() + 4 + 8); - Utils::appendBytes(value, Utils::uint32ToBytes(i)); - Utils::appendBytes(value, Utils::uint64ToBytes(block->getNHeight())); - batchedOperations.push_back(TxHash.get(), value, DBPrefix::txToBlocks); - this->txByHash_.erase(TxHash); - } + auto transaction = eventsDb_.transaction(); - // Delete block from internal mappings and the chain - this->blockByHash_.erase(block->hash()); - this->chain_.pop_front(); + for (uint64_t block = 0; block <= latest_.load()->getNHeight(); block++) { + if (block % 10000 == 0) { + Utils::safePrint("Migrated events for block " + std::to_string(block) + " total block: " + std::to_string(latest_.load()->getNHeight()) + " current progress: " + std::to_string(block * 100 / latest_.load()->getNHeight()) + "%"); + } + if (block % 200000 == 0) { + Utils::safePrint("Reloading SQLite DB transactions"); + transaction->commit(); + transaction = eventsDb_.transaction(); + } + for (const Event& event : getEventsLegacy(legacyEvents, block, block, Address(), {})) { + eventsDb_.putEvent(event); + } } - } - // Batch save to database - this->db_->putBatch(batchedOperations); - this->db_->put(std::string("latest"), latest->serializeBlock(), DBPrefix::blocks); + transaction->commit(); + assert(legacyEvents.close()); + + std::filesystem::rename(legacyEventsPath, options.getRootPath() + "/legacyEventsDb/"); + } } void Storage::initializeBlockchain() { - if (!this->db_->has(std::string("latest"), DBPrefix::blocks)) { - /// Genesis block comes from Options, not hardcoded - const auto genesis = this->options_->getGenesisBlock(); - if (genesis.getNHeight() != 0) { - throw std::runtime_error("Genesis block height is not 0"); - } - this->db_->put(std::string("latest"), genesis.serializeBlock(), DBPrefix::blocks); - this->db_->put(Utils::uint64ToBytes(genesis.getNHeight()), genesis.hash().get(), DBPrefix::blockHeightMaps); - this->db_->put(genesis.hash().get(), genesis.serializeBlock(), DBPrefix::blocks); - Logger::logToDebug(LogType::INFO, Log::storage, __func__, - std::string("Created genesis block: ") + Hex::fromBytes(genesis.hash().get()).get() - ); + // Genesis block comes from Options, not hardcoded + const auto& genesis = options_.getGenesisBlock(); + + if ( + const Bytes latestBlockHash = blocksDb_.getLastByPrefix(DBPrefix::heightToBlock); + latestBlockHash.empty() + ) { + if (genesis.getNHeight() != 0) throw DynamicException("Genesis block height is not 0"); + storeBlock(blocksDb_, genesis, options_.getIndexingMode() != IndexingMode::DISABLED); + LOGINFO(std::string("Created genesis block: ") + Hex::fromBytes(genesis.getHash()).get()); } - /// Sanity check for genesis block. (check if genesis in DB matches genesis in Options) - const auto genesis = this->options_->getGenesisBlock(); - const auto genesisInDBHash = Hash(this->db_->get(Utils::uint64ToBytes(0), DBPrefix::blockHeightMaps)); - const auto genesisInDB = Block(this->db_->get(genesisInDBHash, DBPrefix::blocks), this->options_->getChainID()); + + // Sanity check for genesis block. (check if genesis in DB matches genesis in Options) + const Hash genesisInDBHash(blocksDb_.get(UintConv::uint64ToBytes(0), DBPrefix::heightToBlock)); + + FinalizedBlock genesisInDB = FinalizedBlock::fromBytes( + blocksDb_.get(genesisInDBHash, DBPrefix::blocks), options_.getChainID() + ); + if (genesis != genesisInDB) { - Logger::logToDebug(LogType::ERROR, Log::storage, __func__, "Sanity Check! Genesis block in DB does not match genesis block in Options"); - throw std::runtime_error("Sanity Check! Genesis block in DB does not match genesis block in Options"); + LOGERROR("Sanity Check! Genesis block in DB does not match genesis block in Options"); + throw DynamicException("Sanity Check! Genesis block in DB does not match genesis block in Options"); } } -const TxBlock Storage::getTxFromBlockWithIndex(const BytesArrView blockData, const uint64_t& txIndex) const { +TxBlock Storage::getTxFromBlockWithIndex(View blockData, uint64_t txIndex) const { uint64_t index = 217; // Start of block tx range - /// Count txs until index. + // Count txs until index. uint64_t currentTx = 0; while (currentTx < txIndex) { - uint32_t txSize = Utils::bytesToUint32(blockData.subspan(index, 4)); + uint32_t txSize = UintConv::bytesToUint32(blockData.subspan(index, 4)); index += txSize + 4; currentTx++; } - uint64_t txSize = Utils::bytesToUint32(blockData.subspan(index, 4)); + uint64_t txSize = UintConv::bytesToUint32(blockData.subspan(index, 4)); index += 4; - return TxBlock(blockData.subspan(index, txSize), this->options_->getChainID()); + return TxBlock(blockData.subspan(index, txSize), this->options_.getChainID()); } -void Storage::pushBackInternal(Block&& block) { - // Push the new block and get a pointer to it - if (!this->chain_.empty()) { - if (this->chain_.back()->hash() != block.getPrevBlockHash()) { - throw std::runtime_error("Block " + block.hash().hex().get() - + " does not have the correct previous block hash." - ); - } - if (block.getNHeight() != this->chain_.back()->getNHeight() + 1) { - throw std::runtime_error("Block " + block.hash().hex().get() - + " does not have the correct height." - ); - } - } - this->chain_.emplace_back(std::make_shared(std::move(block))); - std::shared_ptr newBlock = this->chain_.back(); - - // Add block and txs to mappings - this->blockByHash_.insert({newBlock->hash(), newBlock}); - this->blockHashByHeight_.insert({newBlock->getNHeight(), newBlock->hash()}); - this->blockHeightByHash_.insert({newBlock->hash(), newBlock->getNHeight()}); - const auto& Txs = newBlock->getTxs(); - for (uint32_t i = 0; i < Txs.size(); i++) { - this->txByHash_.insert({ Txs[i].hash(), { newBlock->hash(), i, newBlock->getNHeight() }}); +void Storage::pushBlock(FinalizedBlock block) { + if (auto previousBlock = latest_.load(); previousBlock->getHash() != block.getPrevBlockHash()) { + throw DynamicException("\"previous hash\" of new block does not match the latest block hash"); } + auto newBlock = std::make_shared(std::move(block)); + latest_.store(newBlock); + storeBlock(blocksDb_, *newBlock, options_.getIndexingMode() != IndexingMode::DISABLED); } -void Storage::pushFrontInternal(Block&& block) { - // Push the new block and get a pointer to it - if (!this->chain_.empty()) { - if (this->chain_.front()->getPrevBlockHash() != block.hash()) { - throw std::runtime_error("Block " + block.hash().hex().get() - + " does not have the correct previous block hash." - ); - } - if (block.getNHeight() != this->chain_.front()->getNHeight() - 1) { - throw std::runtime_error("Block " + block.hash().hex().get() - + " does not have the correct height." - ); - } - } - this->chain_.emplace_front(std::make_shared(std::move(block))); - std::shared_ptr newBlock = this->chain_.front(); - - // Add block and txs to mappings - this->blockByHash_.insert({newBlock->hash(), newBlock}); - this->blockHashByHeight_.insert({newBlock->getNHeight(), newBlock->hash()}); - this->blockHeightByHash_.insert({newBlock->hash(), newBlock->getNHeight()}); - const auto& Txs = newBlock->getTxs(); - for (uint32_t i = 0; i < Txs.size(); i++) { - this->txByHash_.insert({Txs[i].hash(), { newBlock->hash(), i, newBlock->getNHeight()}}); - } -} +bool Storage::blockExists(const Hash& hash) const { return blocksDb_.has(hash, DBPrefix::blocks); } + +bool Storage::blockExists(uint64_t height) const { return blocksDb_.has(UintConv::uint64ToBytes(height), DBPrefix::heightToBlock); } -void Storage::pushBack(Block&& block) { - std::unique_lock lock(this->chainLock_); - this->pushBackInternal(std::move(block)); +bool Storage::txExists(const Hash& tx) const { return blocksDb_.has(tx, DBPrefix::txToBlock); } + +std::shared_ptr Storage::getBlock(const Hash& hash) const { + Bytes blockBytes = blocksDb_.get(hash, DBPrefix::blocks); + if (blockBytes.empty()) return nullptr; + return std::make_shared(FinalizedBlock::fromBytes(blockBytes, this->options_.getChainID())); } -void Storage::pushFront(Block&& block) { - std::unique_lock lock(this->chainLock_); - this->pushFrontInternal(std::move(block)); +std::shared_ptr Storage::getBlock(uint64_t height) const { + Bytes blockHash = blocksDb_.get(UintConv::uint64ToBytes(height), DBPrefix::heightToBlock); + if (blockHash.empty()) return nullptr; + Bytes blockBytes = blocksDb_.get(blockHash, DBPrefix::blocks); + return std::make_shared(FinalizedBlock::fromBytes(blockBytes, this->options_.getChainID())); } -void Storage::popBack() { - // Delete block and its txs from the mappings, then pop it from the chain - std::unique_lock lock(this->chainLock_); - std::shared_ptr block = this->chain_.back(); - for (const TxBlock& tx : block->getTxs()) this->txByHash_.erase(tx.hash()); - this->blockByHash_.erase(block->hash()); - this->chain_.pop_back(); +std::tuple< + const std::shared_ptr, const Hash, const uint64_t, const uint64_t +> Storage::getTx(const Hash& tx) const { + const Bytes txData = blocksDb_.get(tx, DBPrefix::txToBlock); + if (txData.empty()) return std::make_tuple(nullptr, Hash(), 0u, 0u); + + const View txDataView = txData; + const Hash blockHash(txDataView.subspan(0, 32)); + const uint64_t blockIndex = UintConv::bytesToUint32(txDataView.subspan(32, 4)); + const uint64_t blockHeight = UintConv::bytesToUint64(txDataView.subspan(36, 8)); + const Bytes blockData = blocksDb_.get(blockHash, DBPrefix::blocks); + + return std::make_tuple( + std::make_shared(getTxFromBlockWithIndex(blockData, blockIndex)), + blockHash, blockIndex, blockHeight + ); } -void Storage::popFront() { - // Delete block and its txs from the mappings, then pop it from the chain - std::unique_lock lock(this->chainLock_); - std::shared_ptr block = this->chain_.front(); - for (const TxBlock& tx : block->getTxs()) this->txByHash_.erase(tx.hash()); - this->blockByHash_.erase(block->hash()); - this->chain_.pop_front(); +std::tuple< + const std::shared_ptr, const Hash, const uint64_t, const uint64_t +> Storage::getTxByBlockHashAndIndex(const Hash& blockHash, const uint64_t blockIndex) const { + const Bytes blockData = blocksDb_.get(blockHash, DBPrefix::blocks); + if (blockData.empty()) std::make_tuple(nullptr, Hash(), 0u, 0u); + + const uint64_t blockHeight = UintConv::bytesToUint64(View(blockData).subspan(201, 8)); + return std::make_tuple( + std::make_shared(getTxFromBlockWithIndex(blockData, blockIndex)), + blockHash, blockIndex, blockHeight + ); } -StorageStatus Storage::blockExists(const Hash& hash) { - // Check chain first, then cache, then database - std::shared_lock lock(this->chainLock_); - if (this->blockByHash_.contains(hash)) { - return StorageStatus::OnChain; - } else if (this->cachedBlocks_.contains(hash)) { - return StorageStatus::OnCache; - } else if (this->db_->has(hash.get(), DBPrefix::blocks)) { - return StorageStatus::OnDB; - } else { - return StorageStatus::NotFound; - } - return StorageStatus::NotFound; +std::tuple< + const std::shared_ptr, const Hash, const uint64_t, const uint64_t +> Storage::getTxByBlockNumberAndIndex(uint64_t blockHeight, uint64_t blockIndex) const { + const Bytes blockHash = blocksDb_.get(UintConv::uint64ToBytes(blockHeight), DBPrefix::heightToBlock); + if (blockHash.empty()) return std::make_tuple(nullptr, Hash(), 0u, 0u); + + const Bytes blockData = blocksDb_.get(blockHash, DBPrefix::blocks); + if (blockData.empty()) return std::make_tuple(nullptr, Hash(), 0u, 0u); + + return std::make_tuple( + std::make_shared(getTxFromBlockWithIndex(blockData, blockIndex)), + Hash(blockHash), blockIndex, blockHeight + ); } -StorageStatus Storage::blockExists(const uint64_t& height) { - // Check chain first, then cache, then database - std::shared_lock lock(this->chainLock_); - auto it = this->blockHashByHeight_.find(height); - if (it != this->blockHashByHeight_.end()) { - if (this->blockByHash_.contains(it->second)) return StorageStatus::OnChain; - std::shared_lock lock2(this->cacheLock_); - if (this->cachedBlocks_.contains(it->second)) return StorageStatus::OnCache; - return StorageStatus::OnDB; - } else { - return StorageStatus::NotFound; - } - return StorageStatus::NotFound; +std::shared_ptr Storage::latest() const { return latest_.load(); } + +uint64_t Storage::currentChainSize() const { + auto latest = latest_.load(); + return latest ? (latest->getNHeight() + 1) : 0u; } -const std::shared_ptr Storage::getBlock(const Hash& hash) { - // Check chain first, then cache, then database - StorageStatus blockStatus = this->blockExists(hash); - switch (blockStatus) { - case StorageStatus::NotFound: { - return nullptr; - } - case StorageStatus::OnChain: { - std::shared_lock lock(this->chainLock_); - return this->blockByHash_.find(hash)->second; - } - case StorageStatus::OnCache: { - std::shared_lock lock(this->cacheLock_); - return this->cachedBlocks_[hash]; - } - case StorageStatus::OnDB: { - std::unique_lock lock(this->cacheLock_); - this->cachedBlocks_.insert({hash, std::make_shared( - this->db_->get(hash.get(), DBPrefix::blocks), this->options_->getChainID() - )}); - return this->cachedBlocks_[hash]; - } - } - return nullptr; +void Storage::putCallTrace(const Hash& txHash, const trace::Call& callTrace) { + Bytes serial; + zpp::bits::out out(serial); + out(callTrace).or_throw(); + blocksDb_.put(txHash, serial, DBPrefix::txToCallTrace); } -const std::shared_ptr Storage::getBlock(const uint64_t& height) { - // Check chain first, then cache, then database - StorageStatus blockStatus = this->blockExists(height); - if (blockStatus == StorageStatus::NotFound) return nullptr; - Logger::logToDebug(LogType::INFO, Log::storage, __func__, "height: " + std::to_string(height)); - switch (blockStatus) { - case StorageStatus::NotFound: { - return nullptr; - } - case StorageStatus::OnChain: { - std::shared_lock lock(this->chainLock_); - return this->blockByHash_.find(this->blockHashByHeight_.find(height)->second)->second; - } - case StorageStatus::OnCache: { - std::shared_lock lock(this->cacheLock_); - Hash hash = this->blockHashByHeight_.find(height)->second; - return this->cachedBlocks_.find(hash)->second; - } - case StorageStatus::OnDB: { - std::unique_lock lock(this->cacheLock_); - Hash hash = this->blockHashByHeight_.find(height)->second; - auto blockData = this->db_->get(hash.get(), DBPrefix::blocks); - this->cachedBlocks_.insert({hash, std::make_shared(blockData, this->options_->getChainID())}); - return this->cachedBlocks_[hash]; - } - } - return nullptr; +std::optional Storage::getCallTrace(const Hash& txHash) const { + Bytes serial = blocksDb_.get(txHash, DBPrefix::txToCallTrace); + if (serial.empty()) return std::nullopt; + + trace::Call callTrace; + zpp::bits::in in(serial); + in(callTrace).or_throw(); + + return callTrace; } -StorageStatus Storage::txExists(const Hash& tx) { - // Check chain first, then cache, then database - std::shared_lock lock(this->chainLock_); - if (this->txByHash_.contains(tx)) { - return StorageStatus::OnChain; - } else if (this->cachedTxs_.contains(tx)) { - return StorageStatus::OnCache; - } else if (this->db_->has(tx.get(), DBPrefix::txToBlocks)) { - return StorageStatus::OnDB; - } else { - return StorageStatus::NotFound; - } +void Storage::putTxAdditionalData(const TxAdditionalData& txData) { + Bytes serialized; + zpp::bits::out out(serialized); + out(txData).or_throw(); + blocksDb_.put(txData.hash, serialized, DBPrefix::txToAdditionalData); } -const std::tuple< - const std::shared_ptr, const Hash, const uint64_t, const uint64_t -> Storage::getTx(const Hash& tx) { - // Check chain first, then cache, then database - StorageStatus txStatus = this->txExists(tx); - switch (txStatus) { - case StorageStatus::NotFound: { - return {nullptr, Hash(), 0, 0}; - } - case StorageStatus::OnChain: { - std::shared_lock lock(this->chainLock_); - const auto& [blockHash, blockIndex, blockHeight] = this->txByHash_.find(tx)->second; - const auto transaction = blockByHash_[blockHash]->getTxs()[blockIndex]; - if (transaction.hash() != tx) throw std::runtime_error("Tx hash mismatch"); - return {std::make_shared(transaction), blockHash, blockIndex, blockHeight}; - } - case StorageStatus::OnCache: { - std::shared_lock lock(this->cacheLock_); - return this->cachedTxs_[tx]; - } - case StorageStatus::OnDB: { - Bytes txData(this->db_->get(tx.get(), DBPrefix::txToBlocks)); - BytesArrView txDataView(txData); - auto blockHash = Hash(txDataView.subspan(0, 32)); - uint64_t blockIndex = Utils::bytesToUint32(txDataView.subspan(32, 4)); - uint64_t blockHeight = Utils::bytesToUint64(txDataView.subspan(36,8)); - Bytes blockData(this->db_->get(blockHash.get(), DBPrefix::blocks)); - auto Tx = this->getTxFromBlockWithIndex(blockData, blockIndex); - std::unique_lock lock(this->cacheLock_); - this->cachedTxs_.insert({tx, {std::make_shared(Tx), blockHash, blockIndex, blockHeight}}); - return this->cachedTxs_[tx]; - } - } - return { nullptr, Hash(), 0, 0 }; +std::optional Storage::getTxAdditionalData(const Hash& txHash) const { + Bytes serialized = blocksDb_.get(txHash, DBPrefix::txToAdditionalData); + if (serialized.empty()) return std::nullopt; + + TxAdditionalData txData; + zpp::bits::in in(serialized); + in(txData).or_throw(); + return txData; } -const std::tuple< - const std::shared_ptr, const Hash, const uint64_t, const uint64_t -> Storage::getTxByBlockHashAndIndex(const Hash& blockHash, const uint64_t blockIndex) { - auto Status = this->blockExists(blockHash); - switch (Status) { - case StorageStatus::NotFound: { - return { nullptr, Hash(), 0, 0 }; - } - case StorageStatus::OnChain: { - std::shared_lock lock(this->chainLock_); - auto txHash = this->blockByHash_[blockHash]->getTxs()[blockIndex].hash(); - const auto& [txBlockHash, txBlockIndex, txBlockHeight] = this->txByHash_[txHash]; - if (txBlockHash != blockHash || txBlockIndex != blockIndex) { - throw std::runtime_error("Tx hash mismatch"); - } - const auto transaction = blockByHash_[blockHash]->getTxs()[blockIndex]; - return {std::make_shared(transaction), txBlockHash, txBlockIndex, txBlockHeight}; - } - case StorageStatus::OnCache: { - std::shared_lock lock(this->cacheLock_); - auto txHash = this->cachedBlocks_[blockHash]->getTxs()[blockIndex].hash(); - return this->cachedTxs_[txHash]; - } - case StorageStatus::OnDB: { - Bytes blockData = this->db_->get(blockHash.get(), DBPrefix::blocks); - auto tx = this->getTxFromBlockWithIndex(blockData, blockIndex); - std::unique_lock lock(this->cacheLock_); - auto blockHeight = this->blockHeightByHash_[blockHash]; - this->cachedTxs_.insert({tx.hash(), {std::make_shared(tx), blockHash, blockIndex, blockHeight}}); - return this->cachedTxs_[tx.hash()]; - } +std::vector Storage::getEventsLegacy(const DB& legacyEventsDB, uint64_t fromBlock, uint64_t toBlock, const Address& address, const std::vector& topics) const { + if (toBlock < fromBlock) std::swap(fromBlock, toBlock); + + if (uint64_t count = toBlock - fromBlock + 1; count > options_.getEventBlockCap()) { + throw std::out_of_range( + "Block range too large for event querying! Max allowed is " + + std::to_string(this->options_.getEventBlockCap()) + ); } - return { nullptr, Hash(), 0, 0 }; -} -const std::tuple< - const std::shared_ptr, const Hash, const uint64_t, const uint64_t -> Storage::getTxByBlockNumberAndIndex(const uint64_t& blockHeight, const uint64_t blockIndex) { - auto Status = this->blockExists(blockHeight); - switch (Status) { - case StorageStatus::NotFound: { - return { nullptr, Hash(), 0, 0 }; - } - case StorageStatus::OnChain: { - std::shared_lock lock(this->chainLock_); - auto blockHash = this->blockHashByHeight_.find(blockHeight)->second; - auto txHash = this->blockByHash_[blockHash]->getTxs()[blockIndex].hash(); - const auto& [txBlockHash, txBlockIndex, txBlockHeight] = this->txByHash_[txHash]; - const auto transaction = this->blockByHash_[blockHash]->getTxs()[blockIndex]; - return {std::make_shared(transaction), txBlockHash, txBlockIndex, txBlockHeight}; - } - case StorageStatus::OnCache: { - std::shared_lock lock(this->cacheLock_); - auto blockHash = this->blockHashByHeight_.find(blockHeight)->second; - auto txHash = this->cachedBlocks_[blockHash]->getTxs()[blockIndex].hash(); - return this->cachedTxs_[txHash]; - } - case StorageStatus::OnDB: { - auto blockHash = this->blockHashByHeight_.find(blockHeight)->second; - Bytes blockData = this->db_->get(blockHash.get(), DBPrefix::blocks); - auto tx = this->getTxFromBlockWithIndex(blockData, blockIndex); - std::unique_lock lock(this->cacheLock_); - auto blockHeight2 = this->blockHeightByHash_[blockHash]; - this->cachedTxs_.insert({tx.hash(), { std::make_shared(tx), blockHash, blockIndex, blockHeight2}}); - return this->cachedTxs_[tx.hash()]; + std::vector events; + std::vector keys; + + const Bytes startBytes = Utils::makeBytes(UintConv::uint64ToBytes(fromBlock)); + const Bytes endBytes = Utils::makeBytes(UintConv::uint64ToBytes(toBlock)); + + auto dbKeys = legacyEventsDB.getKeys(DBPrefix::events, startBytes, endBytes); + + for (Bytes key : dbKeys) { + uint64_t nHeight = UintConv::bytesToUint64(Utils::create_view_span(key, 0, 8)); + Address currentAddress(Utils::create_view_span(key, 24, 20)); + if (fromBlock > nHeight || toBlock < nHeight) { + continue; } + if (address == currentAddress || address == Address()) keys.push_back(std::move(key)); } - return { nullptr, Hash(), 0, 0 }; -} -const std::shared_ptr Storage::latest() { - std::shared_lock lock(this->chainLock_); - return this->chain_.back(); -} - -uint64_t Storage::currentChainSize() { return this->latest()->getNHeight() + 1; } - -void Storage::periodicSaveToDB() const { - while (!this->stopPeriodicSave_) { - std::this_thread::sleep_for(std::chrono::seconds(this->periodicSaveCooldown_)); - if (!this->stopPeriodicSave_ && - (this->cachedBlocks_.size() > 1000 || this->cachedTxs_.size() > 1000000) - ) { - // TODO: Properly implement periodic save to DB, saveToDB() function saves **everything** to DB. - // Requirements: - // 1. Save up to 50% of current block list size to DB (e.g. 500 blocks if there are 1000 blocks). - // 2. Save all tx references existing on these blocks to DB. - // 3. Check if block is **unique** to Storage class (use shared_ptr::use_count()), if it is, save it to DB. - // 4. Take max 1 second, Storage::periodicSaveToDB() should never lock this->chainLock_ for too long, otherwise chain might stall. - // use_count() of blocks inside Storage would be 2 if on chain (this->chain + this->blockByHash_) and not being used anywhere else on the program. - // or 1 if on cache (cachedBlocks). - // if ct > 3 (or 1), we have to wait until whoever is using the block - // to stop using it so we can save it. + if (!keys.empty()) { + for (DBEntry item : legacyEventsDB.getBatch(DBPrefix::events, keys)) { + if (events.size() >= options_.getEventLogCap()) break; + Event event(StrConv::bytesToString(item.value)); + if (topicsMatch(event, topics)) events.push_back(std::move(event)); } } + return events; } - diff --git a/src/core/storage.h b/src/core/storage.h index b516f815..2aad8414 100644 --- a/src/core/storage.h +++ b/src/core/storage.h @@ -1,5 +1,5 @@ /* -Copyright (c) [2023-2024] [Sparq Network] +Copyright (c) [2023-2024] [AppLayer Developers] This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. @@ -8,194 +8,109 @@ See the LICENSE.txt file in the project root for more information. #ifndef STORAGE_H #define STORAGE_H -#include - -#include "../utils/block.h" #include "../utils/db.h" -#include "../utils/ecdsa.h" -#include "../utils/randomgen.h" -#include "../utils/safehash.h" -#include "../utils/utils.h" +#include "../utils/eventsdb.h" +#include "../utils/randomgen.h" // utils.h +#include "../utils/safehash.h" // tx.h -> ecdsa.h -> utils.h -> bytes/join.h, strings.h -> libs/zpp_bits.h #include "../utils/options.h" -/// Enum for the status of a block or transaction inside the storage. -enum StorageStatus { NotFound, OnChain, OnCache, OnDB }; +#include "../contract/calltracer.h" +#include "../contract/event.h" /** * Abstraction of the blockchain history. * Used to store blocks in memory and on disk, and helps the State process * new blocks, transactions and RPC queries. - * TODO: Improve const correctness. - * Possible replace `std::shared_ptr` with a better solution. */ -class Storage { +class Storage : public Log::LogicalLocationProvider { + // TODO: possibly replace `std::shared_ptr` with a better solution. private: - /// Pointer to the database that contains the blockchain's entire history. - const std::unique_ptr& db_; + std::atomic> latest_; ///< Pointer to the latest block in the blockchain. + DB blocksDb_; ///< Database object that contains all the blockchain blocks + EventsDB eventsDb_; ///< DB exclusive to events + const Options& options_; ///< Reference to the options singleton. + const std::string instanceIdStr_; ///< Identifier for logging - /// Pointer to the options singleton. - const std::unique_ptr& options_; + void initializeBlockchain(); ///< Initialize the blockchain. /** - * The recent blockchain history, up to the 1000 most recent blocks, - * or 1M transactions, whichever comes first. - * This limit is required because it would be too expensive to keep - * every single transaction in memory all the time, so once it reaches - * the limit, or every now and then, the blocks are dumped to the database. - * This keeps the blockchain lightweight in memory and extremely responsive. - * Older blocks always at FRONT, newer blocks always at BACK. - */ - std::deque> chain_; - - /// Map that indexes blocks in memory by their respective hashes. - std::unordered_map, SafeHash> blockByHash_; - - /// Map that indexes Tx, blockHash, blockIndex and blockHeight by their respective hashes - std::unordered_map, SafeHash> txByHash_; - - /// Map that indexes all block heights in the chain by their respective hashes. - std::unordered_map blockHeightByHash_; - - /// Map that indexes all block hashes in the chain by their respective heights. - std::unordered_map blockHashByHeight_; - - /// Cache space for blocks that will be included in the blockchain. - mutable std::unordered_map, SafeHash> cachedBlocks_; - - /// Cache space for transactions that will be included in the blockchain (tx, txBlockHash, txBlockIndex, txBlockHeight). - mutable std::unordered_map, const Hash, const uint64_t, const uint64_t>, - SafeHash> cachedTxs_; - - /// Mutex for managing read/write access to the blockchain. - mutable std::shared_mutex chainLock_; - - /// Mutex to manage read/write access to the cache. - mutable std::shared_mutex cacheLock_; - - /// Thread that periodically saves the blockchain history to the database. - std::thread periodicSaveThread_; - - /// Cooldown for the periodic save thread, in seconds. - uint64_t periodicSaveCooldown_ = 15; - - /// Flag for stopping the periodic save thread, if required. - bool stopPeriodicSave_ = false; - - /** - * Add a block to the end of the chain. - * Only call this function directly if absolutely sure that `chainLock_` is locked. - * @param block The block to add. - * @throw std::runtime_error on incorrect previous hash or height. - */ - void pushBackInternal(Block&& block); - - /** - * Add a block to the start of the chain. - * Only call this function directly if absolutely sure that `chainLock_` is locked. - * @param block The block to add. - * @throw std::runtime_error on incorrect previous hash or height. - */ - void pushFrontInternal(Block&& block); - - /** - * Initializes the blockchain the first time the blockchain binary is booted. - * Called by the constructor. Will only populate information related to - * the class, such as genesis and mappings. - */ - void initializeBlockchain(); - - /** - * Parse a given transaction from a serialized block data string. - * Used to get only a specific transaction from a block. - * @param blockData The serialized block data string. + * Get a transaction from a block based on a given transaction index. + * @param blockData The raw block string. * @param txIndex The index of the transaction to get. - * @return The transaction itself. */ - const TxBlock getTxFromBlockWithIndex(const BytesArrView blockData, const uint64_t& txIndex) const; + TxBlock getTxFromBlockWithIndex(View blockData, uint64_t txIndex) const; public: /** * Constructor. Automatically loads the chain from the database * and starts the periodic save thread. - * @param db Pointer to the database. - * @param options Pointer to the options singleton. + * @param instanceIdStr Instance ID string to use for logging. + * @param options Reference to the options singleton. */ - Storage(const std::unique_ptr& db, const std::unique_ptr& options); + Storage(std::string instanceIdStr, const Options& options); - /** - * Destructor. - * Automatically saves the chain to the database. - */ - ~Storage(); + /// Log instance (provided in ctor). + std::string getLogicalLocation() const override { return instanceIdStr_; } /// Wrapper for `pushBackInternal()`. Use this as it properly locks `chainLock_`. - void pushBack(Block&& block); - - /// Wrapper for `pushFrontInternal()`. Use this as it properly locks `chainLock_`. - void pushFront(Block&& block); - - /// Remove a block from the end of the chain. - void popBack(); - - /// Remove a block from the start of the chain. - void popFront(); + void pushBlock(FinalizedBlock block); /** * Check if a block exists anywhere in storage (memory/chain, then cache, then database). + * Locks `chainLock_` and `cacheLock_`, to be used by external actors. * @param hash The block hash to search. - * @return An enum telling where the block is. + * @return `true` if the block exists, `false` otherwise. */ - StorageStatus blockExists(const Hash& hash); + bool blockExists(const Hash& hash) const; /** * Overload of blockExists() that works with block height instead of hash. * @param height The block height to search. - * @return An enum telling where the block is. + * @return `true` if the block exists, `false` otherwise. */ - StorageStatus blockExists(const uint64_t& height); + bool blockExists(uint64_t height) const; /** * Get a block from the chain using a given hash. * @param hash The block hash to get. * @return A pointer to the found block, or `nullptr` if block is not found. */ - const std::shared_ptr getBlock(const Hash& hash); + std::shared_ptr getBlock(const Hash& hash) const; /** * Get a block from the chain using a given height. * @param height The block height to get. * @return A pointer to the found block, or `nullptr` if block is not found. */ - const std::shared_ptr getBlock(const uint64_t& height); + std::shared_ptr getBlock(uint64_t height) const; /** * Check if a transaction exists anywhere in storage (memory/chain, then cache, then database). * @param tx The transaction to check. - * @return An enum telling where the transaction is. + * @return Bool telling if the transaction exists. */ - StorageStatus txExists(const Hash& tx); + bool txExists(const Hash& tx) const; /** * Get a transaction from the chain using a given hash. * @param tx The transaction hash to get. * @return A tuple with the found transaction, block hash, index and height. - * @throw std::runtime_error on hash mismatch. + * @throw DynamicException on hash mismatch. */ - const std::tuple< + std::tuple< const std::shared_ptr, const Hash, const uint64_t, const uint64_t - > getTx(const Hash& tx); + > getTx(const Hash& tx) const; /** * Get a transaction from a block with a specific index. * @param blockHash The block hash * @param blockIndex the index within the block * @return A tuple with the found transaction, block hash, index and height. - * @throw std::runtime_error on hash mismatch. + * @throw DynamicException on hash mismatch. */ - const std::tuple< + std::tuple< const std::shared_ptr, const Hash, const uint64_t, const uint64_t - > getTxByBlockHashAndIndex(const Hash& blockHash, const uint64_t blockIndex); + > getTxByBlockHashAndIndex(const Hash& blockHash, const uint64_t blockIndex) const; /** * Get a transaction from a block with a specific index. @@ -203,24 +118,79 @@ class Storage { * @param blockIndex The index within the block. * @return A tuple with the found transaction, block hash, index and height. */ - const std::tuple< + std::tuple< const std::shared_ptr, const Hash, const uint64_t, const uint64_t - > getTxByBlockNumberAndIndex(const uint64_t& blockHeight, const uint64_t blockIndex); + > getTxByBlockNumberAndIndex(uint64_t blockHeight, uint64_t blockIndex) const; + + /// Get the most recently added block from the chain. + std::shared_ptr latest() const; + + /// Get the number of blocks currently in the chain (nHeight of latest block + 1). + uint64_t currentChainSize() const; /** - * Get the most recently added block from the chain. - * @returns A pointer to the latest block. + * Stores additional transaction data + * @param txData The additional transaction data */ - const std::shared_ptr latest(); + void putTxAdditionalData(const TxAdditionalData& txData); - /// Get the number of blocks currently in the chain (nHeight of latest block + 1). - uint64_t currentChainSize(); + /** + * Retrieve the stored additional transaction data. + * @param txHash The target transaction hash. + * @return The transaction data if existent, or an empty optional otherwise. + */ + std::optional getTxAdditionalData(const Hash& txHash) const; - /// Start the periodic save thread. TODO: this should be called by the constructor. - void periodicSaveToDB() const; + /** + * Store a transaction call trace. + * @param txHash The transaction hash. + * @param callTrace The call trace of the transaction. + */ + void putCallTrace(const Hash& txHash, const trace::Call& callTrace); - /// Stop the periodic save thread. TODO: this should be called by the destructor. - void stopPeriodicSaveToDB() { this->stopPeriodicSave_ = true; } + /** + * Retrieve the stored call trace of the target transaction. + * @param txHash The target transaction hash. + * @return The transation call trace if existent, or an empty optional otherwise. + */ + std::optional getCallTrace(const Hash& txHash) const; + + + /** + * Reindex transactions to the DB + * if the transaction is not already indexed. + */ + void reindexTransactions(const FinalizedBlock& block, DBBatch& batch); + + void dumpToDisk(DBBatch& batch); + + EventsDB& events() { return eventsDb_; } + + const EventsDB& events() const { return eventsDb_; } + + std::vector getEventsLegacy(const DB& legacyEventsDB, uint64_t fromBlock, uint64_t toBlock, const Address& address, const std::vector& topics) const; + + /** + * Get the indexing mode of the storage. + * @returns The indexing mode of the storage. + */ + inline IndexingMode getIndexingMode() const { return options_.getIndexingMode(); } + + /** + * Helper function for checking if an event has certain topics. + * @param event The event to check. + * @param topics A list of topics to check for. + * @return `true` if all topics match (or if no topics were provided), `false` otherwise. + */ + static bool topicsMatch(const Event& event, const std::vector& topics); + + /** + * Helper function for storing a block in the database. + * @param db Reference to the database. + * @param block The block to store. + * @param indexingEnabled Whether the node has indexing enabled or not. + */ + static void storeBlock(DB& db, const FinalizedBlock& block, bool indexingEnabled); }; #endif // STORAGE_H diff --git a/src/libs/boost/certify/crlset_parser.hpp b/src/libs/boost/certify/crlset_parser.hpp new file mode 100644 index 00000000..71749447 --- /dev/null +++ b/src/libs/boost/certify/crlset_parser.hpp @@ -0,0 +1,73 @@ +#ifndef BOOST_CERTIFY_CRLSET_PARSER_HPP +#define BOOST_CERTIFY_CRLSET_PARSER_HPP + +#include +#include +#include + +namespace boost +{ + +namespace certify +{ + +namespace detail +{ + +template +struct inline_global +{ + static T value; +}; + +template +T inline_global::value{}; + +} // namespace detail + +enum class crlset_error +{ + header_length_truncated, + header_truncated, + serial_truncated +}; + +class crlset_parser_category : public system::error_category +{ +public: + char const* name() const noexcept override; + + std::string message(int ev) const override; +}; + +system::error_code +make_error_code(crlset_error ev); + +struct crlset +{ + std::array parent_spki_hash; + std::vector serials; +}; + +std::vector +parse_crlset(asio::const_buffer b, system::error_code& ec); + +std::vector +parse_crlset(boost::asio::const_buffer b); + +} // namespace certify + +namespace system +{ +template<> +struct is_error_code_enum<::boost::certify::crlset_error> +{ + static bool const value = true; +}; +} // namespace system + +} // namespace boost + +#include + +#endif // BOOST_CERTIFY_CRLSET_PARSER_HPP diff --git a/src/libs/boost/certify/detail/config.hpp b/src/libs/boost/certify/detail/config.hpp new file mode 100644 index 00000000..678916cf --- /dev/null +++ b/src/libs/boost/certify/detail/config.hpp @@ -0,0 +1,20 @@ +#ifndef BOOST_CERTIFY_DETAIL_CONFIG_HPP +#define BOOST_CERTIFY_DETAIL_CONFIG_HPP + +#ifndef BOOST_CERTIFY_USE_NATIVE_CERTIFICATE_STORE +#define BOOST_CERTIFY_USE_NATIVE_CERTIFICATE_STORE 1 +#endif // BOOST_CERTIFY_USE_NATIVE_CERTIFICATE_STORE + +#ifndef BOOST_CERTIFY_SEPARATE_COMPILATION +#define BOOST_CERTIFY_HEADER_ONLY 1 +#ifndef BOOST_CERTIFY_DECL +#define BOOST_CERTIFY_DECL inline +#endif // BOOST_CERTIFY_DECL +#else +#define BOOST_CERTIFY_HEADER_ONLY 0 +#ifndef BOOST_CERTIFY_DECL +#define BOOST_CERTIFY_DECL +#endif // BOOST_CERTIFY_DECL +#endif // BOOST_CERTIFY_SEPARATE_COMPILATION + +#endif // BOOST_CERTIFY_DETAIL_CONFIG_HPP diff --git a/src/libs/boost/certify/detail/keystore_apple.ipp b/src/libs/boost/certify/detail/keystore_apple.ipp new file mode 100644 index 00000000..da019de0 --- /dev/null +++ b/src/libs/boost/certify/detail/keystore_apple.ipp @@ -0,0 +1,127 @@ +#ifndef CERTIFY_TLS_DETAIL_KEYSTORE_APPLE +#define CERTIFY_TLS_DETAIL_KEYSTORE_APPLE + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace boost +{ +namespace certify +{ + +namespace detail +{ + +template +struct cf_deleter +{ + using pointer = T; + + void operator()(T t) + { + CFRelease(t); + } +}; + +template +using cf_ptr = std::unique_ptr>; + +BOOST_CERTIFY_DECL bool +dump_cert(X509* cert, std::vector& buffer) +{ + auto cert_len = ::i2d_X509(cert, nullptr); + if (cert_len <= 0) + return false; + buffer.resize(cert_len); + auto* b = buffer.data(); + ::i2d_X509(cert, &b); + BOOST_ASSERT(b != nullptr); + return true; +} + +struct certs_vec +{ + certs_vec() = default; + + certs_vec(certs_vec const&) = delete; + certs_vec(certs_vec&&) = delete; + + certs_vec& operator=(certs_vec&&) = delete; + certs_vec& operator=(certs_vec const&) = delete; + + ~certs_vec() + { + for (auto ref : vec_) + if (ref != nullptr) + CFRelease(ref); + } + + std::vector vec_; +}; + +BOOST_CERTIFY_DECL bool +verify_certificate_chain(::X509_STORE_CTX* ctx) +{ + auto* const chain = ::X509_STORE_CTX_get_chain(ctx); + auto const cert_count = sk_X509_num(chain); + if (cert_count <= 0) + return false; + + certs_vec cf_certs; + std::vector buffer; + for (int i = 0; i < cert_count; ++i) + { + auto* const cert = sk_X509_value(chain, i); + if (!detail::dump_cert(cert, buffer)) + return false; + + cf_ptr ref{CFDataCreateWithBytesNoCopy( + nullptr, buffer.data(), buffer.size(), kCFAllocatorNull)}; + if (ref == nullptr) + return false; + + cf_certs.vec_.push_back(nullptr); + cf_certs.vec_.back() = SecCertificateCreateWithData(nullptr, ref.get()); + if (cf_certs.vec_.back() == nullptr) + return false; + } + + cf_ptr cert_array{[&]() { + auto* p = cf_certs.vec_.data(); + return CFArrayCreate(nullptr, p, cf_certs.vec_.size(), nullptr); + }()}; + if (cert_array == nullptr) + return false; + + // No need to pass hostname in - it's already verified by OpenSSL at this + // point. + cf_ptr policy{SecPolicyCreateSSL(true, /*hostname*/ nullptr)}; + + OSStatus status; + auto trust = [&]() -> cf_ptr { + SecTrustRef t = nullptr; + status = + SecTrustCreateWithCertificates(cert_array.get(), policy.get(), &t); + return cf_ptr{t}; + }(); + + if (status != noErr) + return false; + + return SecTrustEvaluateWithError(trust.get(), nullptr); +} + +} // namespace detail +} // namespace certify +} // namespace boost + +#endif // CERTIFY_TLS_DETAIL_KEYSTORE_APPLE diff --git a/src/libs/boost/certify/detail/keystore_default.ipp b/src/libs/boost/certify/detail/keystore_default.ipp new file mode 100644 index 00000000..cec69dfe --- /dev/null +++ b/src/libs/boost/certify/detail/keystore_default.ipp @@ -0,0 +1,24 @@ +#ifndef BOOST_CERTIFY_DETAIL_KEYSTORE_DEFAULT +#define BOOST_CERTIFY_DETAIL_KEYSTORE_DEFAULT + +#include +#include + +namespace boost +{ +namespace certify +{ +namespace detail +{ + +BOOST_CERTIFY_DECL bool +verify_certificate_chain(::X509_STORE_CTX*) +{ + return false; +} + +} // namespace detail +} // namespace certify +} // namespace boost + +#endif // BOOST_CERTIFY_DETAIL_KEYSTORE_DEFAULT diff --git a/src/libs/boost/certify/detail/keystore_windows.ipp b/src/libs/boost/certify/detail/keystore_windows.ipp new file mode 100644 index 00000000..efcc697d --- /dev/null +++ b/src/libs/boost/certify/detail/keystore_windows.ipp @@ -0,0 +1,174 @@ +#ifndef BOOST_CERTIFY_TLS_DETAIL_KEYSTORE_WINDOWS +#define BOOST_CERTIFY_TLS_DETAIL_KEYSTORE_WINDOWS + +#include +#include + +#include +#include + +namespace boost +{ +namespace certify +{ +namespace detail +{ +struct cert_context_deleter +{ + void operator()(::CERT_CONTEXT const* ctx) + { + ::CertFreeCertificateContext(ctx); + } +}; + +struct cert_chain_deleter +{ + void operator()(::CERT_CHAIN_CONTEXT const* ctx) + { + ::CertFreeCertificateChain(ctx); + } +}; + +struct cert_store_deleter +{ + using pointer = HCERTSTORE; + + void operator()(HCERTSTORE store) + { + CertCloseStore(store, 0); + } +}; + +BOOST_CERTIFY_DECL std::unique_ptr<::CERT_CHAIN_CONTEXT const, cert_chain_deleter> +get_cert_chain_context(::CERT_CONTEXT const* cert_ctx, CERT_CHAIN_PARA* params) +{ + ::CERT_CHAIN_CONTEXT const* ctx = nullptr; + auto const success = + ::CertGetCertificateChain(nullptr, + cert_ctx, + nullptr, + cert_ctx->hCertStore, + params, + CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY, + nullptr, + &ctx); + + std::unique_ptr<::CERT_CHAIN_CONTEXT const, cert_chain_deleter> ret{ctx}; + if (!success) + { + return nullptr; + } + + return ret; +} + +BOOST_CERTIFY_DECL bool +dump_cert(X509* cert, std::vector& buffer) +{ + auto cert_len = ::i2d_X509(cert, nullptr); + if (cert_len < 0) + return false; + buffer.resize(cert_len); + auto* buf_ptr = buffer.data(); + auto cert_len2 = ::i2d_X509(cert, &buf_ptr); + if (cert_len != cert_len2) + return false; + return true; +} + +BOOST_CERTIFY_DECL std::unique_ptr<::CERT_CONTEXT const, cert_context_deleter> + create_cert_ctx(STACK_OF(X509) * chain) +{ + std::unique_ptr store{ + CertOpenStore(CERT_STORE_PROV_MEMORY, + 0, + NULL, + CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, + nullptr)}; + + std::vector buffer; + + PCCERT_CONTEXT leaf_cert = nullptr; + + if (!dump_cert(sk_X509_value(chain, 0), buffer)) + return nullptr; + + if (!CertAddEncodedCertificateToStore(store.get(), + X509_ASN_ENCODING, + buffer.data(), + static_cast(buffer.size()), + CERT_STORE_ADD_ALWAYS, + &leaf_cert)) + return nullptr; + + std::unique_ptr<::CERT_CONTEXT const, cert_context_deleter> ret{leaf_cert}; + for (int i = 1; i < sk_X509_num(chain); ++i) + { + if (!dump_cert(sk_X509_value(chain, i), buffer)) + return nullptr; + + if (!CertAddEncodedCertificateToStore(store.get(), + X509_ASN_ENCODING, + buffer.data(), + static_cast(buffer.size()), + CERT_STORE_ADD_ALWAYS, + nullptr)) + return nullptr; + } + + return ret; +} + +BOOST_CERTIFY_DECL bool +verify_certificate_chain(::X509_STORE_CTX* ctx) +{ + auto* const chain = ::X509_STORE_CTX_get_chain(ctx); + if (sk_X509_num(chain) <= 0) + return false; + + auto cert_ctx = create_cert_ctx(chain); + if (cert_ctx == nullptr) + return false; + + char oidPkixKpServerAuth[] = szOID_PKIX_KP_SERVER_AUTH; + char oidServerGatedCrypto[] = szOID_SERVER_GATED_CRYPTO; + char oidSgcNetscape[] = szOID_SGC_NETSCAPE; + std::array chain_usage = { + oidPkixKpServerAuth, + oidServerGatedCrypto, + oidSgcNetscape, + }; + + ::CERT_CHAIN_PARA chain_params = {sizeof(chain_params)}; + chain_params.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; + chain_params.RequestedUsage.Usage.cUsageIdentifier = + static_cast(chain_usage.size()); + chain_params.RequestedUsage.Usage.rgpszUsageIdentifier = chain_usage.data(); + auto const cert_chain_context = + get_cert_chain_context(cert_ctx.get(), &chain_params); + if (cert_chain_context == nullptr || + cert_chain_context->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) + return false; + + ::HTTPSPolicyCallbackData policyData = { + {sizeof(policyData)}, + AUTHTYPE_SERVER, + 0, + nullptr, + }; + ::CERT_CHAIN_POLICY_PARA policy_params = {sizeof(policy_params)}; + policy_params.pvExtraPolicyPara = &policyData; + ::CERT_CHAIN_POLICY_STATUS policy_status = {sizeof(policy_status)}; + + return ::CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, + cert_chain_context.get(), + &policy_params, + &policy_status) && + policy_status.dwError == 0; +} + +} // namespace detail +} // namespace certify +} // namespace boost + +#endif // BOOST_CERTIFY_TLS_DETAIL_KEYSTORE_WINDOWS diff --git a/src/libs/boost/certify/detail/spki_blacklist.hpp b/src/libs/boost/certify/detail/spki_blacklist.hpp new file mode 100644 index 00000000..7833d807 --- /dev/null +++ b/src/libs/boost/certify/detail/spki_blacklist.hpp @@ -0,0 +1,313 @@ +#ifndef BOOST_CERTIFY_DETAIL_SPKI_BLACKLIST_HPP +#define BOOST_CERTIFY_DETAIL_SPKI_BLACKLIST_HPP + +#include + +namespace boost +{ +namespace certify +{ +namespace detail +{ + +constexpr std::array +spki_blacklist[] = { + // 2740d956b1127b791aa1b3cc644a4dbedba76186a23638b95102351a834ea861.pem + {0x04, 0xdd, 0xe9, 0xaa, 0x9a, 0x79, 0xf6, 0x14, 0x98, 0x68, 0x23, + 0x25, 0xfa, 0x08, 0x70, 0x27, 0x67, 0x07, 0xfb, 0x9c, 0xa9, 0x53, + 0x84, 0x12, 0x0b, 0x46, 0x89, 0x32, 0x68, 0x49, 0x4f, 0xc9}, + // 91e5cc32910686c5cac25c18cc805696c7b33868c280caf0c72844a2a8eb91e2.pem + {0x0c, 0x43, 0xea, 0x8b, 0xcd, 0xe9, 0xfc, 0x3b, 0xca, 0x16, 0x56, + 0x64, 0xac, 0x82, 0x15, 0x56, 0x7e, 0x34, 0x89, 0xd5, 0x39, 0x3a, + 0x0c, 0x81, 0xe1, 0xa7, 0x91, 0x41, 0x99, 0x2e, 0x19, 0x53}, + // ead610e6e90b439f2ecb51628b0932620f6ef340bd843fca38d3181b8f4ba197.pem + {0x12, 0x13, 0x23, 0x60, 0xa3, 0x3b, 0xfd, 0xc6, 0xc3, 0xbf, 0x7b, + 0x7f, 0xab, 0x26, 0xa1, 0x68, 0x48, 0x74, 0xe7, 0x2c, 0x12, 0x63, + 0xc1, 0xf5, 0xde, 0x56, 0x5b, 0xb4, 0x9e, 0xf0, 0x37, 0x53}, + // 4bf6bb839b03b72839329b4ea70bb1b2f0d07e014d9d24aa9cc596114702bee3.pem + {0x12, 0x7d, 0xa2, 0x7a, 0x9e, 0x45, 0xf0, 0x82, 0x28, 0x0b, 0x31, + 0xbf, 0x1e, 0x56, 0x15, 0x20, 0x38, 0x9f, 0x96, 0x65, 0x90, 0x93, + 0xb2, 0x69, 0x7c, 0x40, 0xfe, 0x86, 0x00, 0x23, 0x6c, 0x8c}, + // 0f912fd7be760be25afbc56bdc09cd9e5dcc9c6f6a55a778aefcb6aa30e31554.pem + {0x13, 0x0a, 0xd4, 0xe0, 0x63, 0x35, 0x21, 0x29, 0x05, 0x31, 0xb6, + 0x65, 0x1f, 0x57, 0x59, 0xb0, 0xbc, 0x7b, 0xc6, 0x56, 0x70, 0x9f, + 0xf8, 0xf3, 0x65, 0xc2, 0x14, 0x3b, 0x03, 0x89, 0xb6, 0xf6}, + // c7ba6567de93a798ae1faa791e712d378fae1f93c4397fea441bb7cbe6fd5995.pem + {0x15, 0x28, 0x39, 0x7d, 0xa2, 0x12, 0x89, 0x0a, 0x83, 0x0b, 0x0b, + 0x95, 0xa5, 0x99, 0x68, 0xce, 0xf2, 0x34, 0x77, 0x37, 0x79, 0xdf, + 0x51, 0x81, 0xcf, 0x10, 0xfa, 0x64, 0x75, 0x34, 0xbb, 0x65}, + // 1af56c98ff043ef92bebff54cebb4dd67a25ba956c817f3e6dd3c1e52eb584c1.key + {0x1a, 0xf5, 0x6c, 0x98, 0xff, 0x04, 0x3e, 0xf9, 0x2b, 0xeb, 0xff, + 0x54, 0xce, 0xbb, 0x4d, 0xd6, 0x7a, 0x25, 0xba, 0x95, 0x6c, 0x81, + 0x7f, 0x3e, 0x6d, 0xd3, 0xc1, 0xe5, 0x2e, 0xb5, 0x84, 0xc1}, + // e28393773da845a679f2080cc7fb44a3b7a1c3792cb7eb7729fdcb6a8d99aea7.pem + {0x1f, 0x42, 0x24, 0xce, 0xc8, 0x4f, 0xc9, 0x9c, 0xed, 0x88, 0x1f, + 0xf6, 0xfc, 0xfd, 0x3e, 0x21, 0xf8, 0xc5, 0x19, 0xc5, 0x47, 0xaa, + 0x6a, 0x5d, 0xd3, 0xde, 0x24, 0x73, 0x02, 0xce, 0x50, 0xd1}, + // e54e9fc27e7350ff63a77764a40267b7e95ae5df3ed7df5336e8f8541356c845.pem + {0x25, 0xda, 0x1a, 0xd5, 0x8b, 0xbf, 0xcf, 0xb2, 0x27, 0xd8, 0x72, + 0x3b, 0x18, 0x57, 0xd4, 0xc1, 0x8e, 0x7b, 0xaa, 0x74, 0x17, 0xb4, + 0xf9, 0xef, 0xf9, 0x36, 0x6b, 0x5e, 0x86, 0x9f, 0x8b, 0x39}, + // 159ca03a88897c8f13817a212629df84ce824709492b8c9adb8e5437d2fc72be.pem + {0x2c, 0x99, 0x8e, 0x76, 0x11, 0x60, 0xc3, 0xb0, 0x6d, 0x82, 0xfa, + 0xa9, 0xfd, 0xc7, 0x54, 0x5d, 0x9b, 0xda, 0x9e, 0xb6, 0x03, 0x10, + 0xf9, 0x92, 0xaa, 0x51, 0x0a, 0x62, 0x80, 0xb7, 0x42, 0x45}, + // d0d672c2547d574ae055d9e78a993ddbcc74044c4253fbfaca573a67d368e1db.pem + {0x30, 0xef, 0xe4, 0x13, 0x82, 0x47, 0x6c, 0x33, 0x80, 0xf0, 0x2f, + 0x7e, 0x23, 0xe6, 0x6b, 0xa2, 0xf8, 0x67, 0xb0, 0x59, 0xee, 0x1e, + 0xa6, 0x87, 0x96, 0xb4, 0x41, 0xb8, 0x5b, 0x5d, 0x12, 0x56}, + // 32ecc96f912f96d889e73088cd031c7ded2c651c805016157a23b6f32f798a3b.key + {0x32, 0xec, 0xc9, 0x6f, 0x91, 0x2f, 0x96, 0xd8, 0x89, 0xe7, 0x30, + 0x88, 0xcd, 0x03, 0x1c, 0x7d, 0xed, 0x2c, 0x65, 0x1c, 0x80, 0x50, + 0x16, 0x15, 0x7a, 0x23, 0xb6, 0xf3, 0x2f, 0x79, 0x8a, 0x3b}, + // 4aefc3d39ef59e4d4b0304b20f53a8af2efb69edece66def74494abfc10a2d66.pem + {0x36, 0xea, 0x96, 0x12, 0x8c, 0x89, 0x83, 0x9f, 0xb6, 0x21, 0xf8, + 0xad, 0x0e, 0x1e, 0xe0, 0xb9, 0xc2, 0x20, 0x6f, 0x62, 0xab, 0x7b, + 0x4d, 0xa2, 0xc6, 0x76, 0x58, 0x93, 0xc9, 0xb7, 0xce, 0xd2}, + // d487a56f83b07482e85e963394c1ecc2c9e51d0903ee946b02c301581ed99e16.pem + {0x38, 0x1a, 0x3f, 0xc7, 0xa8, 0xb0, 0x82, 0xfa, 0x28, 0x61, 0x3a, + 0x4d, 0x07, 0xf2, 0xc7, 0x55, 0x3f, 0x4e, 0x19, 0x18, 0xee, 0x07, + 0xca, 0xa9, 0xe8, 0xb7, 0xce, 0xde, 0x5a, 0x9c, 0xa0, 0x6a}, + // 0ef7c54a3af101a2cfedb0c9f36fe8214d51a504fdc2ad1e243019cefd7d03c2.pem + {0x38, 0x3e, 0x0e, 0x13, 0x7c, 0x37, 0xbf, 0xb9, 0xdb, 0x29, 0xf9, + 0xa8, 0xe4, 0x5e, 0x9f, 0xf8, 0xdd, 0x4c, 0x30, 0xe4, 0x40, 0xfe, + 0xc2, 0xac, 0xd3, 0xdb, 0xa7, 0xb6, 0xc7, 0x20, 0xb9, 0x93}, + // cb954e9d80a3e520ac71f1a84511657f2f309d172d0bb55e0ec2c236e74ff4b4.pem + {0x39, 0x4c, 0xff, 0x58, 0x9e, 0x68, 0x93, 0x12, 0xcf, 0xc0, 0x71, + 0xee, 0x0b, 0xc1, 0x9f, 0xe4, 0xc6, 0x06, 0x21, 0x6c, 0xe5, 0x43, + 0x42, 0x9d, 0xe6, 0xdb, 0x62, 0xe4, 0x2d, 0xbb, 0x3b, 0xc1}, + // 42187727be39faf667aeb92bf0cc4e268f6e2ead2cefbec575bdc90430024f69.pem + {0x3e, 0xdb, 0xd9, 0xac, 0xe6, 0x39, 0xba, 0x1a, 0x2d, 0x4a, 0xd0, + 0x47, 0x18, 0x71, 0x1f, 0xda, 0x23, 0xe8, 0x59, 0xb2, 0xfb, 0xf5, + 0xd1, 0x37, 0xd4, 0x24, 0x04, 0x5e, 0x79, 0x19, 0xdf, 0xb9}, + // 294f55ef3bd7244c6ff8a68ab797e9186ec27582751a791515e3292e48372d61.pem + {0x45, 0x5b, 0x87, 0xe9, 0x6f, 0x1c, 0xea, 0x2f, 0x8b, 0x6d, 0xae, + 0x08, 0x08, 0xec, 0x24, 0x73, 0x8f, 0xd9, 0x2b, 0x7f, 0xd3, 0x06, + 0x75, 0x71, 0x98, 0xbf, 0x38, 0x9d, 0x75, 0x5c, 0x0b, 0x6c}, + // 3ab0fcc7287454c405863e3aa204fea8eb0c50a524d2a7e15524a830cd4ab0fe.pem + {0x49, 0x0b, 0x6e, 0xc6, 0xbe, 0xb2, 0xd6, 0x03, 0x47, 0x20, 0xb5, + 0x14, 0x9b, 0x6b, 0x29, 0xcd, 0x35, 0x51, 0x59, 0x88, 0xcc, 0x16, + 0xaf, 0x85, 0x41, 0x48, 0xb0, 0x7b, 0x9b, 0x1f, 0x8a, 0x11}, + // b6fe9151402bad1c06d7e66db67a26aa7356f2e6c644dbcf9f98968ff632e1b7.pem + {0x4b, 0xb8, 0xf3, 0x5b, 0xa1, 0xe1, 0x26, 0xf8, 0xdd, 0xe1, 0xb0, + 0xc4, 0x20, 0x62, 0x5e, 0xd8, 0x6d, 0xce, 0x61, 0xa7, 0xbd, 0xda, + 0xdb, 0xde, 0xa9, 0xab, 0xa5, 0x78, 0xff, 0x13, 0x14, 0x5e}, + // fa5a828c9a7e732692682e60b14c634309cbb2bb79eb12aef44318d853ee97e3.pem + {0x4c, 0xdb, 0x06, 0x0f, 0x3c, 0xfe, 0x4c, 0x3d, 0x3f, 0x5e, 0x31, + 0xc3, 0x00, 0xfd, 0x68, 0xa9, 0x1e, 0x0d, 0x1e, 0x5f, 0x46, 0xb6, + 0x4e, 0x48, 0x95, 0xf2, 0x0e, 0x1b, 0x5c, 0xf8, 0x26, 0x9f}, + // 7abd72a323c9d179c722564f4e27a51dd4afd24006b38a40ce918b94960bcf18.pem + {0x57, 0x80, 0x94, 0x46, 0xea, 0xf1, 0x14, 0x84, 0x38, 0x54, 0xfe, + 0x63, 0x6e, 0xd9, 0xbc, 0xb5, 0x52, 0xe3, 0xc6, 0x16, 0x66, 0x3b, + 0xc4, 0x4c, 0xc9, 0x5a, 0xcf, 0x56, 0x50, 0x01, 0x6d, 0x3e}, + // 817d4e05063d5942869c47d8504dc56a5208f7569c3d6d67f3457cfe921b3e29.pem + {0x5c, 0x72, 0x2c, 0xb7, 0x0f, 0xb3, 0x11, 0xf2, 0x1e, 0x0d, 0xa0, + 0xe7, 0xd1, 0x2e, 0xbc, 0x8e, 0x05, 0xf6, 0x07, 0x96, 0xbc, 0x49, + 0xcf, 0x51, 0x18, 0x49, 0xd5, 0xbc, 0x62, 0x03, 0x03, 0x82}, + // 79f69a47cfd6c4b4ceae8030d04b49f6171d3b5d6c812f58d040e586f1cb3f14.pem + // 933f7d8cda9f0d7c8bfd3c22bf4653f4161fd38ccdcf66b22e95a2f49c2650f8.pem + // f8a5ff189fedbfe34e21103389a68340174439ad12974a4e8d4d784d1f3a0faa.pem + {0x5e, 0x53, 0xf2, 0x64, 0x67, 0xf8, 0x94, 0xfd, 0xe5, 0x3b, 0x3f, + 0xa4, 0x06, 0xa4, 0x40, 0xcb, 0xb3, 0xb0, 0x76, 0xbb, 0x5b, 0x75, + 0x8f, 0xe4, 0x83, 0x4a, 0xd6, 0x65, 0x00, 0x20, 0x89, 0x07}, + // 2d11e736f0427fd6ba4b372755d34a0edd8d83f7e9e7f6c01b388c9b7afa850d.pem + {0x6a, 0xdb, 0x8e, 0x3e, 0x05, 0x54, 0x60, 0x92, 0x2d, 0x15, 0x01, + 0xcb, 0x97, 0xf9, 0x4c, 0x6a, 0x02, 0xe3, 0x9c, 0x8f, 0x27, 0x74, + 0xca, 0x40, 0x88, 0x25, 0xb7, 0xb5, 0x83, 0x79, 0xdc, 0x14}, + // 2a33f5b48176523fd3c0d854f20093417175bfd498ef354cc7f38b54adabaf1a.pem + {0x70, 0x7d, 0x36, 0x4e, 0x72, 0xae, 0x52, 0x14, 0x31, 0xdd, 0x95, + 0x38, 0x97, 0xf9, 0xc4, 0x84, 0x6d, 0x5b, 0x8c, 0x32, 0x42, 0x98, + 0xfe, 0x53, 0xfb, 0xd4, 0xad, 0xa1, 0xf2, 0xd1, 0x15, 0x7f}, + // f4a5984324de98bd979ef181a100cf940f2166173319a86a0d9d7c8fac3b0a8f.pem + {0x71, 0x65, 0xe9, 0x91, 0xad, 0xe7, 0x91, 0x6d, 0x86, 0xb4, 0x66, + 0xab, 0xeb, 0xb6, 0xe4, 0x57, 0xca, 0x93, 0x1c, 0x80, 0x4e, 0x58, + 0xce, 0x1f, 0xba, 0xba, 0xe5, 0x09, 0x15, 0x6f, 0xfb, 0x43}, + // 8b45da1c06f791eb0cabf26be588f5fb23165c2e614bf885562d0dce50b29b02.pem + {0x7a, 0xed, 0xdd, 0xf3, 0x6b, 0x18, 0xf8, 0xac, 0xb7, 0x37, 0x9f, + 0xe1, 0xce, 0x18, 0x32, 0x12, 0xb2, 0x35, 0x0d, 0x07, 0x88, 0xab, + 0xe0, 0xe8, 0x24, 0x57, 0xbe, 0x9b, 0xad, 0xad, 0x6d, 0x54}, + // c43807a64c51a3fbde5421011698013d8b46f4e315c46186dc23aea2670cd34f.pem + {0x7c, 0xd2, 0x95, 0xb7, 0x55, 0x44, 0x80, 0x8a, 0xbd, 0x94, 0x09, + 0x46, 0x6f, 0x08, 0x37, 0xc5, 0xaa, 0xdc, 0x02, 0xe3, 0x3b, 0x61, + 0x50, 0xc6, 0x64, 0x4d, 0xe0, 0xa0, 0x96, 0x59, 0xf2, 0x3c}, + // f3bae5e9c0adbfbfb6dbf7e04e74be6ead3ca98a5604ffe591cea86c241848ec.pem + {0x7d, 0x5e, 0x3f, 0x50, 0x50, 0x81, 0x97, 0xb9, 0xa4, 0x78, 0xb1, + 0x13, 0x40, 0xb7, 0xdc, 0xe2, 0x0a, 0x3c, 0x4d, 0xe4, 0x9c, 0x48, + 0xc9, 0xa2, 0x94, 0x15, 0x8a, 0x89, 0x5c, 0x44, 0xa2, 0x1b}, + // b8686723e415534bc0dbd16326f9486f85b0b0799bf6639334e61daae67f36cd.pem + {0x7e, 0x70, 0x58, 0xea, 0x35, 0xad, 0x43, 0x59, 0x65, 0x41, 0x59, + 0x97, 0x3f, 0x56, 0x01, 0x87, 0xf1, 0x6d, 0x19, 0xc5, 0x14, 0xb9, + 0x39, 0xc5, 0x05, 0x56, 0x72, 0xd1, 0xd2, 0xa5, 0x18, 0xac}, + // 5e8e77aafdda2ba5ce442f27d8246650bbd6508befbeda35966a4dc7e6174edc.pem + {0x87, 0xbf, 0xd8, 0xaf, 0xa3, 0xaf, 0x5b, 0x42, 0x9d, 0x09, 0xa9, + 0xaa, 0x54, 0xee, 0x61, 0x36, 0x4f, 0x5a, 0xe1, 0x11, 0x31, 0xe4, + 0x38, 0xfc, 0x41, 0x09, 0x53, 0x43, 0xcd, 0x16, 0xb1, 0x35}, + // ddd8ab9178c99cbd9685ea4ae66dc28bfdc9a5a8a166f7f69ad0b5042ad6eb28.pem + {0x8f, 0x59, 0x1f, 0x7a, 0xa4, 0xdc, 0x3e, 0xfe, 0x94, 0x90, 0xc3, + 0x8a, 0x46, 0x92, 0xc9, 0x01, 0x1e, 0xd1, 0x28, 0xf1, 0xde, 0x59, + 0x55, 0x69, 0x40, 0x6d, 0x77, 0xb6, 0xfa, 0x1f, 0x6b, 0x4c}, + // 450f1b421bb05c8609854884559c323319619e8b06b001ea2dcbb74a23aa3be2.pem + {0x93, 0xca, 0x2d, 0x43, 0x6c, 0xae, 0x7f, 0x68, 0xd2, 0xb4, 0x25, + 0x6c, 0xa1, 0x75, 0xc9, 0x85, 0xce, 0x39, 0x92, 0x6d, 0xc9, 0xf7, + 0xee, 0xae, 0xec, 0xf2, 0xf8, 0x97, 0x0f, 0xb9, 0x78, 0x02}, + // e757fd60d8dd4c26f77aca6a87f63ea4d38d0b736c7f79b56cad932d4c400fb5.pem + {0x96, 0x2e, 0x4b, 0x54, 0xbb, 0x98, 0xa7, 0xee, 0x5d, 0x5f, 0xeb, + 0x96, 0x33, 0xf9, 0x91, 0xd3, 0xc3, 0x30, 0x0e, 0x95, 0x14, 0xda, + 0xde, 0x7b, 0x0d, 0x4f, 0x82, 0x8c, 0x79, 0x4f, 0x8e, 0x87}, + // 3d3d823fad13dfeef32da580166d4a4992bed5a22d695d12c8b08cc3463c67a2.pem + {0x96, 0x8d, 0xba, 0x69, 0xfb, 0xff, 0x15, 0xbf, 0x37, 0x62, 0x08, + 0x94, 0x31, 0xad, 0xe5, 0xa7, 0xea, 0xd4, 0xb7, 0xea, 0xf1, 0xbe, + 0x70, 0x02, 0x68, 0x10, 0xbc, 0x57, 0xd1, 0xc6, 0x4f, 0x6e}, + // 1f17f2cbb109f01c885c94d9e74a48625ae9659665d6d7e7bc5a10332976370f.pem + {0x99, 0xba, 0x47, 0x84, 0xf9, 0xb0, 0x85, 0x12, 0x90, 0x2e, 0xb0, + 0xc3, 0xc8, 0x6d, 0xf0, 0xec, 0x04, 0x9e, 0xac, 0x9b, 0x65, 0xf7, + 0x7a, 0x9b, 0xa4, 0x2b, 0xe9, 0xd6, 0xeb, 0xce, 0x32, 0x0f}, + // a8e1dfd9cd8e470aa2f443914f931cfd61c323e94d75827affee985241c35ce5.pem + {0x9b, 0x8a, 0x93, 0xde, 0xcc, 0xcf, 0xba, 0xfc, 0xf4, 0xd0, 0x4d, + 0x34, 0x42, 0x12, 0x8f, 0xb3, 0x52, 0x18, 0xcf, 0xe4, 0x37, 0xa3, + 0xd8, 0xd0, 0x32, 0x8c, 0x99, 0xf8, 0x90, 0x89, 0xe4, 0x50}, + // 2e0f66a9f9e764c33008482058fe0d92fc0ec0b122fbe994ed7bf6463668cdd4.pem + {0x9c, 0x35, 0x74, 0x7c, 0x3a, 0x53, 0x5c, 0xf2, 0x13, 0xb1, 0x47, + 0x4e, 0xdb, 0x39, 0x77, 0xf1, 0x38, 0x24, 0x0d, 0x6d, 0xc1, 0xce, + 0xcd, 0xee, 0x74, 0x11, 0xa8, 0xf1, 0x25, 0x53, 0xb1, 0x3e}, + // 8253da6738b60c5c0bb139c78e045428a0c841272abdcb952f95ff05ed1ab476.pem + {0x9c, 0x59, 0xa3, 0xcc, 0xae, 0xa4, 0x69, 0x98, 0x42, 0xb0, 0x68, + 0xcf, 0xc5, 0x2c, 0xf9, 0x45, 0xdb, 0x51, 0x98, 0x69, 0x57, 0xc8, + 0x32, 0xcd, 0xb1, 0x8c, 0xa7, 0x38, 0x49, 0xfb, 0xb9, 0xee}, + // 7d8ce822222b90c0b14342c7a8145d1f24351f4d1a1fe0edfd312ee73fb00149.pem + {0x9d, 0x98, 0xa1, 0xfb, 0x60, 0x53, 0x8c, 0x4c, 0xc4, 0x85, 0x7f, + 0xf1, 0xa8, 0xc8, 0x03, 0x4f, 0xaf, 0x6f, 0xc5, 0x92, 0x09, 0x3f, + 0x61, 0x99, 0x94, 0xb2, 0xc8, 0x13, 0xd2, 0x50, 0xb8, 0x64}, + // 1c01c6f4dbb2fefc22558b2bca32563f49844acfc32b7be4b0ff599f9e8c7af7.pem + {0x9d, 0xd5, 0x5f, 0xc5, 0x73, 0xf5, 0x46, 0xcb, 0x6a, 0x38, 0x31, + 0xd1, 0x11, 0x2d, 0x87, 0x10, 0xa6, 0xf4, 0xf8, 0x2d, 0xc8, 0x7f, + 0x5f, 0xae, 0x9d, 0x3a, 0x1a, 0x02, 0x8d, 0xd3, 0x6e, 0x4b}, + // 487afc8d0d411b2a05561a2a6f35918f4040e5570c4c73ee323cc50583bcfbb7.pem + {0xa0, 0xcf, 0x53, 0xf4, 0x22, 0x65, 0x1e, 0x39, 0x31, 0x7a, 0xe3, + 0x1a, 0xf6, 0x45, 0x77, 0xbe, 0x45, 0x0f, 0xa3, 0x76, 0xe2, 0x89, + 0xed, 0x83, 0x42, 0xb7, 0xfc, 0x13, 0x3c, 0x69, 0x74, 0x19}, + // 0d136e439f0ab6e97f3a02a540da9f0641aa554e1d66ea51ae2920d51b2f7217.pem + // 4fee0163686ecbd65db968e7494f55d84b25486d438e9de558d629d28cd4d176.pem + // 8a1bd21661c60015065212cc98b1abb50dfd14c872a208e66bae890f25c448af.pem + {0xa9, 0x03, 0xaf, 0x8c, 0x07, 0xbb, 0x91, 0xb0, 0xd9, 0xe3, 0xf3, + 0xa3, 0x0c, 0x6d, 0x53, 0x33, 0x9f, 0xc5, 0xbd, 0x47, 0xe5, 0xd6, + 0xbd, 0xb4, 0x76, 0x59, 0x88, 0x60, 0xc0, 0x68, 0xa0, 0x24}, + // a2e3bdaacaaf2d2e8204b3bc7eddc805d54d3ab8bdfe7bf102c035f67d8f898a.pem + {0xa9, 0xb5, 0x5a, 0x9b, 0x55, 0x31, 0xbb, 0xf7, 0xc7, 0x1a, 0x1e, + 0x49, 0x20, 0xef, 0xe7, 0x96, 0xc2, 0xb6, 0x79, 0x68, 0xf5, 0x5a, + 0x6c, 0xe5, 0xcb, 0x62, 0x17, 0x2e, 0xd9, 0x94, 0x5b, 0xca}, + // 5472692abe5d02cd22eae3e0a0077f17802721d6576cde1cba2263ee803410c5.pem + {0xaf, 0x59, 0x15, 0x18, 0xe2, 0xe6, 0xc6, 0x0e, 0xbb, 0xfc, 0x09, + 0x07, 0xaf, 0xaa, 0x49, 0xbc, 0x40, 0x51, 0xd4, 0x5e, 0x7f, 0x21, + 0x4a, 0xbf, 0xee, 0x75, 0x12, 0xee, 0x00, 0xf6, 0x61, 0xed}, + // b8c1b957c077ea76e00b0f45bff5ae3acb696f221d2e062164fe37125e5a8d25.pem + {0xb3, 0x18, 0x2e, 0x28, 0x9a, 0xe3, 0x4d, 0xdf, 0x2b, 0xe6, 0x43, + 0xab, 0x79, 0xc2, 0x44, 0x30, 0x16, 0x05, 0xfa, 0x0f, 0x1e, 0xaa, + 0xe6, 0xd1, 0x0f, 0xb9, 0x29, 0x60, 0x0a, 0xf8, 0x4d, 0xf0}, + // be144b56fb1163c49c9a0e6b5a458df6b29f7e6449985960c178a4744624b7bc.pem + {0xb4, 0xd5, 0xc9, 0x20, 0x41, 0x5e, 0xd0, 0xcc, 0x4f, 0x5d, 0xbc, + 0x7f, 0x54, 0x26, 0x36, 0x76, 0x2e, 0x80, 0xda, 0x66, 0x25, 0xf3, + 0x3f, 0x2b, 0x6a, 0xd6, 0xdb, 0x68, 0xbd, 0xba, 0xb2, 0x9a}, + // 372447c43185c38edd2ce0e9c853f9ac1576ddd1704c2f54d96076c089cb4227.pem + {0xc1, 0x73, 0xf0, 0x62, 0x64, 0x56, 0xca, 0x85, 0x4f, 0xf2, 0xa7, + 0xf0, 0xb1, 0x33, 0xa7, 0xcf, 0x4d, 0x02, 0x11, 0xe5, 0x52, 0xf2, + 0x4b, 0x3e, 0x33, 0xad, 0xe8, 0xc5, 0x9f, 0x0a, 0x42, 0x4c}, + // c4387d45364a313fbfe79812b35b815d42852ab03b06f11589638021c8f2cb44.key + {0xc4, 0x38, 0x7d, 0x45, 0x36, 0x4a, 0x31, 0x3f, 0xbf, 0xe7, 0x98, + 0x12, 0xb3, 0x5b, 0x81, 0x5d, 0x42, 0x85, 0x2a, 0xb0, 0x3b, 0x06, + 0xf1, 0x15, 0x89, 0x63, 0x80, 0x21, 0xc8, 0xf2, 0xcb, 0x44}, + // 8290cc3fc1c3aac3239782c141ace8f88aeef4e9576a43d01867cf19d025be66.pem + // 9532e8b504964331c271f3f5f10070131a08bf8ba438978ce394c34feeae246f.pem + {0xc6, 0x01, 0x23, 0x4e, 0x2b, 0x93, 0x25, 0xdc, 0x92, 0xe3, 0xea, + 0xba, 0xc1, 0x96, 0x00, 0xb0, 0xb4, 0x99, 0x47, 0xd4, 0xd0, 0x4d, + 0x8c, 0x99, 0xd3, 0x21, 0x27, 0x49, 0x3e, 0xa0, 0x28, 0xf8}, + // 53d48e7b8869a3314f213fd2e0178219ca09022dbe50053bf6f76fccd61e8112.pem + {0xc8, 0xfd, 0xdc, 0x75, 0xcb, 0x1b, 0xdb, 0xb5, 0x8c, 0x07, 0xb4, + 0xea, 0x84, 0x72, 0x87, 0xf6, 0x26, 0x65, 0x9d, 0xd6, 0x6b, 0xc1, + 0x0a, 0x26, 0xad, 0xd9, 0xb5, 0x75, 0xb3, 0xa0, 0xa3, 0x8d}, + // ec30c9c3065a06bb07dc5b1c6b497f370c1ca65c0f30c08e042ba6bcecc78f2c.pem + {0xcd, 0xee, 0x9f, 0x33, 0x05, 0x57, 0x2a, 0x67, 0x7e, 0x1a, 0x6c, + 0x82, 0xdc, 0x1e, 0x02, 0xa3, 0x5b, 0x11, 0xca, 0xe6, 0xa6, 0x84, + 0x33, 0x8c, 0x9f, 0x37, 0xfe, 0x1a, 0xc8, 0xda, 0xec, 0x23}, + // c71f33c36d8efeefbed9d44e85e21cfe96b36fb0e132c52dca2415868492bf8a.pem + {0xd3, 0x1e, 0xc3, 0x92, 0x85, 0xb7, 0xa5, 0x31, 0x9d, 0x01, 0x57, + 0xdb, 0x42, 0x0e, 0xd8, 0x7c, 0x74, 0x3e, 0x33, 0x3b, 0xbc, 0x77, + 0xf8, 0x77, 0x1f, 0x70, 0x46, 0x4f, 0x43, 0x6a, 0x60, 0x49}, + // 9ed8f9b0e8e42a1656b8e1dd18f42ba42dc06fe52686173ba2fc70e756f207dc.pem + // a686fee577c88ab664d0787ecdfff035f4806f3de418dc9e4d516324fff02083.pem + // fdedb5bdfcb67411513a61aee5cb5b5d7c52af06028efc996cc1b05b1d6cea2b.pem + {0xd3, 0x4b, 0x25, 0x5b, 0x2f, 0xe7, 0xd1, 0xa0, 0x96, 0x56, 0xcb, + 0xab, 0x64, 0x09, 0xf7, 0x3c, 0x79, 0x6e, 0xc7, 0xd6, 0x6a, 0xf7, + 0x36, 0x53, 0xec, 0xc3, 0x9a, 0xf9, 0x78, 0x29, 0x73, 0x10}, + // 4b22d5a6aec99f3cdb79aa5ec06838479cd5ecba7164f7f22dc1d65f63d85708.pem + {0xd6, 0xa1, 0x84, 0x43, 0xd3, 0x48, 0xdb, 0x99, 0x4f, 0x93, 0x4c, + 0xcd, 0x8e, 0x63, 0x5d, 0x83, 0x3a, 0x27, 0xac, 0x1e, 0x56, 0xf8, + 0xaf, 0xaf, 0x7c, 0x97, 0xcb, 0x4f, 0x43, 0xea, 0xb6, 0x8b}, + // d6f034bd94aa233f0297eca4245b283973e447aa590f310c77f48fdf83112254.pem + {0xdb, 0x15, 0xc0, 0x06, 0x2b, 0x52, 0x0f, 0x31, 0x8a, 0x19, 0xda, + 0xcf, 0xec, 0xd6, 0x4f, 0x9e, 0x7a, 0x3f, 0xbe, 0x60, 0x9f, 0xd5, + 0x86, 0x79, 0x6f, 0x20, 0xae, 0x02, 0x8e, 0x8e, 0x30, 0x58}, + // 2a4397aafa6227fa11f9f9d76ecbb022b0a4494852c2b93fb2085c8afb19b62a.pem + {0xdb, 0x1d, 0x13, 0xec, 0x42, 0xa2, 0xcb, 0xa3, 0x67, 0x3b, 0xa6, + 0x7a, 0xf2, 0xde, 0xf8, 0x12, 0xe9, 0xc3, 0x55, 0x66, 0x61, 0x75, + 0x76, 0xd9, 0x5b, 0x4d, 0x6f, 0xac, 0xe3, 0xef, 0x0a, 0xe8}, + // 3946901f46b0071e90d78279e82fababca177231a704be72c5b0e8918566ea66.pem + {0xdd, 0x3e, 0xeb, 0x77, 0x9b, 0xee, 0x07, 0xf9, 0xef, 0xda, 0xc3, + 0x82, 0x40, 0x8b, 0x28, 0xd1, 0x42, 0xfa, 0x84, 0x2c, 0x78, 0xe8, + 0xbc, 0x0e, 0x33, 0x34, 0x8d, 0x57, 0xb9, 0x2f, 0x05, 0x83}, + // c67d722c1495be02cbf9ef1159f5ca4aa782dc832dc6aa60c9aa076a0ad1e69d.pem + {0xde, 0x8f, 0x05, 0x07, 0x4e, 0xc0, 0x31, 0x8e, 0x7e, 0x7e, 0x8d, + 0x31, 0x90, 0xda, 0xe8, 0xb0, 0x08, 0x94, 0xf0, 0xe8, 0xdd, 0xdf, + 0xd3, 0x91, 0x3d, 0x01, 0x75, 0x9b, 0x4f, 0x79, 0xb0, 0x5d}, + // c766a9bef2d4071c863a31aa4920e813b2d198608cb7b7cfe21143b836df09ea.pem + // e17890ee09a3fbf4f48b9c414a17d637b7a50647e9bc752322727fcc1742a911.pem + {0xe4, 0x2f, 0x24, 0xbd, 0x4d, 0x37, 0xf4, 0xaa, 0x2e, 0x56, 0xb9, + 0x79, 0xd8, 0x3d, 0x1e, 0x65, 0x21, 0x9f, 0xe0, 0xe9, 0xe3, 0xa3, + 0x82, 0xa1, 0xb3, 0xcb, 0x66, 0xc9, 0x39, 0x55, 0xde, 0x75}, + // e4f9a3235df7330255f36412bc849fb630f8519961ec3538301deb896c953da5.pem + {0xe6, 0xe1, 0x36, 0xc8, 0x61, 0x54, 0xf3, 0x2c, 0x3e, 0x49, 0xf4, + 0x7c, 0xfc, 0x6b, 0x33, 0x8f, 0xf2, 0xdc, 0x61, 0xce, 0x14, 0xfc, + 0x75, 0x89, 0xb3, 0xb5, 0x6a, 0x14, 0x50, 0x13, 0x27, 0x01}, + // 3e26492e20b52de79e15766e6cb4251a1d566b0dbfb225aa7d08dda1dcebbf0a.pem + {0xe7, 0xb9, 0x32, 0xae, 0x7e, 0x9b, 0xdc, 0x70, 0x1d, 0x77, 0x1d, + 0x6f, 0x39, 0xe8, 0xa6, 0x53, 0x44, 0x9e, 0xea, 0x43, 0xbd, 0xb4, + 0x7b, 0xd9, 0x10, 0x22, 0x95, 0x0d, 0x91, 0x79, 0xd8, 0x7e}, + // 5ccaf9f8f2bb3a0d215922eca383354b6ee3c62407ed32e30f6fb2618edeea10.pem + {0xe8, 0x49, 0xc7, 0x17, 0x6c, 0x93, 0xdf, 0x65, 0xf6, 0x4b, 0x61, + 0x69, 0x82, 0x36, 0x6e, 0x56, 0x63, 0x11, 0x78, 0x12, 0xb6, 0xfa, + 0x2b, 0xc0, 0xc8, 0xfa, 0x8a, 0xea, 0xee, 0x41, 0x81, 0xcc}, + // ea08c8d45d52ca593de524f0513ca6418da9859f7b08ef13ff9dd7bf612d6a37.key + {0xea, 0x08, 0xc8, 0xd4, 0x5d, 0x52, 0xca, 0x59, 0x3d, 0xe5, 0x24, + 0xf0, 0x51, 0x3c, 0xa6, 0x41, 0x8d, 0xa9, 0x85, 0x9f, 0x7b, 0x08, + 0xef, 0x13, 0xff, 0x9d, 0xd7, 0xbf, 0x61, 0x2d, 0x6a, 0x37}, + // 60911c79835c3739432d08c45df64311e06985c5889dc5420ce3d142c8c7ef58.pem + {0xef, 0x55, 0x12, 0x84, 0x71, 0x52, 0x32, 0xde, 0x92, 0xe2, 0x46, + 0xc3, 0x23, 0x32, 0x93, 0x62, 0xb1, 0x32, 0x49, 0x3b, 0xb1, 0x6b, + 0x58, 0x9e, 0x47, 0x75, 0x52, 0x0b, 0xeb, 0x87, 0x1a, 0x56}, + // 31c8fd37db9b56e708b03d1f01848b068c6da66f36fb5d82c008c6040fa3e133.pem + {0xf0, 0x34, 0xf6, 0x42, 0xca, 0x1d, 0x9e, 0x88, 0xe9, 0xef, 0xea, + 0xfc, 0xb1, 0x5c, 0x7c, 0x93, 0x7a, 0xa1, 0x9e, 0x04, 0xb0, 0x80, + 0xf2, 0x73, 0x35, 0xe1, 0xda, 0x70, 0xd1, 0xca, 0x12, 0x01}, + // 83618f932d6947744d5ecca299d4b2820c01483947bd16be814e683f7436be24.pem + {0xf2, 0xbb, 0xe0, 0x4c, 0x5d, 0xc7, 0x0d, 0x76, 0x3e, 0x89, 0xc5, + 0xa0, 0x52, 0x70, 0x48, 0xcd, 0x9e, 0xcd, 0x39, 0xeb, 0x62, 0x1e, + 0x20, 0x72, 0xff, 0x9a, 0x5f, 0x84, 0x32, 0x57, 0x1a, 0xa0}, + // 2a3699deca1e9fd099ba45de8489e205977c9f2a5e29d5dd747381eec0744d71.pem + {0xf3, 0x0e, 0x8f, 0x61, 0x01, 0x1d, 0x65, 0x87, 0x3c, 0xcb, 0x81, + 0xb4, 0x0f, 0xa6, 0x21, 0x97, 0x49, 0xb9, 0x94, 0xf0, 0x1f, 0xa2, + 0x4d, 0x02, 0x01, 0xd5, 0x21, 0xc2, 0x43, 0x56, 0x03, 0xca}, + // 0d90cd8e35209b4cefebdd62b644bed8eb55c74dddff26e75caf8ae70491f0bd.pem + {0xf5, 0x29, 0x3d, 0x47, 0xed, 0x38, 0xd4, 0xc3, 0x1b, 0x2d, 0x42, + 0xde, 0xe3, 0xb5, 0xb3, 0xac, 0xe9, 0x7c, 0xa2, 0x6c, 0xa2, 0xac, + 0x03, 0x65, 0xe3, 0x62, 0x2e, 0xe8, 0x02, 0x13, 0x1f, 0xbb}, + // 67ed4b703d15dc555f8c444b3a05a32579cb7599bd19c9babe10c584ea327ae0.pem + {0xfa, 0x00, 0xbe, 0xc7, 0x3d, 0xd9, 0x97, 0x95, 0xdf, 0x11, 0x62, + 0xc7, 0x89, 0x98, 0x70, 0x04, 0xc2, 0x6c, 0xbf, 0x90, 0xaf, 0x4d, + 0xb4, 0x42, 0xf6, 0x62, 0x20, 0xde, 0x41, 0x35, 0x4a, 0xc9}, +}; + +} // namespace detail +} // namespace certify +} // namespace boost + +#endif // BOOST_CERTIFY_DETAIL_SPKI_BLACKLIST_HPP diff --git a/src/libs/boost/certify/detail/spki_digest.hpp b/src/libs/boost/certify/detail/spki_digest.hpp new file mode 100644 index 00000000..d9e4ba9d --- /dev/null +++ b/src/libs/boost/certify/detail/spki_digest.hpp @@ -0,0 +1,51 @@ +#ifndef BOOST_CERTIFY_DETAIL_SPKI_DIGEST_HPP +#define BOOST_CERTIFY_DETAIL_SPKI_DIGEST_HPP + +#include +#include +#include +#include + +namespace boost +{ +namespace certify +{ +namespace detail +{ + +inline std::array +spki_digest(unsigned char* buf, std::size_t buflen) +{ + std::array digest; + SHA256(buf, buflen, digest.data()); + return digest; +} + +inline std::array +spki_digest(::X509* cert) +{ + struct pkey_ptr + { + ~pkey_ptr() + { + EVP_PKEY_free(ptr); + } + + EVP_PKEY* ptr; + }; + pkey_ptr pkey{X509_get_pubkey(cert)}; + + std::size_t buflen = i2d_PUBKEY(pkey.ptr, nullptr); + unsigned char* buf = nullptr; + auto p = boost::make_unique_noinit(buflen); + buf = p.get(); + auto ret = i2d_PUBKEY(pkey.ptr, &buf); + assert(ret); + + return spki_digest(p.get(), buflen); +} +} // namespace detail +} // namespace certify +} // namespace boost + +#endif // BOOST_CERTIFY_DETAIL_SPKI_DIGEST_HPP diff --git a/src/libs/boost/certify/detail/status_cache.hpp b/src/libs/boost/certify/detail/status_cache.hpp new file mode 100644 index 00000000..91531c85 --- /dev/null +++ b/src/libs/boost/certify/detail/status_cache.hpp @@ -0,0 +1,71 @@ +#ifndef BOOST_CERTIFY_STATUS_CACHE_HPP +#define BOOST_CERTIFY_STATUS_CACHE_HPP + +#include +#include +#include + +namespace boost +{ +namespace certify +{ + +enum class certificate_status +{ + valid, + revoked, + unknown +}; + +class status_cache +{ +public: + using clock_type = std::chrono::system_clock; + using time_point = std::chrono::system_clock::time_point; + + certificate_status check(std::string const& spki) const + { + std::lock_guard guard{mtx_}; + auto it = status_cache_.find(spki); + if (it == status_cache_.end()) + return certificate_status::unknown; + if (it->second.valid_until <= clock_type::now()) + return certificate_status::unknown; + + return it->second.status; + } + + void mark_valid(std::string const& spki, time_point valid_until) + { + std::lock_guard guard{mtx_}; + auto& value = status_cache_[spki]; + if (value.status == certificate_status::revoked || + valid_until <= clock_type::now()) + return; + + value.status = certificate_status::valid; + value.valid_until = valid_until; + } + + void revoke(std::string const& spki) + { + std::lock_guard guard{mtx_}; + auto& value = status_cache_[spki]; + value.status = certificate_status::revoked; + value.valid_until = time_point::max(); + } + +private: + struct value + { + time_point valid_until{time_point::min()}; + certificate_status status{certificate_status::unknown}; + }; + + std::unordered_map status_cache_; + mutable std::mutex mtx_; +}; +} // namespace certify +} // namespace boost + +#endif // BOOST_CERTIFY_STATUS_CACHE_HPP diff --git a/src/libs/boost/certify/extensions.hpp b/src/libs/boost/certify/extensions.hpp new file mode 100644 index 00000000..5be14495 --- /dev/null +++ b/src/libs/boost/certify/extensions.hpp @@ -0,0 +1,33 @@ +#ifndef BOOST_CERTIFY_EXTENSIONS_HPP +#define BOOST_CERTIFY_EXTENSIONS_HPP + +#include + +#include +#include + +namespace boost +{ +namespace certify +{ + +template +string_view +sni_hostname(asio::ssl::stream const& stream); + +template +void +sni_hostname(asio::ssl::stream& stream, + std::string const& hostname, + system::error_code& ec); + +template +void +sni_hostname(asio::ssl::stream& stream, + std::string const& hostname); + +} // namespace certify +} // namespace boost + +#include +#endif // BOOST_CERTIFY_EXTENSIONS_HPP diff --git a/src/libs/boost/certify/https_verification.hpp b/src/libs/boost/certify/https_verification.hpp new file mode 100644 index 00000000..83e45b2f --- /dev/null +++ b/src/libs/boost/certify/https_verification.hpp @@ -0,0 +1,67 @@ +#ifndef BOOST_CERTIFY_HTTPS_VERIFICATION_HPP +#define BOOST_CERTIFY_HTTPS_VERIFICATION_HPP + +#include + +#include +#include + +namespace boost +{ +namespace certify +{ +namespace detail +{ + +inline bool +verify_certificate_chain(::X509_STORE_CTX* ctx); + +inline void +set_server_hostname(::SSL* handle, + string_view hostname, + system::error_code& ec); + +extern "C" inline int +verify_server_certificates(int preverified, X509_STORE_CTX* ctx) noexcept; + +} // namespace detail +} // namespace certify +} // namespace boost + +#if BOOST_CERTIFY_USE_NATIVE_CERTIFICATE_STORE && BOOST_CERTIFY_HEADER_ONLY +#if BOOST_WINDOWS +#include +#elif __APPLE__ +#include +#else +#include +#endif +#endif // BOOST_CERTIFY_USE_NATIVE_CERTIFICATE_STORE && BOOST_CERTIFY_HEADER_ONLY + +namespace boost +{ +namespace certify +{ + +template +void +set_server_hostname(asio::ssl::stream& stream, + string_view hostname, + system::error_code& ec); + +template +void +set_server_hostname(asio::ssl::stream& stream, string_view hostname); + +void +enable_native_https_server_verification(asio::ssl::context& context); + +} // namespace certify +} // namespace boost + +#include +#if BOOST_CERTIFY_HEADER_ONLY +#include +#endif // BOOST_CERTIFY_HEADER_ONLY + +#endif // BOOST_CERTIFY_HTTPS_VERIFICATION_HPP diff --git a/src/libs/boost/certify/impl/crlset_parser.ipp b/src/libs/boost/certify/impl/crlset_parser.ipp new file mode 100644 index 00000000..d41fb7ff --- /dev/null +++ b/src/libs/boost/certify/impl/crlset_parser.ipp @@ -0,0 +1,111 @@ +#ifndef BOOST_CERTIFY_IMPL_CRLSET_PARSER_IPP +#define BOOST_CERTIFY_IMPL_CRLSET_PARSER_IPP + +#include + +namespace boost +{ + +namespace certify +{ + +inline char const* +crlset_parser_category::name() const noexcept +{ + return "crlset.parser"; +} + +inline std::string +crlset_parser_category::message(int ev) const +{ + switch (static_cast(ev)) + { + case crlset_error::header_length_truncated: + return "CRLSet header length truncated"; + case crlset_error::header_truncated: + return "CRLSet header truncated"; + case crlset_error::serial_truncated: + return "CRLSet serial truncated"; + default: + return "unknown"; + } +} + +inline system::error_code +make_error_code(crlset_error ev) +{ + return {static_cast(ev), + detail::inline_global::value}; +} + +inline std::vector +parse_crlset(asio::const_buffer b, system::error_code& ec) +{ + std::vector sets; + + endian::little_uint16_t header_len; + if (b.size() < sizeof(header_len)) + { + ec = crlset_error::header_length_truncated; + return sets; + } + std::memcpy(&header_len, b.data(), sizeof(header_len)); + b += sizeof(header_len); + + if (b.size() < header_len) + { + ec = crlset_error::header_truncated; + return sets; + } + b += header_len; + // TODO: Parse the header + + endian::little_uint32_t serial_count; + + while (b.size() > sizeof(crlset::parent_spki_hash) + sizeof(serial_count)) + { + sets.emplace_back(); + auto& set = sets.back(); + std::memcpy(set.parent_spki_hash.data(), + b.data(), + sizeof(crlset::parent_spki_hash)); + b += sizeof(crlset::parent_spki_hash); + + std::memcpy(&serial_count, b.data(), sizeof(serial_count)); + b += sizeof(serial_count); + + for (std::uint32_t n = serial_count; n > 0 && b.size() > 0; --n) + { + std::uint8_t serial_size; + std::memcpy(&serial_size, b.data(), sizeof(serial_size)); + b += sizeof(serial_size); + if (b.size() < serial_size) + { + ec = crlset_error::serial_truncated; + return sets; + } + auto* first = static_cast(b.data()); + auto* last = first + serial_size; + set.serials.emplace_back(first, last); + b += serial_size; + } + } + + return sets; +} + +inline std::vector +parse_crlset(boost::asio::const_buffer b) +{ + system::error_code ec; + auto ret = certify::parse_crlset(b, ec); + if (ec) + boost::throw_exception(system::system_error{ec}); + return ret; +} + +} // namespace certify + +} // namespace boost + +#endif // BOOST_CERTIFY_IMPL_CRLSET_PARSER_IPP diff --git a/src/libs/boost/certify/impl/extensions.hpp b/src/libs/boost/certify/impl/extensions.hpp new file mode 100644 index 00000000..712d3ac0 --- /dev/null +++ b/src/libs/boost/certify/impl/extensions.hpp @@ -0,0 +1,52 @@ +#ifndef BOOST_CERTIFY_IMPL_EXTENSIONS_HPP +#define BOOST_CERTIFY_IMPL_EXTENSIONS_HPP + +#include + +namespace boost +{ +namespace certify +{ + +template +string_view +sni_hostname(asio::ssl::stream const& stream) +{ + auto handle = + const_cast&>(stream).native_handle(); + auto* hostname = SSL_get_servername(handle, TLSEXT_NAMETYPE_host_name); + if (hostname == nullptr) + return string_view{}; + return {hostname}; +} + +template +void +sni_hostname(asio::ssl::stream& stream, + std::string const& hostname, + system::error_code& ec) +{ + auto ret = + SSL_set_tlsext_host_name(stream.native_handle(), hostname.c_str()); + if (ret == 0) + ec = {static_cast(::ERR_get_error()), + asio::error::get_ssl_category()}; + else + ec = {}; +} + +template +void +sni_hostname(asio::ssl::stream& stream, + std::string const& hostname) +{ + system::error_code ec; + sni_hostname(stream, hostname, ec); + if (ec) + boost::throw_exception(system::system_error{ec}); +} + +} // namespace certify +} // namespace boost + +#endif // BOOST_CERTIFY_IMPL_EXTENSIONS_HPP diff --git a/src/libs/boost/certify/impl/https_verification.hpp b/src/libs/boost/certify/impl/https_verification.hpp new file mode 100644 index 00000000..0c2561d4 --- /dev/null +++ b/src/libs/boost/certify/impl/https_verification.hpp @@ -0,0 +1,33 @@ +#ifndef BOOST_CERTIFY_IMPL_HTTPS_VERIFICATION_HPP +#define BOOST_CERTIFY_IMPL_HTTPS_VERIFICATION_HPP + +#include + +namespace boost +{ +namespace certify +{ + +template +void +set_server_hostname(asio::ssl::stream& stream, + string_view hostname, + system::error_code& ec) +{ + detail::set_server_hostname(stream.native_handle(), hostname, ec); +} + +template +void +set_server_hostname(asio::ssl::stream& stream, string_view hostname) +{ + system::error_code ec; + certify::set_server_hostname(stream, hostname, ec); + if (ec) + boost::throw_exception(system::system_error{ec}); +} + +} // namespace certify +} // namespace boost + +#endif // BOOST_CERTIFY_IMPL_HTTPS_VERIFICATION_HPP diff --git a/src/libs/boost/certify/impl/https_verification.ipp b/src/libs/boost/certify/impl/https_verification.ipp new file mode 100644 index 00000000..78c05061 --- /dev/null +++ b/src/libs/boost/certify/impl/https_verification.ipp @@ -0,0 +1,70 @@ +#ifndef BOOST_CERTIFY_IMPL_HTTPS_VERIFICATION_IPP +#define BOOST_CERTIFY_IMPL_HTTPS_VERIFICATION_IPP + +#include + +#include +#include +#include + +namespace boost +{ +namespace certify +{ +namespace detail +{ + +extern "C" BOOST_CERTIFY_DECL int +verify_server_certificates(int preverified, X509_STORE_CTX* ctx) noexcept +{ + if (preverified == 1) + return true; + + auto const err = ::X509_STORE_CTX_get_error(ctx); + if ((err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN || + err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || + err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) && + detail::verify_certificate_chain(ctx)) + { + ::X509_STORE_CTX_set_error(ctx, X509_V_OK); + return true; + } + + return false; +} + +BOOST_CERTIFY_DECL void +set_server_hostname(::X509_VERIFY_PARAM* param, string_view hostname, system::error_code& ec) +{ + ::X509_VERIFY_PARAM_set_hostflags(param, + X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + // TODO(djarek): OpenSSL doesn't report an error here? + if (!X509_VERIFY_PARAM_set1_host(param, hostname.data(), hostname.size())) + ec = {static_cast(::ERR_get_error()), + asio::error::get_ssl_category()}; + else + ec = {}; +} + +BOOST_CERTIFY_DECL void +set_server_hostname(::SSL* handle, string_view hostname, system::error_code& ec) +{ + auto* param = ::SSL_get0_param(handle); + set_server_hostname(param, hostname, ec); +} + + +} // namespace detail + +BOOST_CERTIFY_DECL void +enable_native_https_server_verification(asio::ssl::context& context) +{ + ::SSL_CTX_set_verify(context.native_handle(), + ::SSL_CTX_get_verify_mode(context.native_handle()), + &detail::verify_server_certificates); +} + +} // namespace certify +} // namespace boost + +#endif // BOOST_CERTIFY_IMPL_HTTPS_VERIFICATION_IPP diff --git a/src/libs/boost/certify/src.hpp b/src/libs/boost/certify/src.hpp new file mode 100644 index 00000000..df83a1d9 --- /dev/null +++ b/src/libs/boost/certify/src.hpp @@ -0,0 +1,15 @@ +#include + +#if BOOST_CERTIFY_HEADER_ONLY +#error src.hpp must not be used in header-only mode, define BOOST_CERTIFY_SEPARATE_COMPILATION +#endif // BOOST_CERTIFY_HEADER_ONLY + +#if BOOST_WINDOWS +#include +#elif __APPLE__ +#include +#else +#include +#endif + +#include diff --git a/src/libs/json.hpp b/src/libs/json.hpp index a70aaf8c..a66a4fcc 100644 --- a/src/libs/json.hpp +++ b/src/libs/json.hpp @@ -45,38 +45,22 @@ SOFTWARE. #include // string, stoi, to_string #include // declval, forward, move, pair, swap #include // vector - -// #include - - -#include - -// #include - - -#include // transform #include // array +#include // valarray #include // forward_list #include // inserter, front_inserter, end #include // map -#include // string #include // tuple, make_tuple #include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible -#include // unordered_map -#include // pair, declval -#include // valarray - -// #include - - #include // exception #include // runtime_error -#include // to_string +// #include +// #include // #include +// #include - -#include // size_t +#include namespace nlohmann { @@ -3764,7 +3748,7 @@ void from_json(const BasicJsonType& j, std::map& template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> -void from_json(const BasicJsonType& j, std::unordered_map& m) +void from_json(const BasicJsonType& j, boost::unordered_flat_map& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -16913,7 +16897,7 @@ class basic_json described below. @tparam ObjectType the container to store objects (e.g., `std::map` or - `std::unordered_map`) + `boost::unordered_flat_map`) @tparam StringType the type of the keys or names (e.g., `std::string`). The comparison function `std::less` is used to order elements inside the container. @@ -17873,7 +17857,7 @@ class basic_json `std::multiset`, and `std::unordered_multiset` with a `value_type` from which a @ref basic_json value can be constructed. - **objects**: @ref object_t and all kinds of compatible associative - containers such as `std::map`, `std::unordered_map`, `std::multimap`, + containers such as `std::map`, `boost::unordered_flat_map`, `std::multimap`, and `std::unordered_multimap` with a `key_type` compatible to @ref string_t and a `value_type` from which a @ref basic_json value can be constructed. @@ -19395,7 +19379,7 @@ class basic_json to other types. There a few things to note: (1) Floating-point numbers can be converted to integers\, (2) A JSON array can be converted to a standard `std::vector`\, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map`.,get__ValueType_const} @since version 2.1.0 @@ -19493,7 +19477,7 @@ class basic_json to other types. There a few things to note: (1) Floating-point numbers can be converted to integers\, (2) A JSON array can be converted to a standard `std::vector`\, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map`.,get_to} @since version 3.3.0 @@ -19701,7 +19685,7 @@ class basic_json to other types. There a few things to note: (1) Floating-point numbers can be converted to integers\, (2) A JSON array can be converted to a standard `std::vector`\, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map`.,operator__ValueType} @since version 1.0.0 diff --git a/src/libs/wyhash.h b/src/libs/wyhash.h new file mode 100644 index 00000000..7df33d4d --- /dev/null +++ b/src/libs/wyhash.h @@ -0,0 +1,231 @@ +// This is free and unencumbered software released into the public domain under The Unlicense (http://unlicense.org/) +// main repo: https://github.com/wangyi-fudan/wyhash +// author: 王一 Wang Yi +// contributors: Reini Urban, Dietrich Epp, Joshua Haberman, Tommy Ettinger, Daniel Lemire, Otmar Ertl, cocowalla, leo-yuriev, Diego Barrios Romero, paulie-g, dumblob, Yann Collet, ivte-ms, hyb, James Z.M. Gao, easyaspi314 (Devin), TheOneric + +/* quick example: + string s="fjsakfdsjkf"; + uint64_t hash=wyhash(s.c_str(), s.size(), 0, _wyp); +*/ + +#ifndef wyhash_final_version_4 +#define wyhash_final_version_4 + +#ifndef WYHASH_CONDOM +//protections that produce different results: +//1: normal valid behavior +//2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication" +#define WYHASH_CONDOM 1 +#endif + +#ifndef WYHASH_32BIT_MUM +//0: normal version, slow on 32 bit systems +//1: faster on 32 bit systems but produces different results, incompatible with wy2u0k function +#define WYHASH_32BIT_MUM 0 +#endif + +//includes +#include +#include +#if defined(_MSC_VER) && defined(_M_X64) + #include + #pragma intrinsic(_umul128) +#endif + +//likely and unlikely macros +#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) + #define _likely_(x) __builtin_expect(x,1) + #define _unlikely_(x) __builtin_expect(x,0) +#else + #define _likely_(x) (x) + #define _unlikely_(x) (x) +#endif + +//128bit multiply function +static inline uint64_t _wyrot(uint64_t x) { return (x>>32)|(x<<32); } +static inline void _wymum(uint64_t *A, uint64_t *B){ +#if(WYHASH_32BIT_MUM) + uint64_t hh=(*A>>32)*(*B>>32), hl=(*A>>32)*(uint32_t)*B, lh=(uint32_t)*A*(*B>>32), ll=(uint64_t)(uint32_t)*A*(uint32_t)*B; + #if(WYHASH_CONDOM>1) + *A^=_wyrot(hl)^hh; *B^=_wyrot(lh)^ll; + #else + *A=_wyrot(hl)^hh; *B=_wyrot(lh)^ll; + #endif +#elif defined(__SIZEOF_INT128__) + __uint128_t r=*A; r*=*B; + #if(WYHASH_CONDOM>1) + *A^=(uint64_t)r; *B^=(uint64_t)(r>>64); + #else + *A=(uint64_t)r; *B=(uint64_t)(r>>64); + #endif +#elif defined(_MSC_VER) && defined(_M_X64) + #if(WYHASH_CONDOM>1) + uint64_t a, b; + a=_umul128(*A,*B,&b); + *A^=a; *B^=b; + #else + *A=_umul128(*A,*B,B); + #endif +#else + uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo; + uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t>32)+(rm1>>32)+c; + #if(WYHASH_CONDOM>1) + *A^=lo; *B^=hi; + #else + *A=lo; *B=hi; + #endif +#endif +} + +//multiply and xor mix function, aka MUM +static inline uint64_t _wymix(uint64_t A, uint64_t B){ _wymum(&A,&B); return A^B; } + +//endian macros +#ifndef WYHASH_LITTLE_ENDIAN + #if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + #define WYHASH_LITTLE_ENDIAN 1 + #elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + #define WYHASH_LITTLE_ENDIAN 0 + #else + #warning could not determine endianness! Falling back to little endian. + #define WYHASH_LITTLE_ENDIAN 1 + #endif +#endif + +//read functions +#if (WYHASH_LITTLE_ENDIAN) +static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;} +static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v;} +#elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) +static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);} +static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return __builtin_bswap32(v);} +#elif defined(_MSC_VER) +static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return _byteswap_uint64(v);} +static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return _byteswap_ulong(v);} +#else +static inline uint64_t _wyr8(const uint8_t *p) { + uint64_t v; memcpy(&v, p, 8); + return (((v >> 56) & 0xff)| ((v >> 40) & 0xff00)| ((v >> 24) & 0xff0000)| ((v >> 8) & 0xff000000)| ((v << 8) & 0xff00000000)| ((v << 24) & 0xff0000000000)| ((v << 40) & 0xff000000000000)| ((v << 56) & 0xff00000000000000)); +} +static inline uint64_t _wyr4(const uint8_t *p) { + uint32_t v; memcpy(&v, p, 4); + return (((v >> 24) & 0xff)| ((v >> 8) & 0xff00)| ((v << 8) & 0xff0000)| ((v << 24) & 0xff000000)); +} +#endif +static inline uint64_t _wyr3(const uint8_t *p, size_t k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];} +//wyhash main function +static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const uint64_t *secret){ + const uint8_t *p=(const uint8_t *)key; seed^=_wymix(seed^secret[0],secret[1]); uint64_t a, b; + if(_likely_(len<=16)){ + if(_likely_(len>=4)){ a=(_wyr4(p)<<32)|_wyr4(p+((len>>3)<<2)); b=(_wyr4(p+len-4)<<32)|_wyr4(p+len-4-((len>>3)<<2)); } + else if(_likely_(len>0)){ a=_wyr3(p,len); b=0;} + else a=b=0; + } + else{ + size_t i=len; + if(_unlikely_(i>48)){ + uint64_t see1=seed, see2=seed; + do{ + seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed); + see1=_wymix(_wyr8(p+16)^secret[2],_wyr8(p+24)^see1); + see2=_wymix(_wyr8(p+32)^secret[3],_wyr8(p+40)^see2); + p+=48; i-=48; + }while(_likely_(i>48)); + seed^=see1^see2; + } + while(_unlikely_(i>16)){ seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed); i-=16; p+=16; } + a=_wyr8(p+i-16); b=_wyr8(p+i-8); + } + a^=secret[1]; b^=seed; _wymum(&a,&b); + return _wymix(a^secret[0]^len,b^secret[1]); +} + +//the default secret parameters +static const uint64_t _wyp[4] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull}; + +//a useful 64bit-64bit mix function to produce deterministic pseudo random numbers that can pass BigCrush and PractRand +static inline uint64_t wyhash64(uint64_t A, uint64_t B){ A^=0xa0761d6478bd642full; B^=0xe7037ed1a0b428dbull; _wymum(&A,&B); return _wymix(A^0xa0761d6478bd642full,B^0xe7037ed1a0b428dbull);} + +//The wyrand PRNG that pass BigCrush and PractRand +static inline uint64_t wyrand(uint64_t *seed){ *seed+=0xa0761d6478bd642full; return _wymix(*seed,*seed^0xe7037ed1a0b428dbull);} + +//convert any 64 bit pseudo random numbers to uniform distribution [0,1). It can be combined with wyrand, wyhash64 or wyhash. +static inline double wy2u01(uint64_t r){ const double _wynorm=1.0/(1ull<<52); return (r>>12)*_wynorm;} + +//convert any 64 bit pseudo random numbers to APPROXIMATE Gaussian distribution. It can be combined with wyrand, wyhash64 or wyhash. +static inline double wy2gau(uint64_t r){ const double _wynorm=1.0/(1ull<<20); return ((r&0x1fffff)+((r>>21)&0x1fffff)+((r>>42)&0x1fffff))*_wynorm-3.0;} + +#ifdef WYTRNG +#include +//The wytrand true random number generator, passed BigCrush. +static inline uint64_t wytrand(uint64_t *seed){ + struct timeval t; gettimeofday(&t,0); + uint64_t teed=(((uint64_t)t.tv_sec)<<32)|t.tv_usec; + teed=_wymix(teed^_wyp[0],*seed^_wyp[1]); + *seed=_wymix(teed^_wyp[0],_wyp[2]); + return _wymix(*seed,*seed^_wyp[3]); +} +#endif + +#if(!WYHASH_32BIT_MUM) +//fast range integer random number generation on [0,k) credit to Daniel Lemire. May not work when WYHASH_32BIT_MUM=1. It can be combined with wyrand, wyhash64 or wyhash. +static inline uint64_t wy2u0k(uint64_t r, uint64_t k){ _wymum(&r,&k); return k; } +#endif + +//make your own secret +static inline void make_secret(uint64_t seed, uint64_t *secret){ + uint8_t c[] = {15, 23, 27, 29, 30, 39, 43, 45, 46, 51, 53, 54, 57, 58, 60, 71, 75, 77, 78, 83, 85, 86, 89, 90, 92, 99, 101, 102, 105, 106, 108, 113, 114, 116, 120, 135, 139, 141, 142, 147, 149, 150, 153, 154, 156, 163, 165, 166, 169, 170, 172, 177, 178, 180, 184, 195, 197, 198, 201, 202, 204, 209, 210, 212, 216, 225, 226, 228, 232, 240 }; + for(size_t i=0;i<4;i++){ + uint8_t ok; + do{ + ok=1; secret[i]=0; + for(size_t j=0;j<64;j+=8) secret[i]|=((uint64_t)c[wyrand(&seed)%sizeof(c)])<> 1) & 0x5555555555555555; + x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333); + x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0f; + x = (x * 0x0101010101010101) >> 56; + if(x!=32){ ok=0; break; } +#endif + } + }while(!ok); + } +} + +#endif + +/* The Unlicense +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ diff --git a/src/libs/zpp_bits.h b/src/libs/zpp_bits.h new file mode 100644 index 00000000..2b26c340 --- /dev/null +++ b/src/libs/zpp_bits.h @@ -0,0 +1,5661 @@ +#ifndef ZPP_BITS_H +#define ZPP_BITS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if __has_include("zpp_throwing.h") +#include "zpp_throwing.h" +#endif + +#ifdef __cpp_exceptions +#include +#endif + +#ifndef ZPP_BITS_AUTODETECT_MEMBERS_MODE +#define ZPP_BITS_AUTODETECT_MEMBERS_MODE (0) +#endif + +#ifndef ZPP_BITS_INLINE +#if defined __clang__ || defined __GNUC__ +#define ZPP_BITS_INLINE __attribute__((always_inline)) +#if defined __clang__ +#define ZPP_BITS_CONSTEXPR_INLINE_LAMBDA __attribute__((always_inline)) constexpr +#else +#define ZPP_BITS_CONSTEXPR_INLINE_LAMBDA constexpr __attribute__((always_inline)) +#endif +#elif defined _MSC_VER +#define ZPP_BITS_INLINE [[msvc::forceinline]] +#define ZPP_BITS_CONSTEXPR_INLINE_LAMBDA /*constexpr*/ [[msvc::forceinline]] +#endif +#else // ZPP_BITS_INLINE +#define ZPP_BITS_CONSTEXPR_INLINE_LAMBDA constexpr +#endif // ZPP_BITS_INLINE + +#if defined ZPP_BITS_INLINE_MODE && !ZPP_BITS_INLINE_MODE +#undef ZPP_BITS_INLINE +#define ZPP_BITS_INLINE +#undef ZPP_BITS_CONSTEXPR_INLINE_LAMBDA +#define ZPP_BITS_CONSTEXPR_INLINE_LAMBDA constexpr +#endif + +#ifndef ZPP_BITS_INLINE_DECODE_VARINT +#define ZPP_BITS_INLINE_DECODE_VARINT (0) +#endif + +namespace zpp::bits +{ +using default_size_type = std::uint32_t; + +#ifndef __cpp_lib_bit_cast +namespace std +{ +using namespace ::std; +template && + is_trivially_copyable_v>> +constexpr ToType bit_cast(FromType const & from) noexcept +{ + return __builtin_bit_cast(ToType, from); +} +} // namespace std +#endif + +enum class kind +{ + in, + out +}; + +template ::max()> +struct members +{ + constexpr static std::size_t value = Count; +}; + +template ::max()> +struct protocol +{ + constexpr static auto value = Protocol; + constexpr static auto members = Members; +}; + +template +struct serialization_id +{ + constexpr static auto value = Id; +}; + +constexpr auto success(std::errc code) +{ + return std::errc{} == code; +} + +constexpr auto failure(std::errc code) +{ + return std::errc{} != code; +} + +struct [[nodiscard]] errc +{ + constexpr errc(std::errc code = {}) : code(code) + { + } + +#if __has_include("zpp_throwing.h") + constexpr zpp::throwing operator co_await() const + { + if (failure(code)) [[unlikely]] { + return code; + } + return zpp::void_v; + } +#endif + + constexpr operator std::errc() const + { + return code; + } + + constexpr void or_throw() const + { + if (failure(code)) [[unlikely]] { +#ifdef __cpp_exceptions + throw std::system_error(std::make_error_code(code)); +#else + std::abort(); +#endif + } + } + + std::errc code; +}; + +constexpr auto success(errc code) +{ + return std::errc{} == code; +} + +constexpr auto failure(errc code) +{ + return std::errc{} != code; +} + +struct access +{ + struct any + { + template + operator Type(); + }; + + template + constexpr static auto make(auto &&... arguments) + { + return Item{std::forward(arguments)...}; + } + + template + constexpr static auto placement_new(void * address, + auto &&... arguments) + { + return ::new (address) + Item(std::forward(arguments)...); + } + + template + constexpr static auto make_unique(auto &&... arguments) + { + return std::unique_ptr( + new Item(std::forward(arguments)...)); + } + + template + constexpr static void destruct(Item & item) + { + item.~Item(); + } + + template + constexpr static auto number_of_members(); + + constexpr static auto max_visit_members = 50; + + ZPP_BITS_INLINE constexpr static decltype(auto) visit_members( + auto && object, + auto && visitor) requires(0 <= + number_of_members()) && + (number_of_members() <= max_visit_members) + { + constexpr auto count = number_of_members(); + + // clang-format off + if constexpr (count == 0) { return visitor(); } else if constexpr (count == 1) { auto && [a1] = object; return visitor(a1); } else if constexpr (count == 2) { auto && [a1, a2] = object; return visitor(a1, a2); /*......................................................................................................................................................................................................................................................................*/ } else if constexpr (count == 3) { auto && [a1, a2, a3] = object; return visitor(a1, a2, a3); } else if constexpr (count == 4) { auto && [a1, a2, a3, a4] = object; return visitor(a1, a2, a3, a4); } else if constexpr (count == 5) { auto && [a1, a2, a3, a4, a5] = object; return visitor(a1, a2, a3, a4, a5); } else if constexpr (count == 6) { auto && [a1, a2, a3, a4, a5, a6] = object; return visitor(a1, a2, a3, a4, a5, a6); } else if constexpr (count == 7) { auto && [a1, a2, a3, a4, a5, a6, a7] = object; return visitor(a1, a2, a3, a4, a5, a6, a7); } else if constexpr (count == 8) { auto && [a1, a2, a3, a4, a5, a6, a7, a8] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8); } else if constexpr (count == 9) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9); } else if constexpr (count == 10) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); } else if constexpr (count == 11) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); } else if constexpr (count == 12) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); } else if constexpr (count == 13) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); } else if constexpr (count == 14) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14); } else if constexpr (count == 15) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); } else if constexpr (count == 16) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16); } else if constexpr (count == 17) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17); } else if constexpr (count == 18) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18); } else if constexpr (count == 19) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19); } else if constexpr (count == 20) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); } else if constexpr (count == 21) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21); } else if constexpr (count == 22) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22); } else if constexpr (count == 23) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23); } else if constexpr (count == 24) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24); } else if constexpr (count == 25) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25); } else if constexpr (count == 26) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26); } else if constexpr (count == 27) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27); } else if constexpr (count == 28) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28); } else if constexpr (count == 29) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29); } else if constexpr (count == 30) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30); } else if constexpr (count == 31) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31); } else if constexpr (count == 32) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32); } else if constexpr (count == 33) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33); } else if constexpr (count == 34) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34); } else if constexpr (count == 35) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35); } else if constexpr (count == 36) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36); } else if constexpr (count == 37) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37); } else if constexpr (count == 38) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38); } else if constexpr (count == 39) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39); } else if constexpr (count == 40) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40); } else if constexpr (count == 41) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41); } else if constexpr (count == 42) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42); } else if constexpr (count == 43) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43); } else if constexpr (count == 44) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44); } else if constexpr (count == 45) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45); } else if constexpr (count == 46) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46); } else if constexpr (count == 47) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47); } else if constexpr (count == 48) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48); } else if constexpr (count == 49) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49); } else if constexpr (count == 50) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50); + // Calls the visitor above with all data members of object. + } + // clang-format on + } + + template + constexpr static decltype(auto) + visit_members_types(auto && visitor) requires(0 <= number_of_members()) && + (number_of_members() <= max_visit_members) + + { + using type = std::remove_cvref_t; + constexpr auto count = number_of_members(); + + // clang-format off + if constexpr (count == 0) { return visitor.template operator()<>(); } else if constexpr (count == 1) { auto f = [&](auto && object) { auto && [a1] = object; return visitor.template operator()(); }; /*......................................................................................................................................................................................................................................................................*/ return decltype(f(std::declval()))(); } else if constexpr (count == 2) { auto f = [&](auto && object) { auto && [a1, a2] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 3) { auto f = [&](auto && object) { auto && [a1, a2, a3] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 4) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 5) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 6) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 7) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 8) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 9) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 10) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 11) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 12) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 13) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 14) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 15) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 16) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 17) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 18) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 19) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 20) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 21) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 22) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 23) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 24) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 25) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 26) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 27) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 28) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 29) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 30) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 31) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 32) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 33) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 34) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 35) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 36) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 37) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 38) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 39) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 40) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 41) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 42) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 43) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 44) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 45) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 46) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 47) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 48) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 49) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 50) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); + // Returns visitor.template operator()(); + } + // clang-format on + } + + constexpr static auto try_serialize(auto && item) + { + if constexpr (requires { serialize(item); }) { + return serialize(item); + } + } + + template + constexpr static auto has_serialize() + { + return requires { + requires std::same_as< + typename std::remove_cvref_t::serialize, + members< + std::remove_cvref_t::serialize::value>>; + } || + requires(Type && item) { + requires std::same_as< + std::remove_cvref_t, + members::value>>; + } || + requires { + requires std::same_as< + typename std::remove_cvref_t::serialize, + protocol< + std::remove_cvref_t::serialize::value, + std::remove_cvref_t::serialize::members>>; + } || + requires(Type && item) { + requires std::same_as< + std::remove_cvref_t, + protocol< + std::remove_cvref_t::value, + std::remove_cvref_t::members>>; + } || + requires(Type && item, Archive && archive) { + std::remove_cvref_t::serialize(archive, item); + } || requires(Type && item, Archive && archive) { + serialize(archive, item); + }; + } + + template + constexpr static auto has_explicit_serialize() + { + return requires(Type && item, Archive && archive) + { + std::remove_cvref_t::serialize(archive, item); + } + || requires(Type && item, Archive && archive) + { + serialize(archive, item); + }; + } + + template + struct byte_serializable_visitor; + + template + constexpr static auto byte_serializable(); + + template + struct endian_independent_byte_serializable_visitor; + + template + constexpr static auto endian_independent_byte_serializable(); + + template + struct self_referencing_visitor; + + template + constexpr static auto self_referencing(); + + template + constexpr static auto has_protocol() + { + return requires + { + requires std::same_as< + typename std::remove_cvref_t::serialize, + protocol::serialize::value, + std::remove_cvref_t::serialize::members>>; + } + || requires(Type && item) + { + requires std::same_as< + std::remove_cvref_t, + protocol< + std::remove_cvref_t::value, + std::remove_cvref_t::members>>; + }; + } + + template + constexpr static auto get_protocol() + { + if constexpr ( + requires { + requires std::same_as< + typename std::remove_cvref_t::serialize, + protocol< + std::remove_cvref_t::serialize::value, + std::remove_cvref_t::serialize::members>>; + }) { + return std::remove_cvref_t::serialize::value; + } else if constexpr ( + requires(Type && item) { + requires std::same_as< + std::remove_cvref_t, + protocol::value, + std::remove_cvref_t::members>>; + }) { + return std::remove_cvref_t()))>::value; + } else { + static_assert(!sizeof(Type)); + } + } +}; + +template +struct destructor_guard +{ + ZPP_BITS_INLINE constexpr ~destructor_guard() + { + access::destruct(object); + } + + Type & object; +}; + +template +destructor_guard(Type) -> destructor_guard; + +namespace traits +{ +template +struct is_unique_ptr : std::false_type +{ +}; + +template +struct is_unique_ptr>> + : std::true_type +{ +}; + +template +struct is_shared_ptr : std::false_type +{ +}; + +template +struct is_shared_ptr> : std::true_type +{ +}; + +template +struct variant_impl; + +template typename Variant> +struct variant_impl> +{ + using variant_type = Variant; + + template + constexpr static auto get_id() + { + if constexpr (Index == CurrentIndex) { + if constexpr (requires { + requires std::same_as< + serialization_id< + FirstType::serialize_id::value>, + typename FirstType::serialize_id>; + }) { + return FirstType::serialize_id::value; + } else if constexpr ( + requires { + requires std::same_as< + serialization_id()))::value>, + decltype(serialize_id(std::declval()))>; + }) { + return decltype(serialize_id( + std::declval()))::value; + } else { + return std::byte{Index}; + } + } else { + return get_id(); + } + } + + template + constexpr static auto id() + { + return get_id(); + } + + template + ZPP_BITS_INLINE constexpr static auto id(auto index) + { + if constexpr (CurrentIndex == (sizeof...(Types) - 1)) { + return id(); + } else { + if (index == CurrentIndex) { + return id(); + } else { + return id(index); + } + } + } + + template + constexpr static std::size_t index() + { + static_assert(CurrentIndex < sizeof...(Types)); + + if constexpr (variant_impl::id() == Id) { + return CurrentIndex; + } else { + return index(); + } + } + + template + ZPP_BITS_INLINE constexpr static std::size_t index(auto && id) + { + if constexpr (CurrentIndex == sizeof...(Types)) { + return std::numeric_limits::max(); + } else { + if (variant_impl::id() == id) { + return CurrentIndex; + } else { + return index(id); + } + } + return std::numeric_limits::max(); + } + + template + constexpr static auto unique_ids(std::index_sequence, + std::index_sequence) + { + auto unique_among_rest = []() + { + return (... && ((LeftIndex == RightIndices) || + (LeftId != id()))); + }; + return (... && unique_among_rest.template + operator()()>()); + } + + template + constexpr static auto + same_id_types(std::index_sequence, + std::index_sequence) + { + auto same_among_rest = []() + { + return (... && + (std::same_as< + std::remove_cv_t, + std::remove_cv_t())>>)); + }; + return (... && same_among_rest.template + operator()()>()); + } + + template + constexpr static std::size_t index_by_type(std::index_sequence) + { + return ((std::same_as< + Type, + std::variant_alternative_t> * + Indices) + + ...); + } + + template + constexpr static std::size_t index_by_type() + { + return index_by_type( + std::make_index_sequence>{}); + } + + using id_type = decltype(id<0>()); +}; + +template +struct variant_checker; + +template typename Variant> +struct variant_checker> +{ + using type = variant_impl>; + static_assert( + type::unique_ids(std::make_index_sequence(), + std::make_index_sequence())); + static_assert( + type::same_id_types(std::make_index_sequence(), + std::make_index_sequence())); +}; + +template +using variant = typename variant_checker::type; + +template +struct tuple; + +template typename Tuple> +struct tuple> +{ + template + ZPP_BITS_INLINE constexpr static auto visit(auto && tuple, auto && index, auto && visitor) + { + if constexpr (Index + 1 == sizeof...(Types)) { + return visitor(std::get(tuple)); + } else { + if (Index == index) { + return visitor(std::get(tuple)); + } + return visit(tuple, index, visitor); + } + } +}; + +template +struct visitor +{ + using byte_type = std::byte; + using view_type = std::span; + + static constexpr bool resizable = false; + + constexpr auto operator()(auto && ... arguments) const + { + if constexpr (requires { + visitor(std::forward( + arguments)...); + }) { + return visitor( + std::forward(arguments)...); + } else { + return sizeof...(arguments); + } + } + + template + constexpr auto serialize_one(auto && ... arguments) const + { + return (*this)(std::forward(arguments)...); + } + + template + constexpr auto serialize_many(auto && ... arguments) const + { + return (*this)(std::forward(arguments)...); + } + + constexpr static auto kind() + { + return kind::out; + } + + std::span data(); + std::span remaining_data(); + std::span processed_data(); + std::size_t position() const; + std::size_t & position(); + errc enlarge_for(std::size_t); + void reset(std::size_t = 0); + + [[no_unique_address]] Visitor visitor; +}; + +constexpr auto get_default_size_type() +{ + return default_size_type{}; +} + +constexpr auto get_default_size_type(auto option, auto... options) +{ + if constexpr (requires { + typename decltype(option)::default_size_type; + }) { + if constexpr (std::is_void_v) { + return std::monostate{}; + } else { + return typename decltype(option)::default_size_type{}; + } + } else { + return get_default_size_type(options...); + } +} + +template +using default_size_type_t = + std::conditional_t()...))>, + void, + decltype(get_default_size_type( + std::declval()...))>; + +template +constexpr auto get_alloc_limit() +{ + if constexpr (requires { + std::remove_cvref_t< + Option>::alloc_limit_value; + }) { + return std::remove_cvref_t