diff --git a/.dockerignore b/.dockerignore index d6492c09d..3968d7088 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,11 @@ +.git +.vscode +**/.env.local +**/dfuse-transactions*.json +**/dist +**/node_modules +**/state build build-dbg build-docker -.vscode -**/.env.local +!build/eden-micro-chain.wasm diff --git a/.github/ccache.conf b/.github/ccache.conf new file mode 100644 index 000000000..64828568b --- /dev/null +++ b/.github/ccache.conf @@ -0,0 +1,2 @@ +max_size = 600M +log_file = /__w/Eden/Eden/ccache.log diff --git a/.github/workflows/box.yml b/.github/workflows/box.yml deleted file mode 100644 index 369b0e17d..000000000 --- a/.github/workflows/box.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: box - -on: - push: - branches: - - main - - "test/*" - pull_request: - types: [assigned, opened, synchronize, reopened, labeled] - paths: - - "docker/eden-box.Dockerfile" - - ".eslintignore" - - ".eslintrc.js" - - ".prettierrc.json" - - "lerna.json" - - "packages/box/**" - - "package.json" - - "tsconfig.build.json" - - "tsconfig.json" - - "yarn.lock" - -jobs: - box-e2e: - name: Eden Box E2E Tests - runs-on: ubuntu-latest - - steps: - - name: โœ… Checkout code - uses: actions/checkout@v2 - - - name: ๐Ÿ›  Build and Start Box - run: | - yarn - yarn build --stream - cd packages/box - yarn start & - - - name: ๐Ÿงช Run E2E - # TODO: add real E2E tests... for now it's just a shameless curl ping - run: | - curl localhost:3032 - - box-build: - needs: box-e2e - name: Build Eden Box - runs-on: ubuntu-latest - - steps: - - name: โœ… Checkout code - uses: actions/checkout@v2 - - - name: Image Preparation - id: prep - run: | - REGISTRY="ghcr.io" - IMAGE="${REGISTRY}/${{ github.repository_owner }}/eden-box" - TAGS="${IMAGE}:${{ github.sha }}" - if [[ $GITHUB_REF == ref/head/master ]]; then - TAGS="${TAGS},${IMAGE}:latest" - fi - echo ::set-output name=tags::${TAGS,,} - - - name: Showtag - id: showtag - run: echo ${{ steps.prep.outputs.tags }} - - - name: Docker Buildx setup - uses: docker/setup-buildx-action@v1 - - - name: Login in to registry - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: ๐Ÿ›  Build & Publish Image - uses: docker/build-push-action@v2 - with: - # push: true # TODO: follow up on this... https://github.community/t/403-error-on-container-registry-push-from-github-action/173071/5 - file: docker/eden-box.Dockerfile - tags: ${{ steps.prep.outputs.tags }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7d9c3cd88..4b731e131 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: build eosio contracts +name: Build on: push: @@ -8,6 +8,9 @@ on: pull_request: types: [assigned, opened, synchronize, reopened, labeled] paths: + - ".github/workflows/build.yml" + + # C++ - "CMakeLists.txt" - "contracts/**" - "external/CMakeLists.txt" @@ -15,19 +18,53 @@ on: - "native/**" - "programs/**" - "wasm/**" - - ".github/workflows/build.yml" + # box, webapp + - ".eslintignore" + - ".eslintrc.js" + - ".prettierrc.json" + - "lerna.json" + - "package.json" + - "packages/common/**" + - "tsconfig.build.json" + - "tsconfig.json" + - "yarn.lock" + + # box + - "docker/eden-box.Dockerfile" + - "packages/box/**" + + # webapp + - "docker/eden-webapp.Dockerfile" + - "packages/webapp/**" jobs: - build: - name: Build Eden Community Contracts and WASMs + build-cpp: + name: Build C++ runs-on: ubuntu-latest - container: ghcr.io/eoscommunity/eden-builder:latest + container: ghcr.io/eoscommunity/eden-builder:sub-chain steps: + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + src: + - ".github/workflows/build.yml" + + - "CMakeLists.txt" + - "contracts/**" + - "external/CMakeLists.txt" + - "libraries/**" + - "native/**" + - "programs/**" + - "wasm/**" + - name: โœ… Checkout code + if: steps.filter.outputs.src == 'true' uses: actions/checkout@v2 - name: Prepare ccache timestamp + if: steps.filter.outputs.src == 'true' id: ccache_cache_timestamp shell: cmake -P {0} run: | @@ -35,21 +72,28 @@ jobs: message("::set-output name=timestamp::${current_date}") - name: show_cache + if: steps.filter.outputs.src == 'true' id: show_cache - run: echo "${{ runner.os }}-ccache-${{ steps.ccache_cache_timestamp.outputs.timestamp }}" + run: echo "${{ runner.os }}-ccache_whole-${{ steps.ccache_cache_timestamp.outputs.timestamp }}" - name: ccache cache files + if: steps.filter.outputs.src == 'true' uses: actions/cache@v1.1.0 with: path: .ccache - key: ${{ runner.os }}-ccache-${{ steps.ccache_cache_timestamp.outputs.timestamp }} + key: ${{ runner.os }}-ccache_whole-${{ steps.ccache_cache_timestamp.outputs.timestamp }} restore-keys: | - ${{ runner.os }}-ccache- + ${{ runner.os }}-ccache_whole- - name: ๐Ÿ›  Build + if: steps.filter.outputs.src == 'true' run: | set -e export CCACHE_DIR=${GITHUB_WORKSPACE}/.ccache + export CCACHE_CONFIGPATH=${GITHUB_WORKSPACE}/.github/ccache.conf + echo ===== + pwd + echo ${GITHUB_WORKSPACE} echo ===== ccache -s echo ===== @@ -62,14 +106,26 @@ jobs: tar czf clsdk-ubuntu-20-04.tar.gz clsdk + echo ===== + ls -la ${GITHUB_WORKSPACE} echo ===== ccache -s echo ===== - name: ๐Ÿงช Run tests with CTest + if: steps.filter.outputs.src == 'true' run: cd build && ctest -j$(nproc) -V + - name: ๐Ÿ“ƒ Upload ccache.log + if: steps.filter.outputs.src == 'true' + uses: actions/upload-artifact@v2 + with: + name: ccache_log + path: | + ccache.log + - name: ๐Ÿ“ƒ Upload clsdk + if: steps.filter.outputs.src == 'true' uses: actions/upload-artifact@v2 with: name: clsdk @@ -77,9 +133,335 @@ jobs: build/clsdk-ubuntu-20-04.tar.gz - name: ๐Ÿ“ƒ Upload Eden Smart Contract + if: steps.filter.outputs.src == 'true' uses: actions/upload-artifact@v2 with: name: Eden Smart Contract path: | build/eden.abi build/eden.wasm + build/eden-micro-chain.wasm + + build-micro-chain: + name: Build Micro Chain + runs-on: ubuntu-latest + container: ghcr.io/eoscommunity/eden-builder:sub-chain + + steps: + - name: โœ… Checkout code + uses: actions/checkout@v2 + + - name: Prepare ccache timestamp + id: ccache_cache_timestamp + shell: cmake -P {0} + run: | + string(TIMESTAMP current_date "%Y-%m-%d-%H-%M-%S" UTC) + message("::set-output name=timestamp::${current_date}") + + - name: show_cache + id: show_cache + run: echo "${{ runner.os }}-ccache_microchain-${{ steps.ccache_cache_timestamp.outputs.timestamp }}" + + - name: ccache cache files + uses: actions/cache@v1.1.0 + with: + path: .ccache + key: ${{ runner.os }}-ccache_microchain-${{ steps.ccache_cache_timestamp.outputs.timestamp }} + restore-keys: | + ${{ runner.os }}-ccache_microchain- + + - name: ๐Ÿ›  Build + run: | + set -e + export CCACHE_DIR=${GITHUB_WORKSPACE}/.ccache + export CCACHE_CONFIGPATH=${GITHUB_WORKSPACE}/.github/ccache.conf + echo ===== + pwd + echo ${GITHUB_WORKSPACE} + echo ===== + ccache -s + echo ===== + + git submodule update --init external/atomicassets-contract + git submodule update --init external/Catch2 + git submodule update --init external/fmt + git submodule update --init external/rapidjson + mkdir build + cd build + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DSKIP_TS=Yes -DEDEN_ATOMIC_ASSETS_ACCOUNT=atomicassets -DEDEN_ATOMIC_MARKET_ACCOUNT=atomicmarket -DEDEN_SCHEMA_NAME=members -DBUILD_NATIVE=OFF .. + make -j$(nproc) wasm-configure + bash -c "cd wasm && make -j$(nproc) eden-micro-chain" + + echo ===== + ls -la ${GITHUB_WORKSPACE} + echo ===== + ccache -s + echo ===== + + - name: ๐Ÿ“ƒ Upload ccache.log + uses: actions/upload-artifact@v2 + with: + name: microchain_ccache_log + path: | + ccache.log + + - name: ๐Ÿ“ƒ Upload Eden Microchain + uses: actions/upload-artifact@v2 + with: + name: Eden Microchain + path: | + build/eden-micro-chain.wasm + + box-e2e: + needs: build-micro-chain + name: Eden Box E2E Tests + runs-on: ubuntu-latest + + steps: + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + src: + - ".github/workflows/build.yml" + + - ".eslintignore" + - ".eslintrc.js" + - ".prettierrc.json" + - "lerna.json" + - "package.json" + - "packages/common/**" + - "tsconfig.build.json" + - "tsconfig.json" + - "yarn.lock" + + - "docker/eden-box.Dockerfile" + - "packages/box/**" + + - name: โœ… Checkout code + if: steps.filter.outputs.src == 'true' + uses: actions/checkout@v2 + + - name: Download Eden Microchain + if: steps.filter.outputs.src == 'true' + uses: actions/download-artifact@v2 + with: + name: Eden Microchain + path: build + + - name: ๐Ÿ›  Build and Start Box + if: steps.filter.outputs.src == 'true' + run: | + export SUBCHAIN_DFUSE_PREVENT_CONNECT=1 + yarn + yarn build --stream + cd packages/box + yarn start & + + - name: ๐Ÿงช Run E2E + if: steps.filter.outputs.src == 'true' + # TODO: add real E2E tests... for now it's just a shameless curl ping + run: | + curl localhost:3032 + + box-build: + needs: build-micro-chain + name: Build Eden Box + runs-on: ubuntu-latest + + steps: + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + src: + - ".github/workflows/build.yml" + + - ".eslintignore" + - ".eslintrc.js" + - ".prettierrc.json" + - "lerna.json" + - "package.json" + - "packages/common/**" + - "tsconfig.build.json" + - "tsconfig.json" + - "yarn.lock" + + - "docker/eden-box.Dockerfile" + - "packages/box/**" + + - name: โœ… Checkout code + if: steps.filter.outputs.src == 'true' + uses: actions/checkout@v2 + + - name: Download Eden Microchain + if: steps.filter.outputs.src == 'true' + uses: actions/download-artifact@v2 + with: + name: Eden Microchain + path: build + + - name: Image Preparation + if: steps.filter.outputs.src == 'true' + id: prep + run: | + REGISTRY="ghcr.io" + IMAGE="${REGISTRY}/${{ github.repository_owner }}/eden-box" + TAGS="${IMAGE}:${{ github.sha }}" + if [[ $GITHUB_REF == ref/head/master ]]; then + TAGS="${TAGS},${IMAGE}:latest" + fi + echo ::set-output name=tags::${TAGS,,} + + - name: Showtag + if: steps.filter.outputs.src == 'true' + id: showtag + run: echo ${{ steps.prep.outputs.tags }} + + - name: Docker Buildx setup + if: steps.filter.outputs.src == 'true' + uses: docker/setup-buildx-action@v1 + + - name: Login in to registry + if: steps.filter.outputs.src == 'true' + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: ๐Ÿ›  Build & Publish Image + if: steps.filter.outputs.src == 'true' + uses: docker/build-push-action@v2 + with: + push: true + file: docker/eden-box.Dockerfile + tags: ${{ steps.prep.outputs.tags }} + context: . + + webapp-e2e: + needs: build-micro-chain + name: WebApp E2E Tests + runs-on: ubuntu-latest + + steps: + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + src: + - ".github/workflows/build.yml" + + - ".eslintignore" + - ".eslintrc.js" + - ".prettierrc.json" + - "lerna.json" + - "package.json" + - "packages/common/**" + - "tsconfig.build.json" + - "tsconfig.json" + - "yarn.lock" + + - "docker/eden-webapp.Dockerfile" + - "packages/webapp/**" + + - name: โœ… Checkout code + if: steps.filter.outputs.src == 'true' + uses: actions/checkout@v2 + + - name: Download Eden Microchain + if: steps.filter.outputs.src == 'true' + uses: actions/download-artifact@v2 + with: + name: Eden Microchain + path: build + + - name: ๐Ÿ›  Build and Start WebApp + if: steps.filter.outputs.src == 'true' + run: | + export SUBCHAIN_DFUSE_PREVENT_CONNECT=1 + yarn + yarn build --stream --ignore @edenos/example-history-app + yarn start --stream --ignore @edenos/example-history-app & + + - name: ๐Ÿงช Run E2E + if: steps.filter.outputs.src == 'true' + run: | + yarn test --stream + + - name: ๐ŸŽฅ Upload Cypress Results + if: always() && steps.filter.outputs.src == 'true' + uses: actions/upload-artifact@v2 + with: + name: Cypress E2E Videos and Screenshots + path: | + packages/webapp/cypress/screenshots + packages/webapp/cypress/videos + + webapp-build: + needs: build-micro-chain + name: Build Eden Community WebApp + runs-on: ubuntu-latest + + steps: + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + src: + - ".github/workflows/build.yml" + + - ".eslintignore" + - ".eslintrc.js" + - ".prettierrc.json" + - "lerna.json" + - "package.json" + - "packages/common/**" + - "tsconfig.build.json" + - "tsconfig.json" + - "yarn.lock" + + - "docker/eden-webapp.Dockerfile" + - "packages/webapp/**" + + - name: โœ… Checkout code + if: steps.filter.outputs.src == 'true' + uses: actions/checkout@v2 + + - name: Image Preparation + if: steps.filter.outputs.src == 'true' + id: prep + run: | + REGISTRY="ghcr.io" + IMAGE="${REGISTRY}/${{ github.repository_owner }}/eden-webapp" + TAGS="${IMAGE}:${{ github.sha }}" + if [[ $GITHUB_REF == ref/head/master ]]; then + TAGS="${TAGS},${IMAGE}:latest" + fi + echo ::set-output name=tags::${TAGS,,} + + - name: Showtag + if: steps.filter.outputs.src == 'true' + id: showtag + run: echo ${{ steps.prep.outputs.tags }} + + - name: Docker Buildx setup + if: steps.filter.outputs.src == 'true' + uses: docker/setup-buildx-action@v1 + + - name: Login in to registry + if: steps.filter.outputs.src == 'true' + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: ๐Ÿ›  Build & Publish Image + if: steps.filter.outputs.src == 'true' + uses: docker/build-push-action@v2 + with: + push: true + file: docker/eden-webapp.Dockerfile + tags: ${{ steps.prep.outputs.tags }} + context: . diff --git a/.github/workflows/eden-builder.yml b/.github/workflows/eden-builder.yml index a345fd78f..e767847e8 100644 --- a/.github/workflows/eden-builder.yml +++ b/.github/workflows/eden-builder.yml @@ -36,22 +36,21 @@ jobs: id: showtag run: echo ${{ steps.prep.outputs.tags }} - # TODO: follow up on this... https://github.community/t/403-error-on-container-registry-push-from-github-action/173071/5 - # - name: Docker Buildx setup - # uses: docker/setup-buildx-action@v1 - # with: - # buildkitd-flags: --debug - - # - name: Login in to registry - # uses: docker/login-action@v1 - # with: - # registry: ghcr.io - # username: ${{ github.repository_owner }} - # password: ${{ secrets.GITHUB_TOKEN }} - - # - name: ๐Ÿ›  Build & Publish Image - # uses: docker/build-push-action@v2 - # with: - # push: true - # file: docker/eden-builder.Dockerfile - # tags: ${{ steps.prep.outputs.tags }} + - name: Docker Buildx setup + uses: docker/setup-buildx-action@v1 + with: + buildkitd-flags: --debug + + - name: Login in to registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: ๐Ÿ›  Build & Publish Image + uses: docker/build-push-action@v2 + with: + push: true + file: docker/eden-builder.Dockerfile + tags: ${{ steps.prep.outputs.tags }} diff --git a/.github/workflows/webapp.yml b/.github/workflows/webapp.yml deleted file mode 100644 index bf0f9ecf5..000000000 --- a/.github/workflows/webapp.yml +++ /dev/null @@ -1,89 +0,0 @@ -name: webapp - -on: - push: - branches: - - main - - "test/*" - pull_request: - types: [assigned, opened, synchronize, reopened, labeled] - paths: - - "docker/eden-webapp.Dockerfile" - - ".eslintignore" - - ".eslintrc.js" - - ".prettierrc.json" - - "lerna.json" - - "packages/webapp/**" - - "package.json" - - "tsconfig.build.json" - - "tsconfig.json" - - "yarn.lock" - -jobs: - webapp-e2e: - name: WebApp E2E Tests - runs-on: ubuntu-latest - - steps: - - name: โœ… Checkout code - uses: actions/checkout@v2 - - - name: ๐Ÿ›  Build and Start WebApp - run: | - yarn - yarn build --stream - yarn start --stream & - - - name: ๐Ÿงช Run E2E - run: | - yarn test --stream - - - name: ๐ŸŽฅ Upload Cypress Results - uses: actions/upload-artifact@v2 - if: always() - with: - name: Cypress E2E Videos and Screenshots - path: | - packages/webapp/cypress/screenshots - packages/webapp/cypress/videos - - webapp-build: - needs: webapp-e2e - name: Build Eden Community WebApp - runs-on: ubuntu-latest - - steps: - - name: โœ… Checkout code - uses: actions/checkout@v2 - - - name: Image Preparation - id: prep - run: | - REGISTRY="ghcr.io" - IMAGE="${REGISTRY}/${{ github.repository_owner }}/eden-webapp" - TAGS="${IMAGE}:${{ github.sha }}" - if [[ $GITHUB_REF == ref/head/master ]]; then - TAGS="${TAGS},${IMAGE}:latest" - fi - echo ::set-output name=tags::${TAGS,,} - - - name: Showtag - id: showtag - run: echo ${{ steps.prep.outputs.tags }} - - - name: Docker Buildx setup - uses: docker/setup-buildx-action@v1 - - - name: Login in to registry - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: ๐Ÿ›  Build & Publish Image - uses: docker/build-push-action@v2 - with: - # push: true # TODO: follow up on this... https://github.community/t/403-error-on-container-registry-push-from-github-action/173071/5 - file: docker/eden-webapp.Dockerfile - tags: ${{ steps.prep.outputs.tags }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 69d5b7399..9ba09da1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,10 +12,13 @@ endif() file(WRITE ${CMAKE_BINARY_DIR}/CTestTestfile.cmake) +set(DEPEND_TESTER "") + option(BUILD_NATIVE "Build native code" ON) if(BUILD_NATIVE) add_subdirectory(native) file(APPEND ${CMAKE_BINARY_DIR}/CTestTestfile.cmake "subdirs(\"native\")\n") + set(DEPEND_TESTER cltester) endif() if(DEFINED WASI_SDK_PREFIX) @@ -26,13 +29,15 @@ if(DEFINED WASI_SDK_PREFIX) ExternalProject_Add(wasm SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/wasm - DEPENDS cltester # for abi generation + DEPENDS ${DEPEND_TESTER} # for abi generation BINARY_DIR wasm INSTALL_COMMAND "" BUILD_ALWAYS 1 TEST_EXCLUDE_FROM_MAIN 1 + STEP_TARGETS configure CMAKE_ARGS -DCMAKE_BUILD_TYPE= + -DCMAKE_TARGET_MESSAGES=${CMAKE_TARGET_MESSAGES} -DCMAKE_TOOLCHAIN_FILE=${CMAKE_CURRENT_SOURCE_DIR}/wasm/toolchain.cmake -DWASI_SDK_PREFIX=${WASI_SDK_PREFIX} -DWASM_CLANG_PREFIX=${WASM_CLANG_PREFIX} @@ -45,6 +50,8 @@ if(DEFINED WASI_SDK_PREFIX) -DFORCE_COLORED_OUTPUT=${FORCE_COLORED_OUTPUT} -DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER} -DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER} + -DCMAKE_FIND_ROOT_PATH=${CMAKE_CURRENT_BINARY_DIR}/wasm/deps + -DCMAKE_PREFIX_PATH=/ ) file(APPEND ${CMAKE_BINARY_DIR}/CTestTestfile.cmake "subdirs(\"wasm\")\n") ExternalProject_Add_StepTargets(wasm test) diff --git a/README.md b/README.md index a5f6e540e..4a20985d4 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,9 @@ sudo apt-get install -yq \ git \ libboost-all-dev \ libcurl4-openssl-dev \ + libgbm-dev \ libgmp-dev \ + libnss3-dev \ libssl-dev \ libusb-1.0-0-dev \ pkg-config \ diff --git a/contracts/eden/CMakeLists.txt b/contracts/eden/CMakeLists.txt index f74d38dcd..07fcf341c 100644 --- a/contracts/eden/CMakeLists.txt +++ b/contracts/eden/CMakeLists.txt @@ -52,3 +52,29 @@ endfunction() add_test_eden("") add_test_eden("-debug") eden_tester_test(test-eden) + +function(add_eden_microchain suffix) + add_executable(eden-micro-chain${suffix} + src/eden-micro-chain.cpp + ) + target_link_libraries(eden-micro-chain${suffix} clchain${suffix} eosio-contracts-wasi-polyfill${suffix}) + target_include_directories(eden-micro-chain${suffix} PRIVATE + include + ../../libraries/eosiolib/contracts/include + ../../libraries/eosiolib/core/include + ) + set_target_properties(eden-micro-chain${suffix} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${ROOT_BINARY_DIR}) + target_link_options(eden-micro-chain${suffix} PRIVATE + -Wl,--stack-first + -Wl,--no-entry + -Wl,-z,stack-size=8192 + -nostdlib + -lc++ + -lc++abi + -lc + ${WASI_SDK_PREFIX}/lib/clang/11.0.0/lib/wasi/libclang_rt.builtins-wasm32.a + ) + add_dependencies(eden-micro-chain${suffix} eden) +endfunction() +add_eden_microchain("") +# add_eden_microchain("-debug") diff --git a/contracts/eden/src/eden-micro-chain.cpp b/contracts/eden/src/eden-micro-chain.cpp new file mode 100644 index 000000000..cf563dcb6 --- /dev/null +++ b/contracts/eden/src/eden-micro-chain.cpp @@ -0,0 +1,598 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace eosio::literals; + +eosio::name eden_account; +eosio::name token_account; +eosio::name atomic_account; +eosio::name atomicmarket_account; + +// TODO: switch to uint64_t (js BigInt) after we upgrade to nodejs >= 15 +extern "C" void __wasm_call_ctors(); +[[clang::export_name("initialize")]] void initialize(uint32_t eden_account_low, + uint32_t eden_account_high, + uint32_t token_account_low, + uint32_t token_account_high, + uint32_t atomic_account_low, + uint32_t atomic_account_high, + uint32_t atomicmarket_account_low, + uint32_t atomicmarket_account_high) +{ + __wasm_call_ctors(); + eden_account.value = (uint64_t(eden_account_high) << 32) | eden_account_low; + token_account.value = (uint64_t(token_account_high) << 32) | token_account_low; + atomic_account.value = (uint64_t(atomic_account_high) << 32) | atomic_account_low; + atomicmarket_account.value = + (uint64_t(atomicmarket_account_high) << 32) | atomicmarket_account_low; +} + +[[clang::export_name("allocateMemory")]] void* allocateMemory(uint32_t size) +{ + return malloc(size); +} +[[clang::export_name("freeMemory")]] void freeMemory(void* p) +{ + free(p); +} + +std::variant> result; +[[clang::export_name("getResultSize")]] uint32_t getResultSize() +{ + return std::visit([](auto& data) { return data.size(); }, result); +} +[[clang::export_name("getResult")]] const char* getResult() +{ + return std::visit([](auto& data) { return data.data(); }, result); +} + +template +void dump(const T& ind) +{ + printf("%s\n", eosio::format_json(ind).c_str()); +} + +namespace boost +{ + BOOST_NORETURN void throw_exception(std::exception const& e) + { + eosio::detail::assert_or_throw(e.what()); + } + BOOST_NORETURN void throw_exception(std::exception const& e, boost::source_location const& loc) + { + eosio::detail::assert_or_throw(e.what()); + } +} // namespace boost + +struct by_id; +struct by_pk; +struct by_invitee; + +template +using mic = boost:: + multi_index_container, chainbase::allocator>; + +template +using ordered_by_id = boost::multi_index::ordered_unique< // + boost::multi_index::tag, + boost::multi_index::key<&T::id>>; + +template +using ordered_by_pk = boost::multi_index::ordered_unique< // + boost::multi_index::tag, + boost::multi_index::key<&T::pk>>; + +template +using ordered_by_invitee = boost::multi_index::ordered_unique< // + boost::multi_index::tag, + boost::multi_index::key<&T::invitee>>; + +uint64_t available_pk(const auto& table, const auto& first) +{ + auto& idx = table.template get(); + if (idx.empty()) + return first; + return (--idx.end())->pk() + 1; +} + +enum tables +{ + status_table, + induction_table, + member_table, +}; + +struct status +{ + bool active = false; + std::string community; + eosio::symbol communitySymbol; + eosio::asset minimumDonation; + std::vector initialMembers; + std::string genesisVideo; + eden::atomicassets::attribute_map collectionAttributes; + eosio::asset auctionStartingBid; + uint32_t auctionDuration; + std::string memo; +}; +EOSIO_REFLECT(status, + active, + community, + communitySymbol, + minimumDonation, + initialMembers, + genesisVideo, + collectionAttributes, + auctionStartingBid, + auctionDuration, + memo) + +struct status_object : public chainbase::object +{ + CHAINBASE_DEFAULT_CONSTRUCTOR(status_object) + + id_type id; + status status; +}; +using status_index = mic>; + +struct induction +{ + uint64_t id = 0; + eosio::name inviter; + eosio::name invitee; + std::vector witnesses; + eden::new_member_profile profile; + std::string video; +}; +EOSIO_REFLECT(induction, id, inviter, invitee, witnesses, profile, video) + +struct induction_object : public chainbase::object +{ + CHAINBASE_DEFAULT_CONSTRUCTOR(induction_object) + + id_type id; + induction induction; + + uint64_t pk() const { return induction.id; } + std::pair invitee() const { return {induction.invitee, induction.id}; } +}; +using induction_index = mic, + ordered_by_pk, + ordered_by_invitee>; + +struct member +{ + eosio::name account; + eosio::name inviter; + std::vector inductionWitnesses; + eden::new_member_profile profile; + std::string inductionVideo; +}; +EOSIO_REFLECT(member, account, inviter, inductionWitnesses, profile, inductionVideo) + +struct member_object : public chainbase::object +{ + CHAINBASE_DEFAULT_CONSTRUCTOR(member_object) + + id_type id; + member member; + + eosio::name pk() const { return member.account; } +}; +using member_index = mic, ordered_by_pk>; + +struct database +{ + chainbase::database db; + chainbase::generic_index status; + chainbase::generic_index inductions; + chainbase::generic_index members; + + database() + { + db.add_index(status); + db.add_index(inductions); + db.add_index(members); + } +}; +database db; + +template +void add_or_modify(Table& table, const Key& key, F&& f) +{ + auto& idx = table.template get(); + auto it = idx.find(key); + if (it != idx.end()) + table.modify(*it, [&](auto& obj) { return f(false, obj); }); + else + table.emplace([&](auto& obj) { return f(true, obj); }); +} + +template +void add_or_replace(Table& table, const Key& key, F&& f) +{ + auto& idx = table.template get(); + auto it = idx.find(key); + if (it != idx.end()) + table.remove(*it); + table.emplace(f); +} + +template +void modify(Table& table, const Key& key, F&& f) +{ + auto& idx = table.template get(); + auto it = idx.find(key); + eosio::check(it != idx.end(), "missing record"); + table.modify(*it, [&](auto& obj) { return f(obj); }); +} + +template +void remove_if_exists(Table& table, const Key& key) +{ + auto& idx = table.template get(); + auto it = idx.find(key); + if (it != idx.end()) + table.remove(*it); +} + +template +const auto& get(Table& table, const Key& key) +{ + auto& idx = table.template get(); + auto it = idx.find(key); + eosio::check(it != idx.end(), "missing record"); + return *it; +} + +const auto& get_status() +{ + auto& idx = db.status.get(); + eosio::check(idx.size() == 1, "missing genesis action"); + return *idx.begin(); +} + +void add_genesis_member(const status& status, eosio::name member) +{ + db.inductions.emplace([&](auto& obj) { + obj.induction.id = available_pk(db.inductions, 1); + obj.induction.inviter = eden_account; + obj.induction.invitee = member; + for (auto witness : status.initialMembers) + if (witness != member) + obj.induction.witnesses.push_back(witness); + }); +} + +void clearall(auto& table) +{ + for (auto it = table.begin(); it != table.end();) + { + auto next = it; + ++next; + table.remove(*it); + it = next; + } +} + +void clearall() +{ + clearall(db.status); + clearall(db.inductions); + clearall(db.members); +} + +void genesis(std::string community, + eosio::symbol community_symbol, + eosio::asset minimum_donation, + std::vector initial_members, + std::string genesis_video, + eden::atomicassets::attribute_map collection_attributes, + eosio::asset auction_starting_bid, + uint32_t auction_duration, + std::string memo) +{ + auto& idx = db.status.get(); + eosio::check(idx.empty(), "duplicate genesis action"); + db.status.emplace([&](auto& obj) { + obj.status.community = std::move(community); + obj.status.communitySymbol = std::move(community_symbol); + obj.status.minimumDonation = std::move(minimum_donation); + obj.status.initialMembers = std::move(initial_members); + obj.status.genesisVideo = std::move(genesis_video); + obj.status.collectionAttributes = std::move(collection_attributes); + obj.status.auctionStartingBid = std::move(auction_starting_bid); + obj.status.auctionDuration = std::move(auction_duration); + obj.status.memo = std::move(memo); + for (auto& member : obj.status.initialMembers) + add_genesis_member(obj.status, member); + }); +} + +void addtogenesis(eosio::name new_genesis_member) +{ + auto& status = get_status(); + db.status.modify(get_status(), + [&](auto& obj) { obj.status.initialMembers.push_back(new_genesis_member); }); + for (auto& obj : db.inductions) + db.inductions.modify( + obj, [&](auto& obj) { obj.induction.witnesses.push_back(new_genesis_member); }); + add_genesis_member(status.status, new_genesis_member); +} + +void inductinit(uint64_t id, + eosio::name inviter, + eosio::name invitee, + std::vector witnesses) +{ + // TODO: expire records + + // contract doesn't allow inductinit() until it transitioned to active + const auto& status = get_status(); + if (!status.status.active) + db.status.modify(status, [&](auto& obj) { obj.status.active = true; }); + + add_or_replace(db.inductions, id, [&](auto& obj) { + obj.induction.id = id; + obj.induction.inviter = inviter; + obj.induction.invitee = invitee; + obj.induction.witnesses = witnesses; + }); +} + +void inductprofil(uint64_t id, eden::new_member_profile profile) +{ + modify(db.inductions, id, [&](auto& obj) { obj.induction.profile = profile; }); +} + +void inductvideo(eosio::name account, uint64_t id, std::string video) +{ + modify(db.inductions, id, [&](auto& obj) { obj.induction.video = video; }); +} + +void inductcancel(eosio::name account, uint64_t id) +{ + remove_if_exists(db.inductions, id); +} + +void inductdonate(eosio::name payer, uint64_t id, eosio::asset quantity) +{ + auto& induction = get(db.inductions, id); + auto& member = db.members.emplace([&](auto& obj) { + obj.member.account = induction.induction.invitee; + obj.member.inviter = induction.induction.inviter; + obj.member.inductionWitnesses = induction.induction.witnesses; + obj.member.profile = induction.induction.profile; + obj.member.inductionVideo = induction.induction.video; + }); + + auto& index = db.inductions.get(); + for (auto it = index.lower_bound(std::pair{member.member.account, 0}); + it != index.end() && it->induction.invitee == member.member.account;) + { + auto next = it; + ++next; + db.inductions.remove(*it); + it = next; + } +} + +template +void call(void (*f)(Args...), const std::vector& data) +{ + std::tuple...> t; + eosio::input_stream s(data); + // TODO: prevent abort, indicate what failed + eosio::from_bin(t, s); + std::apply([f](auto&&... args) { f(std::move(args)...); }, t); +} + +void filter_block(const subchain::eosio_block& block) +{ + for (auto& trx : block.transactions) + { + for (auto& action : trx.actions) + { + if (action.firstReceiver == eden_account) + { + if (action.name == "clearall"_n) + call(clearall, action.hexData.data); + else if (action.name == "genesis"_n) + call(genesis, action.hexData.data); + else if (action.name == "addtogenesis"_n) + call(addtogenesis, action.hexData.data); + else if (action.name == "inductinit"_n) + call(inductinit, action.hexData.data); + else if (action.name == "inductprofil"_n) + call(inductprofil, action.hexData.data); + else if (action.name == "inductvideo"_n) + call(inductvideo, action.hexData.data); + else if (action.name == "inductcancel"_n) + call(inductcancel, action.hexData.data); + else if (action.name == "inductdonate"_n) + call(inductdonate, action.hexData.data); + } + } + } +} + +subchain::block_log block_log; + +void forked_n_blocks(size_t n) +{ + if (n) + printf("forked %d blocks, %d now in log\n", (int)n, (int)block_log.blocks.size()); + while (n--) + db.db.undo(); +} + +bool add_block(subchain::block_with_id&& bi, uint32_t eosio_irreversible) +{ + auto [status, num_forked] = block_log.add_block(bi); + if (status) + return false; + forked_n_blocks(num_forked); + if (auto* b = block_log.block_before_eosio_num(eosio_irreversible + 1)) + block_log.irreversible = std::max(block_log.irreversible, b->num); + db.db.commit(block_log.irreversible); + bool need_undo = bi.num > block_log.irreversible; + auto session = db.db.start_undo_session(bi.num > block_log.irreversible); + filter_block(bi.eosioBlock); + session.push(); + if (!need_undo) + db.db.set_revision(bi.num); + // printf("%s block: %d %d log: %d irreversible: %d db: %d-%d %s\n", block_log.status_str[status], + // (int)bi.eosioBlock.num, (int)bi.num, (int)block_log.blocks.size(), + // block_log.irreversible, // + // (int)db.db.undo_stack_revision_range().first, + // (int)db.db.undo_stack_revision_range().second, // + // to_string(bi.eosioBlock.id).c_str()); + return true; +} + +bool add_block(subchain::block&& eden_block, uint32_t eosio_irreversible) +{ + auto bin = eosio::convert_to_bin(eden_block); + subchain::block_with_id bi; + static_cast(bi) = std::move(eden_block); + bi.id = clchain::sha256(bin.data(), bin.size()); + auto bin_with_id = eosio::convert_to_bin(bi.id); + bin_with_id.insert(bin_with_id.end(), bin.begin(), bin.end()); + result = std::move(bin_with_id); + return add_block(std::move(bi), eosio_irreversible); +} + +// TODO: prevent from_json from aborting +[[clang::export_name("addEosioBlockJson")]] bool addEosioBlockJson(const char* json, + uint32_t size, + uint32_t eosio_irreversible) +{ + std::string str(json, size); + eosio::json_token_stream s(str.data()); + subchain::eosio_block eosio_block; + eosio::from_json(eosio_block, s); + + subchain::block eden_block; + eden_block.eosioBlock = std::move(eosio_block); + auto* prev = block_log.block_before_eosio_num(eden_block.eosioBlock.num); + if (prev) + { + eden_block.num = prev->num + 1; + eden_block.previous = prev->id; + } + else + eden_block.num = 1; + return add_block(std::move(eden_block), eosio_irreversible); + + // printf("%d blocks processed, %d blocks now in log\n", (int)eosio_blocks.size(), + // (int)block_log.blocks.size()); + // for (auto& b : block_log.blocks) + // printf("%d\n", (int)b->num); +} + +// TODO: prevent from_bin from aborting +[[clang::export_name("addBlock")]] bool addBlock(const char* data, + uint32_t size, + uint32_t eosio_irreversible) +{ + // TODO: verify id integrity + eosio::input_stream bin{data, size}; + subchain::block_with_id block; + eosio::from_bin(block, bin); + return add_block(std::move(block), eosio_irreversible); +} + +[[clang::export_name("setIrreversible")]] uint32_t setIrreversible(uint32_t irreversible) +{ + if (auto* b = block_log.block_before_num(irreversible + 1)) + block_log.irreversible = std::max(block_log.irreversible, b->num); + db.db.commit(block_log.irreversible); + return block_log.irreversible; +} + +[[clang::export_name("trimBlocks")]] void trimBlocks() +{ + block_log.trim(); +} + +[[clang::export_name("undoBlockNum")]] void undoBlockNum(uint32_t blockNum) +{ + forked_n_blocks(block_log.undo(blockNum)); +} + +[[clang::export_name("undoEosioNum")]] void undoEosioNum(uint32_t eosioNum) +{ + if (auto* b = block_log.block_by_eosio_num(eosioNum)) + forked_n_blocks(block_log.undo(b->num)); +} + +[[clang::export_name("getBlock")]] bool getBlock(uint32_t num) +{ + auto block = block_log.block_by_num(num); + if (!block) + return false; + result = eosio::convert_to_bin(*block); + return true; +} + +constexpr const char MemberConnection_name[] = "MemberConnection"; +constexpr const char MemberEdge_name[] = "MemberEdge"; +using MemberConnection = + clchain::Connection, + MemberConnection_name, + MemberEdge_name>>; + +struct Query +{ + subchain::BlockLog blockLog; + + MemberConnection members(std::optional gt, + std::optional ge, + std::optional lt, + std::optional le, + std::optional first, + std::optional last, + std::optional before, + std::optional after) const + { + return clchain::make_connection( + gt, ge, lt, le, first, last, before, after, // + db.members.get(), // + [](auto& obj) { return obj.member.account; }, // + [](auto& obj) { return std::cref(obj.member); }, // + [](auto& members, auto key) { return members.lower_bound(key); }, + [](auto& members, auto key) { return members.upper_bound(key); }); + } +}; +EOSIO_REFLECT2(Query, + blockLog, + method(members, "gt", "ge", "lt", "le", "first", "last", "before", "after")) + +auto schema = clchain::get_gql_schema(); +[[clang::export_name("getSchemaSize")]] uint32_t getSchemaSize() +{ + return schema.size(); +} +[[clang::export_name("getSchema")]] const char* getSchema() +{ + return schema.c_str(); +} + +[[clang::export_name("query")]] void query(const char* query, + uint32_t size, + const char* variables, + uint32_t variables_size) +{ + Query root{block_log}; + result = clchain::gql_query(root, {query, size}, {variables, variables_size}); +} diff --git a/docker/eden-box.Dockerfile b/docker/eden-box.Dockerfile index d5974b27c..098260f11 100644 --- a/docker/eden-box.Dockerfile +++ b/docker/eden-box.Dockerfile @@ -23,6 +23,7 @@ COPY .eslintignore .eslintrc.js .prettierrc.json lerna.json package.json tsconfi COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/packages/common/node_modules ./packages/common/node_modules COPY --from=deps /app/packages/box/node_modules ./packages/box/node_modules +COPY ./build/eden-micro-chain.wasm /app/build/ RUN yarn build --stream diff --git a/docker/eden-builder.Dockerfile b/docker/eden-builder.Dockerfile index 71e618495..9e58f00ae 100644 --- a/docker/eden-builder.Dockerfile +++ b/docker/eden-builder.Dockerfile @@ -5,7 +5,6 @@ RUN export DEBIAN_FRONTEND=noninteractive \ && apt-get install -yq \ binaryen \ build-essential \ - ccache \ cmake \ curl \ git \ @@ -14,15 +13,27 @@ RUN export DEBIAN_FRONTEND=noninteractive \ libgmp-dev \ libssl-dev \ libusb-1.0-0-dev \ + libzstd-dev \ pkg-config \ && apt-get clean -yq \ && rm -rf /var/lib/apt/lists/* +RUN cd /root \ + && curl -LO https://github.com/ccache/ccache/releases/download/v4.3/ccache-4.3.tar.gz \ + && tar xf ccache-4.3.tar.gz \ + && cd /root/ccache-4.3 \ + && cmake . \ + && make -j \ + && make -j install \ + && cd /root \ + && rm -rf ccache* + RUN cd /opt \ && curl -LO https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk-12.0-linux.tar.gz \ && tar xf wasi-sdk-12.0-linux.tar.gz \ && curl -LO https://nodejs.org/dist/v14.16.0/node-v14.16.0-linux-x64.tar.xz \ && tar xf node-v14.16.0-linux-x64.tar.xz \ + && rm *.tar.* \ && export PATH="/opt/node-v14.16.0-linux-x64/bin:$PATH" \ && npm i -g yarn diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 61313c949..0c964e8c6 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -40,6 +40,7 @@ if(IS_NATIVE) CMAKE_ARGS -DENABLE_OC=OFF -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_TARGET_MESSAGES=${CMAKE_TARGET_MESSAGES} -DCMAKE_INSTALL_PREFIX=${ROOT_BINARY_DIR}/eos -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER} @@ -124,8 +125,12 @@ if(IS_NATIVE) BINARY_DIR rocksdb-ext TEST_EXCLUDE_FROM_MAIN 1 EXCLUDE_FROM_ALL 1 + BUILD_COMMAND sed -i s/@@//g ${CMAKE_CURRENT_SOURCE_DIR}/rocksdb/util/build_version.cc.in + COMMAND sed -i "s/__DATE__/\"\"/g" ${CMAKE_CURRENT_SOURCE_DIR}/rocksdb/util/build_version.cc.in + COMMAND cmake --build . -j ${NUM_PROCS} CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_TARGET_MESSAGES=${CMAKE_TARGET_MESSAGES} -DCMAKE_INSTALL_PREFIX=${ROOT_BINARY_DIR}/rocksdb -DPORTABLE=ON # don't use sse4.2 -DWITH_GFLAGS=OFF @@ -152,6 +157,7 @@ ExternalProject_Add(fmt-ext EXCLUDE_FROM_ALL 1 CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_TARGET_MESSAGES=${CMAKE_TARGET_MESSAGES} -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/fmt -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER} @@ -186,6 +192,7 @@ ExternalProject_Add(catch2-ext ${CATCH2_INSTALL_COMMAND} CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_TARGET_MESSAGES=${CMAKE_TARGET_MESSAGES} -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/catch2 -DCATCH_INSTALL_DOCS=FALSE -DCATCH_INSTALL_EXTRAS=FALSE diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 9977656cf..d672beaae 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -11,4 +11,5 @@ if(DEFINED IS_NATIVE) add_subdirectory(state_history) endif() +add_subdirectory(clchain) add_subdirectory(rodeos) diff --git a/libraries/abieos/include/eosio/fixed_bytes.hpp b/libraries/abieos/include/eosio/fixed_bytes.hpp index a35be1252..91dd3a013 100644 --- a/libraries/abieos/include/eosio/fixed_bytes.hpp +++ b/libraries/abieos/include/eosio/fixed_bytes.hpp @@ -5,6 +5,7 @@ #include #include #include "from_json.hpp" +#include "hex.hpp" #include "operators.hpp" #include "reflection.hpp" #include "to_json.hpp" @@ -288,4 +289,10 @@ namespace eosio eosio::to_json_hex((const char*)bytes.data(), bytes.size(), stream); } + template + std::string to_string(const fixed_bytes& obj) + { + auto bytes = obj.extract_as_byte_array(); + return hex(bytes.begin(), bytes.end()); + } } // namespace eosio diff --git a/libraries/abieos/include/eosio/for_each_field.hpp b/libraries/abieos/include/eosio/for_each_field.hpp index 0390ccc09..f0c0821ed 100644 --- a/libraries/abieos/include/eosio/for_each_field.hpp +++ b/libraries/abieos/include/eosio/for_each_field.hpp @@ -24,7 +24,7 @@ namespace eosio constexpr auto for_each_field(T&& t, F&& f) -> std::enable_if_t>> { - eosio_for_each_field((std::decay_t*)nullptr, [&](const char*, auto member) { + eosio_for_each_field((std::decay_t*)nullptr, [&](const char*, auto member, auto...) { if constexpr (std::is_member_object_pointer_v) { f(t.*member(&t)); @@ -35,7 +35,7 @@ namespace eosio template constexpr void for_each_field(F&& f) { - eosio_for_each_field((T*)nullptr, [&f](const char* name, auto member) { + eosio_for_each_field((T*)nullptr, [&f](const char* name, auto member, auto...) { if constexpr (std::is_member_object_pointer_v) { f(name, [member](auto p) -> decltype((p->*member(p))) { return p->*member(p); }); @@ -47,10 +47,10 @@ namespace eosio template constexpr void for_each_method(F&& f) { - eosio_for_each_field((T*)nullptr, [&f](const char* name, auto member) { + eosio_for_each_field((T*)nullptr, [&f](const char* name, auto member, auto... arg_names) { if constexpr (std::is_member_function_pointer_v) { - f(name, member((T*)nullptr)); + f(name, member((T*)nullptr), arg_names...); } }); } diff --git a/libraries/abieos/include/eosio/hex.hpp b/libraries/abieos/include/eosio/hex.hpp new file mode 100644 index 000000000..832012831 --- /dev/null +++ b/libraries/abieos/include/eosio/hex.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +namespace eosio +{ + template + void hex(SrcIt begin, SrcIt end, DestIt dest) + { + auto nibble = [&dest](uint8_t i) { + if (i <= 9) + *dest++ = '0' + i; + else + *dest++ = 'A' + i - 10; + }; + while (begin != end) + { + nibble(((uint8_t)*begin) >> 4); + nibble(((uint8_t)*begin) & 0xf); + ++begin; + } + } + + template + std::string hex(SrcIt begin, SrcIt end) + { + std::string s; + s.reserve((end - begin) * 2); + hex(begin, end, std::back_inserter(s)); + return s; + } +} // namespace eosio diff --git a/libraries/abieos/include/eosio/reflection2.hpp b/libraries/abieos/include/eosio/reflection2.hpp new file mode 100644 index 000000000..bba15f8be --- /dev/null +++ b/libraries/abieos/include/eosio/reflection2.hpp @@ -0,0 +1,68 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EOSIO_REFLECT2_MATCH_CHECK_N(x, n, r, ...) \ + BOOST_PP_BITAND(n, BOOST_PP_COMPL(BOOST_PP_CHECK_EMPTY(r))) +#define EOSIO_REFLECT2_MATCH_CHECK(...) EOSIO_REFLECT2_MATCH_CHECK_N(__VA_ARGS__, 0, ) +#define EOSIO_REFLECT2_MATCH(base, x) EOSIO_REFLECT2_MATCH_CHECK(BOOST_PP_CAT(base, x)) + +#define EOSIO_REFLECT2_FIRST(a, ...) a +#define EOSIO_REFLECT2_APPLY_FIRST(a) EOSIO_REFLECT2_FIRST(a) +#define EOSIO_REFLECT2_SKIP_SECOND(a, b, ...) (a, __VA_ARGS__) + +#define EOSIO_REFLECT2_KNOWN_ITEM(STRUCT, item) \ + EOSIO_REFLECT2_FIRST EOSIO_REFLECT2_APPLY_FIRST(BOOST_PP_CAT(EOSIO_REFLECT2_MATCH_ITEM, item)) \ + EOSIO_REFLECT2_SKIP_SECOND BOOST_PP_TUPLE_PUSH_FRONT( \ + EOSIO_REFLECT2_APPLY_FIRST(BOOST_PP_CAT(EOSIO_REFLECT2_MATCH_ITEM, item)), STRUCT) +#define EOSIO_REFLECT2_MATCH_ITEM(r, STRUCT, item) \ + BOOST_PP_IIF(BOOST_PP_CHECK_EMPTY(item), , \ + BOOST_PP_IIF(EOSIO_REFLECT2_MATCH(EOSIO_REFLECT2_MATCH_ITEM, item), \ + EOSIO_REFLECT2_KNOWN_ITEM, EOSIO_REFLECT2_MEMBER)(STRUCT, item)) + +#define EOSIO_REFLECT2_MEMBER(STRUCT, member) \ + f(#member, [](auto p) -> decltype(&std::decay_t::member) { \ + return &std::decay_t::member; \ + }); + +#define EOSIO_REFLECT2_MATCH_ITEMbase(...) (EOSIO_REFLECT2_base, __VA_ARGS__), 1 +#define EOSIO_REFLECT2_base(STRUCT, base) \ + static_assert(std::is_base_of_v, \ + BOOST_PP_STRINGIZE(base) " is not a base class of " BOOST_PP_STRINGIZE(STRUCT)); \ + eosio_for_each_field((base*)nullptr, f); + +#define EOSIO_REFLECT2_MATCH_ITEMmethod(...) (EOSIO_REFLECT2_method, __VA_ARGS__), 1 +#define EOSIO_REFLECT2_method(STRUCT, member, ...) \ + f( \ + BOOST_PP_STRINGIZE(member), \ + [](auto p) -> decltype(&std::decay_t::member) { \ + return &std::decay_t::member; \ + }, \ + __VA_ARGS__); + +/** + * EOSIO_REFLECT2(, ...) + * Each parameter may be one of the following: + * * ident: non-static data member or method + * * base(ident): base class + * * method(ident, "arg1", ...): method + */ +#define EOSIO_REFLECT2(STRUCT, ...) \ + [[maybe_unused]] inline const char* get_type_name(STRUCT*) { return #STRUCT; } \ + template \ + constexpr void eosio_for_each_field(STRUCT*, F f) \ + { \ + BOOST_PP_SEQ_FOR_EACH(EOSIO_REFLECT2_MATCH_ITEM, STRUCT, \ + BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \ + } + +#define EOSIO_REFLECT2_FOR_EACH_FIELD(STRUCT, ...) \ + BOOST_PP_SEQ_FOR_EACH(EOSIO_REFLECT2_MATCH_ITEM, STRUCT, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) diff --git a/libraries/abieos/include/eosio/stream.hpp b/libraries/abieos/include/eosio/stream.hpp index aef07aef9..ec7171f83 100644 --- a/libraries/abieos/include/eosio/stream.hpp +++ b/libraries/abieos/include/eosio/stream.hpp @@ -99,6 +99,24 @@ namespace eosio } }; + struct string_stream + { + std::string& data; + string_stream(std::string& data) : data(data) {} + + void write(char c) { data.push_back(c); } + void write(const void* src, std::size_t sz) + { + auto s = reinterpret_cast(src); + data.insert(data.end(), s, s + sz); + } + template + void write_raw(const T& v) + { + write(&v, sizeof(v)); + } + }; + struct fixed_buf_stream { char* pos; @@ -119,12 +137,6 @@ namespace eosio pos += sz; } - template - void write(const char (&src)[Size]) - { - write(src, Size); - } - template void write_raw(const T& v) { @@ -140,12 +152,6 @@ namespace eosio void write(const void* src, std::size_t sz) { size += sz; } - template - void write(const char (&src)[Size]) - { - size += Size; - } - template void write_raw(const T& v) { @@ -153,6 +159,12 @@ namespace eosio } }; + template + void write_str(std::string_view str, S& stream) + { + stream.write(str.data(), str.size()); + } + template void increase_indent(S&) { diff --git a/libraries/abieos/include/eosio/time.hpp b/libraries/abieos/include/eosio/time.hpp index 2e0c3019c..96b3ccc19 100644 --- a/libraries/abieos/include/eosio/time.hpp +++ b/libraries/abieos/include/eosio/time.hpp @@ -103,18 +103,42 @@ namespace eosio void from_json(time_point& obj, S& stream) { auto s = stream.get_string(); + auto pos = s.data(); + auto end = pos + s.size(); uint64_t utc_microseconds; - if (!eosio::string_to_utc_microseconds(utc_microseconds, s.data(), s.data() + s.size())) + if (!eosio::string_to_utc_microseconds(utc_microseconds, pos, end, false) || + !(pos == end || pos + 1 == end && *pos == 'Z')) { check(false, convert_json_error(eosio::from_json_error::expected_time_point)); } obj = time_point(microseconds(utc_microseconds)); } + template + struct time_point_include_z_stream : Base + { + using Base::Base; + }; + + template + constexpr bool time_point_include_z(const S*) + { + return false; + } + + template + constexpr bool time_point_include_z(const time_point_include_z_stream*) + { + return true; + } + template void to_json(const time_point& obj, S& stream) { - return to_json(eosio::microseconds_to_str(obj.elapsed._count), stream); + if constexpr (time_point_include_z((S*)nullptr)) + return to_json(eosio::microseconds_to_str(obj.elapsed._count) + "Z", stream); + else + return to_json(eosio::microseconds_to_str(obj.elapsed._count), stream); } /** diff --git a/libraries/abieos/include/eosio/to_json.hpp b/libraries/abieos/include/eosio/to_json.hpp index b4258399c..7ae85bb65 100644 --- a/libraries/abieos/include/eosio/to_json.hpp +++ b/libraries/abieos/include/eosio/to_json.hpp @@ -331,16 +331,22 @@ template void to_json(__int128 value, S& stream) { return int_to_js return result; } - template - std::string format_json(const T& t) + template