diff --git a/.cicd/build.sh b/.cicd/build.sh index 0268ae9e6a..7f210c132c 100755 --- a/.cicd/build.sh +++ b/.cicd/build.sh @@ -8,7 +8,7 @@ if [[ $(uname) == 'Darwin' ]]; then # You can't use chained commands in execute cd $BUILD_DIR - cmake .. + cmake .. -DCMAKE_BUILD_TYPE=Release make -j$JOBS else # Linux @@ -19,15 +19,19 @@ else # Linux # PRE_COMMANDS: Executed pre-cmake PRE_COMMANDS="cd $MOUNTED_DIR/build" - BUILD_COMMANDS="cmake .. && make -j$JOBS" + BUILD_COMMANDS="cmake .. -DCMAKE_BUILD_TYPE=Release && make -j$JOBS" - [[ $IMAGE_TAG == 'centos-7.6' ]] && PRE_COMMANDS="$PRE_COMMANDS && source /opt/rh/devtoolset-7/enable" + BUILD_COMMANDS_1604="cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_FLAGS=\"-stdlib=libc++\" && make -j$JOBS" + [[ $IMAGE_TAG == 'ubuntu-16.04' ]] && BUILD_COMMANDS="$BUILD_COMMANDS_1604" + + [[ $IMAGE_TAG == 'centos-7.7' ]] && PRE_COMMANDS="$PRE_COMMANDS && source /opt/rh/devtoolset-7/enable" # Docker Commands if [[ $BUILDKITE == true ]]; then # Generate Base Images $CICD_DIR/generate-base-images.sh - elif [[ $TRAVIS == true ]]; then - ARGS="$ARGS -e JOBS -e CCACHE_DIR=/opt/.ccache" + if [[ "$IMAGE_TAG" == 'ubuntu-18.04' ]]; then + FULL_TAG='eosio/ci-contracts-builder:base-ubuntu-18.04-develop' + fi fi COMMANDS="$PRE_COMMANDS && $BUILD_COMMANDS" @@ -42,4 +46,4 @@ else # Linux eval docker run $ARGS $evars $FULL_TAG bash -c \"$COMMANDS\" -fi \ No newline at end of file +fi diff --git a/.cicd/create-docker-from-binary.sh b/.cicd/create-docker-from-binary.sh new file mode 100755 index 0000000000..98fa89dabb --- /dev/null +++ b/.cicd/create-docker-from-binary.sh @@ -0,0 +1,53 @@ +#!/bin/bash +echo '--- :evergreen_tree: Configuring Environment' +set -euo pipefail + +buildkite-agent artifact download '*.deb' --step ':ubuntu: Ubuntu 18.04 - Package Builder' . +echo ":done: download successful" + +SANITIZED_BRANCH=$(echo "$BUILDKITE_BRANCH" | sed 's.^/..' | sed 's/[:/]/_/g') +SANITIZED_TAG=$(echo "$BUILDKITE_TAG" | sed 's.^/..' | tr '/' '_') +echo "$SANITIZED_BRANCH" +echo "$SANITIZED_TAG" + +# do docker build +echo '+++ :docker: Building Container' +echo ":docker::build: Building image..." +DOCKERHUB_REGISTRY="docker.io/eosio/eosio.cdt" + +BUILD_TAG=${BUILDKITE_BUILD_NUMBER:-latest} +DOCKER_BUILD_GEN="docker build -t eosio_cdt_image:$BUILD_TAG -f ./docker/dockerfile ." +echo "$ $DOCKER_BUILD_GEN" +eval $DOCKER_BUILD_GEN + +#tag and push on each destination AWS & DOCKERHUB +echo '+++ :arrow_up: Pushing Container' +EOSIO_REGS=("$EOSIO_CDT_REGISTRY" "$DOCKERHUB_REGISTRY") +for REG in ${EOSIO_REGS[@]}; do + DOCKER_TAG_COMMIT="docker tag eosio_cdt_image:$BUILD_TAG $REG:$BUILDKITE_COMMIT" + DOCKER_TAG_BRANCH="docker tag eosio_cdt_image:$BUILD_TAG $REG:$SANITIZED_BRANCH" + echo -e "$ Tagging Images: \n$DOCKER_TAG_COMMIT \n$DOCKER_TAG_BRANCH" + eval $DOCKER_TAG_COMMIT + eval $DOCKER_TAG_BRANCH + DOCKER_PUSH_COMMIT="docker push $REG:$BUILDKITE_COMMIT" + DOCKER_PUSH_BRANCH="docker push $REG:$SANITIZED_BRANCH" + echo -e "$ Pushing Images: \n$DOCKER_PUSH_COMMIT \n$DOCKER_PUSH_BRANCH" + eval $DOCKER_PUSH_COMMIT + eval $DOCKER_PUSH_BRANCH + CLEAN_IMAGE_COMMIT="docker rmi $REG:$BUILDKITE_COMMIT" + CLEAN_IMAGE_BRANCH="docker rmi $REG:$SANITIZED_BRANCH" + echo -e "Cleaning Up: \n$CLEAN_IMAGE_COMMIT \n$CLEAN_IMAGE_BRANCH$" + eval $CLEAN_IMAGE_COMMIT + eval $CLEAN_IMAGE_BRANCH + if [[ ! -z "$SANITIZED_TAG" ]]; then + DOCKER_TAG="docker tag eosio_cdt_image $REG:$SANITIZED_TAG" + DOCKER_REM="docker rmi $REG:$SANITIZED_TAG" + echo -e "$ \n Tagging Image: \n$DOCKER_TAG \n Cleaning Up: \n$DOCKER_REM" + eval $DOCKER_TAG + eval $DOCKER_REM + fi +done + +DOCKER_GEN="docker rmi eosio_cdt_image:$BUILD_TAG" +echo "Clean up base image" +eval $DOCKER_GEN diff --git a/.cicd/docker/amazonlinux-2.dockerfile b/.cicd/docker/amazonlinux-2.dockerfile index 28a2f5a614..99e78df328 100644 --- a/.cicd/docker/amazonlinux-2.dockerfile +++ b/.cicd/docker/amazonlinux-2.dockerfile @@ -18,4 +18,6 @@ RUN curl -LO https://cmake.org/files/v3.10/cmake-3.10.2.tar.gz && \ make -j$(nproc) && \ make install && \ cd .. && \ - rm -f cmake-3.10.2.tar.gz \ No newline at end of file + rm -f cmake-3.10.2.tar.gz + +RUN ln -sfn /usr/bin/python3 /usr/local/bin/python3 diff --git a/.cicd/docker/centos-7.6.dockerfile b/.cicd/docker/centos-7.7.dockerfile similarity index 56% rename from .cicd/docker/centos-7.6.dockerfile rename to .cicd/docker/centos-7.7.dockerfile index f7baa808a5..6555a26aef 100644 --- a/.cicd/docker/centos-7.6.dockerfile +++ b/.cicd/docker/centos-7.7.dockerfile @@ -1,23 +1,34 @@ -FROM centos:7.6.1810 +FROM centos:7.7.1908 # install dependencies RUN yum update -y && \ yum --enablerepo=extras install -y centos-release-scl && \ yum install -y devtoolset-7 && \ - yum install -y python33.x86_64 git autoconf automake bzip2 \ + yum install -y git autoconf automake bzip2 \ libtool ocaml.x86_64 doxygen graphviz-devel.x86_64 \ libicu-devel.x86_64 bzip2.x86_64 bzip2-devel.x86_64 openssl-devel.x86_64 \ - gmp-devel.x86_64 python-devel.x86_64 gettext-devel.x86_64 gcc-c++.x86_64 perl + gmp-devel.x86_64 gettext-devel.x86_64 gcc-c++.x86_64 perl \ + libffi-devel.x86_64 + +# build Python 3.7.4 +RUN curl -LO https://www.python.org/ftp/python/3.7.4/Python-3.7.4.tgz && \ + source /opt/rh/devtoolset-7/enable && \ + tar xzf Python-3.7.4.tgz && \ + cd Python-3.7.4 && \ + ./configure --enable-optimizations && \ + make -j$(nproc) altinstall && \ + cd .. && \ + rm -rf Python-3.7.4 && rm -rf Python-3.7.4.tgz + # build lcov RUN git clone https://github.com/linux-test-project/lcov.git && \ - source /opt/rh/python33/enable && \ source /opt/rh/devtoolset-7/enable && \ cd lcov && \ make install && \ cd / && \ rm -rf lcov/ + # build cmake RUN curl -LO https://cmake.org/files/v3.10/cmake-3.10.2.tar.gz && \ - source /opt/rh/python33/enable && \ source /opt/rh/devtoolset-7/enable && \ tar -xzf cmake-3.10.2.tar.gz && \ cd cmake-3.10.2 && \ @@ -25,4 +36,6 @@ RUN curl -LO https://cmake.org/files/v3.10/cmake-3.10.2.tar.gz && \ make -j$(nproc) && \ make install && \ cd .. && \ - rm -f cmake-3.10.2.tar.gz \ No newline at end of file + rm -f cmake-3.10.2.tar.gz + +RUN ln -sfn /usr/local/bin/python3.7 /usr/local/bin/python3 diff --git a/.cicd/docker/centos-8.dockerfile b/.cicd/docker/centos-8.dockerfile new file mode 100644 index 0000000000..5ddb698b83 --- /dev/null +++ b/.cicd/docker/centos-8.dockerfile @@ -0,0 +1,9 @@ +FROM centos:8 +# install dependencies +RUN yum update -y && \ + yum install -y gdisk && \ + yum install -y cmake && \ + yum install -y git autoconf automake bzip2 \ + libtool make \ + libicu-devel.x86_64 bzip2.x86_64 bzip2-devel.x86_64 openssl-devel.x86_64 \ + gmp-devel.x86_64 python38 python3-devel gettext-devel.x86_64 gcc-c++.x86_64 perl diff --git a/.cicd/docker/ubuntu-16.04.dockerfile b/.cicd/docker/ubuntu-16.04.dockerfile index 0bda1a4138..74e145daa5 100644 --- a/.cicd/docker/ubuntu-16.04.dockerfile +++ b/.cicd/docker/ubuntu-16.04.dockerfile @@ -1,10 +1,12 @@ FROM ubuntu:16.04 # install dependencies RUN apt-get update && apt-get upgrade -y && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y git clang-4.0 \ - lldb-4.0 libclang-4.0-dev make automake libbz2-dev libssl-dev \ + DEBIAN_FRONTEND=noninteractive apt-get install -y git \ + lldb-4.0 make automake libbz2-dev libssl-dev \ libgmp3-dev autotools-dev build-essential libicu-dev python2.7-dev \ - python3-dev autoconf libtool curl zlib1g-dev doxygen graphviz + autoconf libtool curl zlib1g-dev doxygen graphviz \ + wget libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev xz-utils + # install cmake RUN curl -LO https://cmake.org/files/v3.10/cmake-3.10.2.tar.gz && \ tar -xzf cmake-3.10.2.tar.gz && \ @@ -13,4 +15,25 @@ RUN curl -LO https://cmake.org/files/v3.10/cmake-3.10.2.tar.gz && \ make -j$(nproc) && \ make install && \ cd .. && \ - rm -f cmake-3.10.2.tar.gz \ No newline at end of file + rm -f cmake-3.10.2.tar.gz + +# install Python 3.7.4 +RUN curl -LO https://www.python.org/ftp/python/3.7.4/Python-3.7.4.tgz && \ + tar xzf Python-3.7.4.tgz && \ + cd Python-3.7.4 && \ + ./configure --enable-optimizations && \ + make -j$(nproc) altinstall && \ + cd .. && \ + rm -rf Python-3.7.4 && rm -rf Python-3.7.4.tar.gz + +RUN ln -sfn /usr/local/bin/python3.7 /usr/local/bin/python3 + +# install clang 9 +RUN curl -LO https://releases.llvm.org/9.0.0/clang+llvm-9.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz && \ + tar -xvf clang+llvm-9.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz && \ + cp -R clang+llvm-9.0.0-x86_64-linux-gnu-ubuntu-16.04/bin/* /usr/local/bin && \ + cp -R clang+llvm-9.0.0-x86_64-linux-gnu-ubuntu-16.04/include/* /usr/local/include && \ + cp -R clang+llvm-9.0.0-x86_64-linux-gnu-ubuntu-16.04/lib/* /usr/local/lib && \ + mkdir -p /usr/local/libexec && cp -R clang+llvm-9.0.0-x86_64-linux-gnu-ubuntu-16.04/libexec/* /usr/local/libexec && \ + cp -R clang+llvm-9.0.0-x86_64-linux-gnu-ubuntu-16.04/share/* /usr/local/share && \ + cp -R clang+llvm-9.0.0-x86_64-linux-gnu-ubuntu-16.04/lib/libc++* /usr/lib diff --git a/.cicd/docker/ubuntu-18.04.dockerfile b/.cicd/docker/ubuntu-18.04.dockerfile index 817636b8e2..ea7647b55e 100644 --- a/.cicd/docker/ubuntu-18.04.dockerfile +++ b/.cicd/docker/ubuntu-18.04.dockerfile @@ -1,7 +1,20 @@ FROM ubuntu:18.04 # install dependencies + RUN apt-get update && apt-get upgrade -y && \ DEBIAN_FRONTEND=noninteractive apt-get install -y git clang-4.0 \ lldb-4.0 libclang-4.0-dev cmake make automake libbz2-dev libssl-dev \ - libgmp3-dev autotools-dev build-essential libicu-dev python2.7-dev \ - python3-dev autoconf libtool curl zlib1g-dev doxygen graphviz \ No newline at end of file + libgmp3-dev autotools-dev build-essential libicu-dev python2.7-dev \ + autoconf libtool curl zlib1g-dev doxygen graphviz \ + libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev + +# install Python 3.7.4 +RUN curl -LO https://www.python.org/ftp/python/3.7.4/Python-3.7.4.tgz && \ + tar xzf Python-3.7.4.tgz && \ + cd Python-3.7.4 && \ + ./configure --enable-optimizations && \ + make -j$(nproc) altinstall && \ + cd .. && \ + rm -rf Python-3.7.4 && rm -rf Python-3.7.4.tar.gz + +RUN ln -sfn /usr/local/bin/python3.7 /usr/local/bin/python3 diff --git a/.cicd/docker/ubuntu-20.04.dockerfile b/.cicd/docker/ubuntu-20.04.dockerfile new file mode 100644 index 0000000000..7543e0a3d2 --- /dev/null +++ b/.cicd/docker/ubuntu-20.04.dockerfile @@ -0,0 +1,11 @@ +FROM ubuntu:20.04 +# install dependencies + +RUN apt-get update && apt-get upgrade -y && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y git clang-8 \ + lldb-8 libclang-8-dev cmake make automake libbz2-dev libssl-dev \ + libgmp3-dev autotools-dev build-essential libicu-dev python2.7-dev python3 \ + autoconf libtool curl zlib1g-dev doxygen graphviz \ + libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev + +RUN ln -sfn /usr/bin/python3 /usr/local/bin/python3 diff --git a/.cicd/integration-tests.sh b/.cicd/integration-tests.sh new file mode 100755 index 0000000000..6ae9b85c00 --- /dev/null +++ b/.cicd/integration-tests.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -eo pipefail +. ./.cicd/helpers/general.sh + +mkdir -p $BUILD_DIR + +PRE_COMMANDS="cd $MOUNTED_DIR/build" +TEST="ctest -j$JOBS -L integration_tests -V -T Test" +COMMANDS="$PRE_COMMANDS && $TEST" + +ARGS=${ARGS:-"--rm --init -v $(pwd):$MOUNTED_DIR"} + +# . $HELPERS_DIR/docker-hash.sh + +# Load BUILDKITE Environment Variables for use in docker run +if [[ -f $BUILDKITE_ENV_FILE ]]; then + evars="" + while read -r var; do + evars="$evars --env ${var%%=*}" + done < "$BUILDKITE_ENV_FILE" +fi + +if [[ "$BUILDKITE" == 'true' && "$IMAGE_TAG" == 'ubuntu-18.04' ]]; then + FULL_TAG='eosio/ci-contracts-builder:base-ubuntu-18.04-develop' +fi + +set +e +eval docker run $ARGS $evars $FULL_TAG bash -c \"$COMMANDS\" +EXIT_STATUS=$? + +# buildkite +if [[ "$BUILDKITE" == 'true' ]]; then + cd build + # upload artifacts + echo '+++ :arrow_up: Uploading Artifacts' + echo 'Exporting xUnit XML' + mv -f ./Testing/$(ls ./Testing/ | grep '2' | tail -n 1)/Test.xml test-results.xml + echo 'Uploading artifacts' + buildkite-agent artifact upload test-results.xml + echo 'Done uploading artifacts.' +fi +# re-throw +if [[ "$EXIT_STATUS" != 0 ]]; then + echo "Failing due to non-zero exit status from ctest: $EXIT_STATUS" + exit $EXIT_STATUS +fi \ No newline at end of file diff --git a/.cicd/package.sh b/.cicd/package.sh index 9f75d1134d..dbcb56e50e 100755 --- a/.cicd/package.sh +++ b/.cicd/package.sh @@ -32,8 +32,8 @@ else # Linux if [[ "$IMAGE_TAG" =~ "ubuntu" ]]; then ARTIFACT='*.deb' PACKAGE_TYPE='deb' - PACKAGE_COMMANDS="./generate_package.sh $PACKAGE_TYPE" - elif [[ "$IMAGE_TAG" =~ "centos" ]]; then + PACKAGE_COMMANDS="./generate_package.sh $PACKAGE_TYPE $OS" + elif [[ "$IMAGE_TAG" =~ "centos" || "$IMAGE_TAG" =~ "amazonlinux" ]]; then ARTIFACT='*.rpm' PACKAGE_TYPE='rpm' PACKAGE_COMMANDS="mkdir -p ~/rpmbuild/BUILD && mkdir -p ~/rpmbuild/BUILDROOT && mkdir -p ~/rpmbuild/RPMS && mkdir -p ~/rpmbuild/SOURCES && mkdir -p ~/rpmbuild/SPECS && mkdir -p ~/rpmbuild/SRPMS && yum install -y rpm-build && ./generate_package.sh $PACKAGE_TYPE" @@ -63,4 +63,4 @@ else # Linux fi done -fi \ No newline at end of file +fi diff --git a/.cicd/pipeline.yml b/.cicd/pipeline.yml index a9f896bd6c..c0df6e5efc 100644 --- a/.cicd/pipeline.yml +++ b/.cicd/pipeline.yml @@ -10,20 +10,31 @@ steps: IMAGE_TAG: "amazonlinux-2" agents: queue: "automation-eks-eos-builder-fleet" - timeout: ${TIMEOUT:-10} + timeout: ${TIMEOUT:-60} skip: $SKIP_AMAZON_LINUX_2 - - label: ":centos: CentOS 7.6 - Build" + - label: ":centos: CentOS 7.7 - Build" command: - "./.cicd/build.sh" - "tar -pczf build.tar.gz build && buildkite-agent artifact upload build.tar.gz" env: - IMAGE_TAG: "centos-7.6" + IMAGE_TAG: "centos-7.7" agents: queue: "automation-eks-eos-builder-fleet" - timeout: ${TIMEOUT:-10} + timeout: ${TIMEOUT:-60} skip: $SKIP_CENTOS_7 + - label: ":centos: CentOS 8 - Build" + command: + - "./.cicd/build.sh" + - "tar -pczf build.tar.gz build && buildkite-agent artifact upload build.tar.gz" + env: + IMAGE_TAG: "centos-8" + agents: + queue: "automation-eks-eos-builder-fleet" + timeout: ${TIMEOUT:-60} + skip: $SKIP_CENTOS_8 + - label: ":ubuntu: Ubuntu 16.04 - Build" command: - "./.cicd/build.sh" @@ -32,7 +43,7 @@ steps: IMAGE_TAG: "ubuntu-16.04" agents: queue: "automation-eks-eos-builder-fleet" - timeout: ${TIMEOUT:-10} + timeout: ${TIMEOUT:-60} skip: $SKIP_UBUNTU_16 - label: ":ubuntu: Ubuntu 18.04 - Build" @@ -43,11 +54,23 @@ steps: IMAGE_TAG: "ubuntu-18.04" agents: queue: "automation-eks-eos-builder-fleet" - timeout: ${TIMEOUT:-10} + timeout: ${TIMEOUT:-60} skip: $SKIP_UBUNTU_18 + - label: ":ubuntu: Ubuntu 20.04 - Build" + command: + - "./.cicd/build.sh" + - "tar -pczf build.tar.gz build && buildkite-agent artifact upload build.tar.gz" + env: + IMAGE_TAG: "ubuntu-20.04" + agents: + queue: "automation-eks-eos-builder-fleet" + timeout: ${TIMEOUT:-60} + skip: $SKIP_UBUNTU_20 + - label: ":darwin: macOS 10.14 - Build" command: + - "brew update && brew upgrade" - "brew install git automake libtool wget cmake gmp gettext doxygen graphviz lcov python@3" - "git clone $BUILDKITE_REPO eosio.cdt" - "cd eosio.cdt && if [[ $BUILDKITE_BRANCH =~ ^pull/[0-9]+/head: ]]; then git fetch -v --prune origin refs/pull/$(echo $BUILDKITE_BRANCH | cut -d/ -f2)/head; fi" @@ -55,19 +78,46 @@ steps: - "cd eosio.cdt && ./.cicd/build.sh" - "cd eosio.cdt && tar -pczf build.tar.gz build && buildkite-agent artifact upload build.tar.gz" plugins: - - chef/anka#v0.5.1: + - EOSIO/anka#v0.6.1: no-volume: true inherit-environment-vars: true - vm-name: 10.14.6_6C_14G_40G + vm-name: 10.14.6_6C_14G_80G vm-registry-tag: "clean::cicd::git-ssh::nas::brew::buildkite-agent" modify-cpu: 12 modify-ram: 24 always-pull: true debug: true wait-network: true + - EOSIO/skip-checkout#v0.1.1: + cd: ~ agents: - "queue=mac-anka-large-node-fleet" - skip: $SKIP_MOJAVE + skip: $SKIP_MACOS_10_14 + + - label: ":darwin: macOS 10.15 - Build" + command: + - "brew update && brew upgrade" + - "brew install git automake libtool wget cmake gmp gettext doxygen graphviz lcov python@3" + - "git clone $BUILDKITE_REPO eosio.cdt" + - "cd eosio.cdt && if [[ $BUILDKITE_BRANCH =~ ^pull/[0-9]+/head: ]]; then git fetch -v --prune origin refs/pull/$(echo $BUILDKITE_BRANCH | cut -d/ -f2)/head; fi" + - "cd eosio.cdt && git checkout -f $BUILDKITE_COMMIT && git submodule update --init --recursive" + - "cd eosio.cdt && ./.cicd/build.sh" + - "cd eosio.cdt && tar -pczf build.tar.gz build && buildkite-agent artifact upload build.tar.gz" + plugins: + - EOSIO/anka#v0.6.1: + no-volume: true + inherit-environment-vars: true + vm-name: 10.15.5_6C_14G_80G + vm-registry-tag: "clean::cicd::git-ssh::nas::brew::buildkite-agent" + modify-cpu: 12 + modify-ram: 24 + always-pull: true + debug: true + wait-network: true + agents: + - "queue=mac-anka-large-node-fleet" + skip: $SKIP_MACOS_10_15 + - wait @@ -82,17 +132,28 @@ steps: timeout: ${TIMEOUT:-10} skip: ${SKIP_AMAZON_LINUX_2}${SKIP_UNIT_TESTS} - - label: ":centos: CentOS 7.6 - Unit Tests" + - label: ":centos: CentOS 7.7 - Unit Tests" command: - - "buildkite-agent artifact download build.tar.gz . --step ':centos: CentOS 7.6 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" + - "buildkite-agent artifact download build.tar.gz . --step ':centos: CentOS 7.7 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" - "./.cicd/tests.sh" env: - IMAGE_TAG: "centos-7.6" + IMAGE_TAG: "centos-7.7" agents: queue: "automation-eks-eos-tester-fleet" timeout: ${TIMEOUT:-10} skip: ${SKIP_CENTOS_7}${SKIP_UNIT_TESTS} + - label: ":centos: CentOS 8 - Unit Tests" + command: + - "buildkite-agent artifact download build.tar.gz . --step ':centos: CentOS 8 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" + - "./.cicd/tests.sh" + env: + IMAGE_TAG: "centos-8" + agents: + queue: "automation-eks-eos-tester-fleet" + timeout: ${TIMEOUT:-10} + skip: ${SKIP_CENTOS_8}${SKIP_UNIT_TESTS} + - label: ":ubuntu: Ubuntu 16.04 - Unit Tests" command: - "buildkite-agent artifact download build.tar.gz . --step ':ubuntu: Ubuntu 16.04 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" @@ -115,8 +176,20 @@ steps: timeout: ${TIMEOUT:-10} skip: ${SKIP_UBUNTU_18}${SKIP_UNIT_TESTS} + - label: ":ubuntu: Ubuntu 20.04 - Unit Tests" + command: + - "buildkite-agent artifact download build.tar.gz . --step ':ubuntu: Ubuntu 20.04 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" + - "./.cicd/tests.sh" + env: + IMAGE_TAG: "ubuntu-20.04" + agents: + queue: "automation-eks-eos-tester-fleet" + timeout: ${TIMEOUT:-10} + skip: ${SKIP_UBUNTU_20}${SKIP_UNIT_TESTS} + - label: ":darwin: macOS 10.14 - Unit Tests" command: + - "brew update && brew upgrade" - "brew install git automake libtool wget cmake gmp gettext doxygen graphviz lcov python@3" - "git clone $BUILDKITE_REPO eosio.cdt" - "cd eosio.cdt && if [[ $BUILDKITE_BRANCH =~ ^pull/[0-9]+/head: ]]; then git fetch -v --prune origin refs/pull/$(echo $BUILDKITE_BRANCH | cut -d/ -f2)/head; fi" @@ -124,19 +197,174 @@ steps: - "cd eosio.cdt && buildkite-agent artifact download build.tar.gz . --step ':darwin: macOS 10.14 - Build' && tar -xzf build.tar.gz" - "cd eosio.cdt && ./.cicd/tests.sh" plugins: - - chef/anka#v0.5.1: + - EOSIO/anka#v0.6.1: no-volume: true inherit-environment-vars: true - vm-name: 10.14.6_6C_14G_40G + vm-name: 10.14.6_6C_14G_80G vm-registry-tag: "clean::cicd::git-ssh::nas::brew::buildkite-agent" modify-cpu: 12 modify-ram: 24 always-pull: true debug: true wait-network: true + - EOSIO/skip-checkout#v0.1.1: + cd: ~ agents: - "queue=mac-anka-large-node-fleet" - skip: ${SKIP_MOJAVE}${SKIP_UNIT_TESTS} + skip: ${SKIP_MACOS_10_14}${SKIP_UNIT_TESTS} + + - label: ":darwin: macOS 10.15 - Unit Tests" + command: + - "brew update && brew upgrade" + - "brew install git automake libtool wget cmake gmp gettext doxygen graphviz lcov python@3" + - "git clone $BUILDKITE_REPO eosio.cdt" + - "cd eosio.cdt && if [[ $BUILDKITE_BRANCH =~ ^pull/[0-9]+/head: ]]; then git fetch -v --prune origin refs/pull/$(echo $BUILDKITE_BRANCH | cut -d/ -f2)/head; fi" + - "cd eosio.cdt && git checkout -f $BUILDKITE_COMMIT && git submodule update --init --recursive" + - "cd eosio.cdt && buildkite-agent artifact download build.tar.gz . --step ':darwin: macOS 10.15 - Build' && tar -xzf build.tar.gz" + - "cd eosio.cdt && ./.cicd/tests.sh" + plugins: + - EOSIO/anka#v0.6.1: + no-volume: true + inherit-environment-vars: true + vm-name: 10.15.5_6C_14G_80G + vm-registry-tag: "clean::cicd::git-ssh::nas::brew::buildkite-agent" + modify-cpu: 12 + modify-ram: 24 + always-pull: true + debug: true + wait-network: true + agents: + - "queue=mac-anka-large-node-fleet" + skip: ${SKIP_MACOS_10_15}${SKIP_UNIT_TESTS} + + - label: ":aws: Amazon_Linux 2 - Toolchain Tests" + command: + - "buildkite-agent artifact download build.tar.gz . --step ':aws: Amazon_Linux 2 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" + - "./.cicd/toolchain-tests.sh" + env: + IMAGE_TAG: "amazonlinux-2" + agents: + queue: "automation-eks-eos-tester-fleet" + timeout: ${TIMEOUT:-10} + skip: ${SKIP_AMAZON_LINUX_2}${SKIP_TOOLCHAIN_TESTS} + + - label: ":centos: CentOS 7.7 - Toolchain Tests" + command: + - "buildkite-agent artifact download build.tar.gz . --step ':centos: CentOS 7.7 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" + - "./.cicd/toolchain-tests.sh" + env: + IMAGE_TAG: "centos-7.7" + agents: + queue: "automation-eks-eos-tester-fleet" + timeout: ${TIMEOUT:-10} + skip: ${SKIP_CENTOS_7}${SKIP_TOOLCHAIN_TESTS} + + - label: ":centos: CentOS 8 - Toolchain Tests" + command: + - "buildkite-agent artifact download build.tar.gz . --step ':centos: CentOS 8 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" + - "./.cicd/toolchain-tests.sh" + env: + IMAGE_TAG: "centos-8" + agents: + queue: "automation-eks-eos-tester-fleet" + timeout: ${TIMEOUT:-10} + skip: ${SKIP_CENTOS_8}${SKIP_TOOLCHAIN_TESTS} + + - label: ":ubuntu: Ubuntu 16.04 - Toolchain Tests" + command: + - "buildkite-agent artifact download build.tar.gz . --step ':ubuntu: Ubuntu 16.04 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" + - "./.cicd/toolchain-tests.sh" + env: + IMAGE_TAG: "ubuntu-16.04" + agents: + queue: "automation-eks-eos-tester-fleet" + timeout: ${TIMEOUT:-10} + skip: ${SKIP_UBUNTU_16}${SKIP_TOOLCHAIN_TESTS} + + - label: ":ubuntu: Ubuntu 18.04 - Toolchain Tests" + command: + - "buildkite-agent artifact download build.tar.gz . --step ':ubuntu: Ubuntu 18.04 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" + - "./.cicd/toolchain-tests.sh" + env: + IMAGE_TAG: "ubuntu-18.04" + agents: + queue: "automation-eks-eos-tester-fleet" + timeout: ${TIMEOUT:-10} + skip: ${SKIP_UBUNTU_18}${SKIP_TOOLCHAIN_TESTS} + + - label: ":ubuntu: Ubuntu 20.04 - Toolchain Tests" + command: + - "buildkite-agent artifact download build.tar.gz . --step ':ubuntu: Ubuntu 20.04 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" + - "./.cicd/toolchain-tests.sh" + env: + IMAGE_TAG: "ubuntu-20.04" + agents: + queue: "automation-eks-eos-tester-fleet" + timeout: ${TIMEOUT:-10} + skip: ${SKIP_UBUNTU_20}${SKIP_TOOLCHAIN_TESTS} + + - label: ":darwin: macOS 10.14 - Toolchain Tests" + command: + - "brew update && brew upgrade" + - "brew install git automake libtool wget cmake gmp gettext doxygen graphviz lcov python@3" + - "git clone $BUILDKITE_REPO eosio.cdt" + - "cd eosio.cdt && if [[ $BUILDKITE_BRANCH =~ ^pull/[0-9]+/head: ]]; then git fetch -v --prune origin refs/pull/$(echo $BUILDKITE_BRANCH | cut -d/ -f2)/head; fi" + - "cd eosio.cdt && git checkout -f $BUILDKITE_COMMIT && git submodule update --init --recursive" + - "cd eosio.cdt && buildkite-agent artifact download build.tar.gz . --step ':darwin: macOS 10.14 - Build' && tar -xzf build.tar.gz" + - "cd eosio.cdt && ./.cicd/toolchain-tests.sh" + plugins: + - EOSIO/anka#v0.6.1: + no-volume: true + inherit-environment-vars: true + vm-name: 10.14.6_6C_14G_80G + vm-registry-tag: "clean::cicd::git-ssh::nas::brew::buildkite-agent" + modify-cpu: 12 + modify-ram: 24 + always-pull: true + debug: true + wait-network: true + - EOSIO/skip-checkout#v0.1.1: + cd: ~ + agents: + - "queue=mac-anka-large-node-fleet" + skip: ${SKIP_MACOS_10_14}${SKIP_TOOLCHAIN_TESTS} + + - label: ":darwin: macOS 10.15 - Toolchain Tests" + command: + - "brew update && brew upgrade" + - "brew install git automake libtool wget cmake gmp gettext doxygen graphviz lcov python@3" + - "git clone $BUILDKITE_REPO eosio.cdt" + - "cd eosio.cdt && if [[ $BUILDKITE_BRANCH =~ ^pull/[0-9]+/head: ]]; then git fetch -v --prune origin refs/pull/$(echo $BUILDKITE_BRANCH | cut -d/ -f2)/head; fi" + - "cd eosio.cdt && git checkout -f $BUILDKITE_COMMIT && git submodule update --init --recursive" + - "cd eosio.cdt && buildkite-agent artifact download build.tar.gz . --step ':darwin: macOS 10.15 - Build' && tar -xzf build.tar.gz" + - "cd eosio.cdt && ./.cicd/toolchain-tests.sh" + plugins: + - EOSIO/anka#v0.6.1: + no-volume: true + inherit-environment-vars: true + vm-name: 10.15.5_6C_14G_80G + vm-registry-tag: "clean::cicd::git-ssh::nas::brew::buildkite-agent" + modify-cpu: 12 + modify-ram: 24 + always-pull: true + debug: true + wait-network: true + - EOSIO/skip-checkout#v0.1.1: + cd: ~ + agents: + - "queue=mac-anka-large-node-fleet" + skip: ${SKIP_MACOS_10_14}${SKIP_TOOLCHAIN_TESTS} + + - label: ":ubuntu: Ubuntu 18.04 - Integration Tests" + command: + - "buildkite-agent artifact download build.tar.gz . --step ':ubuntu: Ubuntu 18.04 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" + - "./.cicd/integration-tests.sh" + env: + IMAGE_TAG: "ubuntu-18.04" + agents: + queue: "automation-eks-eos-tester-fleet" + timeout: ${TIMEOUT:-10} + skip: ${SKIP_UBUNTU_18}${SKIP_INTEGRATION_TESTS} - wait: continue_on_failure: true @@ -154,20 +382,62 @@ steps: - wait - - label: ":centos: Centos 7.6 - Package Builder" + - label: ":aws: Amazon_Linux 2 - Package Builder" command: - - "buildkite-agent artifact download build.tar.gz . --step ':centos: CentOS 7.6 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" + - "buildkite-agent artifact download build.tar.gz . --step ':aws: Amazon_Linux 2 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" - "./.cicd/package.sh" env: BUILDKITE_AGENT_ACCESS_TOKEN: - IMAGE_TAG: "centos-7.6" - OS: "centos" # OS and PKGTYPE required for lambdas + IMAGE_TAG: "amazonlinux-2" + OS: "amzn" # OS and PKGTYPE required for lambdas + PKGTYPE: "rpm" + agents: + queue: "automation-eks-eos-tester-fleet" + timeout: ${TIMEOUT:-10} + skip: ${SKIP_AMAZON_LINUX_2}${SKIP_PACKAGE_BUILDER} + + - label: ":centos: Centos 7.7 - Package Builder" + command: + - "buildkite-agent artifact download build.tar.gz . --step ':centos: CentOS 7.7 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" + - "./.cicd/package.sh" + env: + BUILDKITE_AGENT_ACCESS_TOKEN: + IMAGE_TAG: "centos-7.7" + OS: "el7" # OS and PKGTYPE required for lambdas PKGTYPE: "rpm" agents: queue: "automation-eks-eos-tester-fleet" timeout: ${TIMEOUT:-10} skip: ${SKIP_CENTOS_7}${SKIP_PACKAGE_BUILDER} + - label: ":centos: Centos 8 - Package Builder" + command: + - "buildkite-agent artifact download build.tar.gz . --step ':centos: CentOS 8 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" + - "./.cicd/package.sh" + env: + BUILDKITE_AGENT_ACCESS_TOKEN: + IMAGE_TAG: "centos-8" + OS: "centos" # OS and PKGTYPE required for lambdas + PKGTYPE: "rpm" + agents: + queue: "automation-eks-eos-tester-fleet" + timeout: ${TIMEOUT:-10} + skip: ${SKIP_CENTOS_8}${SKIP_PACKAGE_BUILDER} + + - label: ":ubuntu: Ubuntu 16.04 - Package Builder" + command: + - "buildkite-agent artifact download build.tar.gz . --step ':ubuntu: Ubuntu 16.04 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" + - "./.cicd/package.sh" + env: + BUILDKITE_AGENT_ACCESS_TOKEN: + IMAGE_TAG: "ubuntu-16.04" + OS: "ubuntu-16.04" # OS and PKGTYPE required for lambdas + PKGTYPE: "deb" + agents: + queue: "automation-eks-eos-tester-fleet" + timeout: ${TIMEOUT:-10} + skip: ${SKIP_UBUNTU_16}${SKIP_PACKAGE_BUILDER} + - label: ":ubuntu: Ubuntu 18.04 - Package Builder" command: - "buildkite-agent artifact download build.tar.gz . --step ':ubuntu: Ubuntu 18.04 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" @@ -182,6 +452,20 @@ steps: timeout: ${TIMEOUT:-10} skip: ${SKIP_UBUNTU_18}${SKIP_PACKAGE_BUILDER} + - label: ":ubuntu: Ubuntu 20.04 - Package Builder" + command: + - "buildkite-agent artifact download build.tar.gz . --step ':ubuntu: Ubuntu 20.04 - Build' --agent-access-token $$BUILDKITE_AGENT_ACCESS_TOKEN && tar -xzf build.tar.gz" + - "./.cicd/package.sh" + env: + BUILDKITE_AGENT_ACCESS_TOKEN: + IMAGE_TAG: "ubuntu-20.04" + OS: "ubuntu-20.04" # OS and PKGTYPE required for lambdas + PKGTYPE: "deb" + agents: + queue: "automation-eks-eos-tester-fleet" + timeout: ${TIMEOUT:-10} + skip: ${SKIP_UBUNTU_20}${SKIP_PACKAGE_BUILDER} + - label: ":darwin: Mojave - Package Builder" command: - "git clone $BUILDKITE_REPO eosio.cdt" @@ -190,22 +474,53 @@ steps: - "cd eosio.cdt && buildkite-agent artifact download build.tar.gz . --step ':darwin: macOS 10.14 - Build' && tar -xzf build.tar.gz" - "cd eosio.cdt && ./.cicd/package.sh" plugins: - - chef/anka#v0.5.1: + - EOSIO/anka#v0.6.1: no-volume: true inherit-environment-vars: true - vm-name: 10.14.6_6C_14G_40G + vm-name: 10.14.6_6C_14G_80G vm-registry-tag: "clean::cicd::git-ssh::nas::brew::buildkite-agent" always-pull: true debug: true wait-network: true + - EOSIO/skip-checkout#v0.1.1: + cd: ~ agents: - "queue=mac-anka-node-fleet" timeout: 10 - skip: ${SKIP_MOJAVE}${SKIP_PACKAGE_BUILDER} + skip: ${SKIP_MACOS_10_14}${SKIP_PACKAGE_BUILDER} + + - label: ":darwin: Catalina - Package Builder" + command: + - "git clone $BUILDKITE_REPO eosio.cdt" + - "cd eosio.cdt && if [[ $BUILDKITE_BRANCH =~ ^pull/[0-9]+/head: ]]; then git fetch -v --prune origin refs/pull/$(echo $BUILDKITE_BRANCH | cut -d/ -f2)/head; fi" + - "cd eosio.cdt && git checkout -f $BUILDKITE_COMMIT && git submodule update --init --recursive" + - "cd eosio.cdt && buildkite-agent artifact download build.tar.gz . --step ':darwin: macOS 10.15 - Build' && tar -xzf build.tar.gz" + - "cd eosio.cdt && ./.cicd/package.sh" + plugins: + - EOSIO/anka#v0.6.1: + no-volume: true + inherit-environment-vars: true + vm-name: 10.15.5_6C_14G_80G + vm-registry-tag: "clean::cicd::git-ssh::nas::brew::buildkite-agent" + always-pull: true + debug: true + wait-network: true + agents: + - "queue=mac-anka-node-fleet" + timeout: 20 + skip: ${SKIP_MACOS_10_15}${SKIP_PACKAGE_BUILDER} + + - wait + + - label: ":docker::ubuntu: Docker - Build 18.04 Docker Image" + command: "./.cicd/create-docker-from-binary.sh" + agents: + queue: "automation-eks-eos-tester-fleet" + timeout: 10 - label: ":git: Git Submodule Regression Check" command: - "./.cicd/submodule-regression-checker.sh" agents: queue: "automation-basic-builder-fleet" - timeout: 5 \ No newline at end of file + timeout: 5 diff --git a/.cicd/submodule-regression-checker.sh b/.cicd/submodule-regression-checker.sh index 47b4bcacc4..9392ebb43b 100755 --- a/.cicd/submodule-regression-checker.sh +++ b/.cicd/submodule-regression-checker.sh @@ -2,16 +2,15 @@ set -eo pipefail declare -A PR_MAP declare -A BASE_MAP -# Support Travis and BK -if ${TRAVIS:-false}; then - [[ -z $TRAVIS_PULL_REQUEST_BRANCH ]] && echo "Unable to find TRAVIS_PULL_REQUEST_BRANCH ENV. Skipping submodule regression check." && exit 0 - BASE_BRANCH=$TRAVIS_BRANCH - CURRENT_BRANCH=$TRAVIS_PULL_REQUEST_BRANCH - [[ ! -z $TRAVIS_PULL_REQUEST_SLUG ]] && CURRENT_BRANCH=$TRAVIS_COMMIT # When we're not running from a PR, the slug is not set. When we are, we need to use the TRAVIS_COMMIT to be sure we're supporting the Forked PR's merge/code that's in the EOS repo. This is needed for the git log below. -else + +if [[ $BUILDKITE == true ]]; then [[ -z $BUILDKITE_PULL_REQUEST_BASE_BRANCH ]] && echo "Unable to find BUILDKITE_PULL_REQUEST_BASE_BRANCH ENV. Skipping submodule regression check." && exit 0 BASE_BRANCH=$BUILDKITE_PULL_REQUEST_BASE_BRANCH CURRENT_BRANCH=$BUILDKITE_BRANCH +else + [[ -z $GITHUB_BASE_REF ]] && echo "Cannot find \$GITHUB_BASE_REF, so we have nothing to compare submodules to. Skipping submodule regression check." && exit 0 + BASE_BRANCH=$GITHUB_BASE_REF + CURRENT_BRANCH="refs/remotes/pull/$PR_NUMBER/merge" fi echo "getting submodule info for $CURRENT_BRANCH" @@ -26,17 +25,9 @@ while read -r a b; do BASE_MAP[$a]=$b done < <(git submodule --quiet foreach --recursive 'echo $path `git log -1 --format=%ct`') -# We need to switch back to the PR ref/head so we can git log properly -if [[ $TRAVIS == true && ! -z $TRAVIS_PULL_REQUEST_SLUG ]]; then - echo "git fetch origin +refs/pull/$TRAVIS_PULL_REQUEST/merge:" - git fetch origin +refs/pull/$TRAVIS_PULL_REQUEST/merge: 1> /dev/null - echo "switching back to $TRAVIS_COMMIT" - echo 'git checkout -qf FETCH_HEAD' - git checkout -qf FETCH_HEAD 1> /dev/null -elif [[ $BUILDKITE == true ]]; then - echo "switching back to $CURRENT_BRANCH" - git checkout -f $CURRENT_BRANCH 1> /dev/null -fi +echo "switching back to $CURRENT_BRANCH..." +echo "git checkout -qf $CURRENT_BRANCH" +git checkout -qf $CURRENT_BRANCH 1> /dev/null for k in "${!BASE_MAP[@]}"; do base_ts=${BASE_MAP[$k]} diff --git a/.cicd/tests.sh b/.cicd/tests.sh index 41a4537b36..e0d2a2cedc 100755 --- a/.cicd/tests.sh +++ b/.cicd/tests.sh @@ -23,8 +23,6 @@ else # Linux . $HELPERS_DIR/docker-hash.sh - [[ $TRAVIS == true ]] && ARGS="$ARGS -e JOBS -e CCACHE_DIR=/opt/.ccache" - # Load BUILDKITE Environment Variables for use in docker run if [[ -f $BUILDKITE_ENV_FILE ]]; then evars="" diff --git a/.cicd/toolchain-tests.sh b/.cicd/toolchain-tests.sh new file mode 100755 index 0000000000..7437600f97 --- /dev/null +++ b/.cicd/toolchain-tests.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -eo pipefail +. ./.cicd/helpers/general.sh + +mkdir -p $BUILD_DIR + +if [[ $(uname) == 'Darwin' ]]; then + + # You can't use chained commands in execute + cd $BUILD_DIR + TEST="./tools/toolchain-tester/toolchain-tester ../tests/toolchain/" + bash -c "$TEST" + +else # Linux + + ARGS=${ARGS:-"--rm --init -v $(pwd):$MOUNTED_DIR"} + + if [[ "$IMAGE_TAG" == "centos-8" ]]; then + PRE_COMMANDS="cd $MOUNTED_DIR/build" + PACKAGE_COMMANDS="yum install -y python38" + SET_PYTHON_PATH="export CICD_PYTHON_PATH=/usr/bin/python3.8" + TEST="./tools/toolchain-tester/toolchain-tester ../tests/toolchain/" + + COMMANDS="$PRE_COMMANDS && $PACKAGE_COMMANDS && $SET_PYTHON_PATH && $TEST" + else + PRE_COMMANDS="cd $MOUNTED_DIR/build" + SET_PYTHON_PATH="export CICD_PYTHON_PATH=/usr/local/bin/python3" + TEST="./tools/toolchain-tester/toolchain-tester ../tests/toolchain/" + + COMMANDS="$PRE_COMMANDS && $SET_PYTHON_PATH && $TEST" + fi + + . $HELPERS_DIR/docker-hash.sh + + # Load BUILDKITE Environment Variables for use in docker run + if [[ -f $BUILDKITE_ENV_FILE ]]; then + evars="" + while read -r var; do + evars="$evars --env ${var%%=*}" + done < "$BUILDKITE_ENV_FILE" + fi + + eval docker run $ARGS $evars $FULL_TAG bash -c \"$COMMANDS\" + +fi diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..f153c1d1ee --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,363 @@ +name: Pull Request +on: [pull_request] + +env: + PR_NUMBER: ${{ toJson(github.event.number) }} + +jobs: + submodule_regression_check: + if: github.event.pull_request.base.repo.id != github.event.pull_request.head.repo.id + name: Submodule Regression Check + runs-on: ubuntu-latest + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Submodule Regression Check + run: ./.cicd/submodule-regression-checker.sh + + + amazon_linux-2-build: + if: github.event.pull_request.base.repo.id != github.event.pull_request.head.repo.id + name: Amazon_Linux 2 | Build + runs-on: ubuntu-latest + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Build + run: | + ./.cicd/build.sh + tar -pczf build.tar.gz build + env: + IMAGE_TAG: amazonlinux-2 + - name: Upload Build Artifact + uses: actions/upload-artifact@v1 + with: + name: amazon_linux-2-build + path: build.tar.gz + amazon_linux-2-unit-test: + name: Amazon_Linux 2 | Unit Test + runs-on: ubuntu-latest + needs: amazon_linux-2-build + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Download Build Artifact + uses: actions/download-artifact@v1 + with: + name: amazon_linux-2-build + - name: Unit Test + run: | + tar -xzf amazon_linux-2-build/build.tar.gz + ./.cicd/tests.sh + env: + IMAGE_TAG: amazonlinux-2 + amazon_linux-2-toolchain-test: + name: Amazon_Linux 2 | Toolchain Test + runs-on: ubuntu-latest + needs: amazon_linux-2-build + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Download Build Artifact + uses: actions/download-artifact@v1 + with: + name: amazon_linux-2-build + - name: Toolchain Test + run: | + tar -xzf amazon_linux-2-build/build.tar.gz + ./.cicd/toolchain-tests.sh + env: + IMAGE_TAG: amazonlinux-2 + + + centos-77-build: + if: github.event.pull_request.base.repo.id != github.event.pull_request.head.repo.id + name: CentOS 7.7 | Build + runs-on: ubuntu-latest + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Build + run: | + ./.cicd/build.sh + tar -pczf build.tar.gz build + env: + IMAGE_TAG: centos-7.7 + - name: Upload Build Artifact + uses: actions/upload-artifact@v1 + with: + name: centos-77-build + path: build.tar.gz + centos-77-unit-test: + name: CentOS 7.7 | Unit Test + runs-on: ubuntu-latest + needs: centos-77-build + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Download Build Artifact + uses: actions/download-artifact@v1 + with: + name: centos-77-build + - name: Unit Test + run: | + tar -xzf centos-77-build/build.tar.gz + ./.cicd/tests.sh + env: + IMAGE_TAG: centos-7.7 + centos-77-toolchain-test: + name: CentOS 7.7 | Toolchain Test + runs-on: ubuntu-latest + needs: centos-77-build + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Download Build Artifact + uses: actions/download-artifact@v1 + with: + name: centos-77-build + - name: Toolchain Test + run: | + tar -xzf centos-77-build/build.tar.gz + ./.cicd/toolchain-tests.sh + env: + IMAGE_TAG: centos-7.7 + + + ubuntu-1604-build: + if: github.event.pull_request.base.repo.id != github.event.pull_request.head.repo.id + name: Ubuntu 16.04 | Build + runs-on: ubuntu-latest + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Build + run: | + ./.cicd/build.sh + tar -pczf build.tar.gz build + env: + IMAGE_TAG: ubuntu-16.04 + - name: Upload Build Artifact + uses: actions/upload-artifact@v1 + with: + name: ubuntu-1604-build + path: build.tar.gz + ubuntu-1604-unit-test: + name: Ubuntu 16.04 | Unit Test + runs-on: ubuntu-latest + needs: ubuntu-1604-build + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Download Build Artifact + uses: actions/download-artifact@v1 + with: + name: ubuntu-1604-build + - name: Unit Test + run: | + tar -xzf ubuntu-1604-build/build.tar.gz + ./.cicd/tests.sh + env: + IMAGE_TAG: ubuntu-16.04 + ubuntu-1604-toolchain-test: + name: Ubuntu 16.04 | Toolchain Test + runs-on: ubuntu-latest + needs: ubuntu-1604-build + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Download Build Artifact + uses: actions/download-artifact@v1 + with: + name: ubuntu-1604-build + - name: Toolchain Test + run: | + tar -xzf ubuntu-1604-build/build.tar.gz + ./.cicd/toolchain-tests.sh + env: + IMAGE_TAG: ubuntu-16.04 + + + ubuntu-1804-build: + if: github.event.pull_request.base.repo.id != github.event.pull_request.head.repo.id + name: Ubuntu 18.04 | Build + runs-on: ubuntu-latest + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Build + run: | + ./.cicd/build.sh + tar -pczf build.tar.gz build + env: + IMAGE_TAG: ubuntu-18.04 + - name: Upload Build Artifact + uses: actions/upload-artifact@v1 + with: + name: ubuntu-1804-build + path: build.tar.gz + ubuntu-1804-unit-test: + name: Ubuntu 18.04 | Unit Test + runs-on: ubuntu-latest + needs: ubuntu-1804-build + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Download Build Artifact + uses: actions/download-artifact@v1 + with: + name: ubuntu-1804-build + - name: Unit Test + run: | + tar -xzf ubuntu-1804-build/build.tar.gz + ./.cicd/tests.sh + env: + IMAGE_TAG: ubuntu-18.04 + ubuntu-1804-toolchain-test: + name: Ubuntu 18.04 | Toolchain Test + runs-on: ubuntu-latest + needs: ubuntu-1804-build + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Download Build Artifact + uses: actions/download-artifact@v1 + with: + name: ubuntu-1804-build + - name: Toolchain Test + run: | + tar -xzf ubuntu-1804-build/build.tar.gz + ./.cicd/toolchain-tests.sh + env: + IMAGE_TAG: ubuntu-18.04 + + + macos-1015-build: + if: github.event.pull_request.base.repo.id != github.event.pull_request.head.repo.id + name: MacOS 10.15 | Build + runs-on: macos-latest + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Build + run: | + brew install git automake libtool wget cmake gmp gettext doxygen graphviz lcov python@3 + ./.cicd/build.sh + tar -pczf build.tar.gz build + - name: Upload Build Artifact + uses: actions/upload-artifact@v1 + with: + name: macos-1015-build + path: build.tar.gz + macos-1015-unit-test: + name: MacOS 10.15 | Unit Test + runs-on: macos-latest + needs: macos-1015-build + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Download Build Artifact + uses: actions/download-artifact@v1 + with: + name: macos-1015-build + - name: Unit Test + run: | + brew install git automake libtool wget cmake gmp gettext doxygen graphviz lcov python@3 + tar -xzf macos-1015-build/build.tar.gz + ./.cicd/tests.sh + macos-1015-toolchain-test: + name: MacOS 10.15 | Toolchain Test + runs-on: macos-latest + needs: macos-1015-build + steps: + - name: Checkout + run: | + git clone https://github.com/${GITHUB_REPOSITORY} . + git fetch -v --prune origin +refs/pull/${PR_NUMBER}/merge:refs/remotes/pull/${PR_NUMBER}/merge + git checkout --force --progress refs/remotes/pull/${PR_NUMBER}/merge + git submodule sync --recursive + git submodule update --init --force --recursive + - name: Download Build Artifact + uses: actions/download-artifact@v1 + with: + name: macos-1015-build + - name: Toolchain Test + run: | + brew install git automake libtool wget cmake gmp gettext doxygen graphviz lcov python@3 + tar -xzf macos-1015-build/build.tar.gz + ./.cicd/toolchain-tests.sh diff --git a/.gitignore b/.gitignore index 3febf9557f..229f8745ef 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,9 @@ # Build build/* +cmake-build-debug/ +examples/multi_index_example/build +examples/hello/build # Python __pycache__/ @@ -43,3 +46,6 @@ __pycache__/ # Editor Files .vscode/ +*.vim +.idea/ +.ccls-cache/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8858c04709..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -language: cpp -git: - depth: false -if: fork = true OR type = api OR type = cron -matrix: - include: - - os: linux - dist: xenial - services: docker - env: - - IMAGE_TAG='ubuntu-18.04' - - os: linux - dist: xenial - services: docker - env: - - IMAGE_TAG='ubuntu-16.04' - - os: linux - dist: xenial - services: docker - env: - - IMAGE_TAG='amazonlinux-2' - - os: linux - dist: xenial - services: docker - env: - - IMAGE_TAG='centos-7.6' -script: "./.cicd/build.sh && ./.cicd/tests.sh" diff --git a/CMakeLists.txt b/CMakeLists.txt index 81e9583759..871bf33972 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,11 +25,10 @@ else() endif() endif() - set(VERSION_MAJOR 1) -set(VERSION_MINOR 7) +set(VERSION_MINOR 8) set(VERSION_PATCH 0) -#set(VERSION_SUFFIX rc1) +#set(VERSION_SUFFIX rc2) if (VERSION_SUFFIX) set(VERSION_FULL "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}-${VERSION_SUFFIX}") @@ -39,8 +38,19 @@ endif() set(CDT_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/eosio.cdt) +option(ENABLE_NATIVE_COMPILER "enable native builds with the eosio.cdt toolchain" ON) +option(ENABLE_TESTS "enable building tests" ON) + include(GNUInstallDirs) +if(NOT TOOLS_BUILD_TYPE) + set(TOOLS_BUILD_TYPE "Release") +endif() + +if(NOT LIBS_BUILD_TYPE) + set(LIBS_BUILD_TYPE "Release") +endif() + include(modules/ClangExternalProject.txt) include(modules/ToolsExternalProject.txt) @@ -58,7 +68,6 @@ set(CDT_ROOT_DIR ${CDT_INSTALL_PREFIX}) configure_file(${CMAKE_SOURCE_DIR}/modules/eosio.cdt-config.cmake ${CMAKE_BINARY_DIR}/modules/eosio.cdt-config.cmake @ONLY) configure_file(${CMAKE_SOURCE_DIR}/modules/EosioCDTMacros.cmake.in ${CMAKE_BINARY_DIR}/modules/EosioCDTMacros.cmake @ONLY) configure_file(${CMAKE_SOURCE_DIR}/modules/EosioWasmToolchain.cmake.in ${CMAKE_BINARY_DIR}/modules/EosioWasmToolchain.cmake @ONLY) -install(FILES ${CMAKE_BINARY_DIR}/modules/EosioCDTMacros.cmake DESTINATION ${CDT_INSTALL_PREFIX}/lib/cmake/eosio.cdt) set(CDT_ROOT_DIR "_PREFIX_") configure_file(${CMAKE_SOURCE_DIR}/modules/EosioCDTMacros.cmake.in ${CMAKE_BINARY_DIR}/modules/EosioCDTMacrosPackage.cmake @ONLY) @@ -90,6 +99,7 @@ configure_file(${CMAKE_SOURCE_DIR}/scripts/generate_tarball.sh ${CMAKE_BINARY_DI # add licenses configure_file(${CMAKE_SOURCE_DIR}/eosio_llvm/LICENSE.TXT ${CMAKE_BINARY_DIR}/licenses/llvm.license COPYONLY) configure_file(${CMAKE_SOURCE_DIR}/libraries/boost/boost.license ${CMAKE_BINARY_DIR}/licenses/boost.license COPYONLY) +configure_file(${CMAKE_SOURCE_DIR}/libraries/meta_refl/LICENSE ${CMAKE_BINARY_DIR}/licenses/meta_refl.license COPYONLY) configure_file(${CMAKE_SOURCE_DIR}/tools/external/wabt/LICENSE ${CMAKE_BINARY_DIR}/licenses/wabt.license COPYONLY) configure_file(${CMAKE_SOURCE_DIR}/tools/jsoncons/LICENSE ${CMAKE_BINARY_DIR}/licenses/jsoncons.license COPYONLY) configure_file(${CMAKE_SOURCE_DIR}/LICENSE ${CMAKE_BINARY_DIR}/licenses/eosio.cdt.license COPYONLY) diff --git a/Doxyfile b/Doxyfile index 266e1614ae..171f16435a 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1980,7 +1980,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = EOSIO_CDT_DOXYGEN # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/LICENSE b/LICENSE index 22d36d65db..df058142c3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2017-2019 block.one and its contributors. All rights reserved. +Copyright (c) 2017-2021 block.one and its contributors. All rights reserved. The MIT License diff --git a/README.md b/README.md index 9ff4c63570..1dd41a3157 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # EOSIO.CDT (Contract Development Toolkit) -## Version : 1.7.0 +## Version : 1.8.0 -EOSIO.CDT is a toolchain for WebAssembly (WASM) and set of tools to facilitate smart contract development for the EOSIO platform. In addition to being a general purpose WebAssembly toolchain, [EOSIO](https://github.com/eosio/eos) specific optimizations are available to support building EOSIO smart contracts. This new toolchain is built around [Clang 7](https://github.com/eosio/llvm), which means that EOSIO.CDT has the most currently available optimizations and analyses from LLVM, but as the WASM target is still considered experimental, some optimizations are incomplete or not available. +EOSIO.CDT is a toolchain for WebAssembly (WASM) and a set of tools to facilitate smart contract development for the EOSIO platform. In addition to being a general purpose WebAssembly toolchain, [EOSIO](https://github.com/eosio/eos) specific optimizations are available to support building EOSIO smart contracts. This new toolchain is built around [Clang 9](https://github.com/eosio/llvm), which means that EOSIO.CDT has the most currently available optimizations and analyses from LLVM, but as the WASM target is still considered experimental, some optimizations are incomplete or not available. ### New Introductions As of this release two new repositories are under the suite of tools provided by **EOSIO.CDT**. These are the [Ricardian Template Toolkit](https://github.com/eosio/ricardian-template-toolkit) and the [Ricardian Specification](https://github.com/eosio/ricardian-spec). The **Ricardian Template Toolkit** is a set of libraries to facilitate smart contract writers in crafting their Ricardian contracts. The Ricardian specification is the working specification for the above mentioned toolkit. Please note that both projects are **alpha** releases and are subject to change. @@ -28,8 +28,8 @@ brew remove eosio.cdt ### Debian Package Install ```sh -$ wget https://github.com/eosio/eosio.cdt/releases/download/v1.7.0/eosio.cdt_1.7.0-1-ubuntu-18.04_amd64.deb -$ sudo apt install ./eosio.cdt_1.7.0-1-ubuntu-18.04_amd64.deb +wget https://github.com/eosio/eosio.cdt/releases/download/v1.8.0/eosio.cdt_1.8.0-1-ubuntu-18.04_amd64.deb +sudo apt install ./eosio.cdt_1.8.0-1-ubuntu-18.04_amd64.deb ``` ### Debian Package Uninstall @@ -39,8 +39,8 @@ sudo apt remove eosio.cdt ### RPM Package Install ```sh -$ wget https://github.com/eosio/eosio.cdt/releases/download/v1.7.0/eosio.cdt-1.7.0-1.el7.x86_64.rpm -$ sudo yum install ./eosio.cdt-1.7.0-1.el7.x86_64.rpm +wget https://github.com/eosio/eosio.cdt/releases/download/v1.8.0/eosio.cdt-1.8.0-1.el7.x86_64.rpm +sudo yum install ./eosio.cdt-1.8.0-1.el7.x86_64.rpm ``` ### RPM Package Uninstall @@ -74,7 +74,7 @@ sudo rm /usr/local/bin/eosio-* ``` ## Installed Tools - +--- * eosio-cpp * eosio-cc * eosio-ld @@ -87,22 +87,15 @@ sudo rm /usr/local/bin/eosio-* * eosio-objdump * eosio-readelf -Below tools are not installed after brew install, you get them only by building the repository and installing from scracth, [see here](#guided_installation_or_building_from_scratch) -eosio-abidiff -eosio-ranlib -eosio-ar -eosio-objdump -eosio-readelf - ## Contributing -[Contributing Guide](../CONTRIBUTING.md) +[Contributing Guide](./CONTRIBUTING.md) -[Code of Conduct](../CONTRIBUTING.md#conduct) +[Code of Conduct](./CONTRIBUTING.md#conduct) ## License -[MIT](../LICENSE) +[MIT](./LICENSE) ## Important diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 63c170d8eb..0000000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM ubuntu:18.04 - -# Arguments that may be overridden by the user -ARG release=latest - -# Install required packages -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install openssl ca-certificates curl wget && rm -rf /var/lib/apt/lists/* - -# Install CDT from deb package -ADD install_deb.sh / -RUN /install_deb.sh $release && rm -f install_deb.sh - -USER root diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile deleted file mode 100644 index 584686a6ea..0000000000 --- a/docker/dev/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM ubuntu:18.04 - -# Install required packages -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install openssl ca-certificates curl wget && rm -rf /var/lib/apt/lists/* - -# Install CDT from deb package -ADD build/packages/*.deb / -RUN for filename in $(ls *.deb); do /usr/bin/dpkg -i "$filename" && rm -f "$filename"; done - -USER root \ No newline at end of file diff --git a/docker/dockerfile b/docker/dockerfile new file mode 100644 index 0000000000..20978c92e0 --- /dev/null +++ b/docker/dockerfile @@ -0,0 +1,6 @@ +FROM ubuntu:18.04 +COPY *.deb / +RUN apt-get update && \ + apt-get install -y /*.deb && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* diff --git a/docker/install_deb.sh b/docker/install_deb.sh deleted file mode 100755 index 43c6cb25a9..0000000000 --- a/docker/install_deb.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -if [ "$1" = "" ]; then - echo "In order to continue, you must specify either latest or the release version." - exit 1 -elif [ "$1" = "latest" ]; then - deb=$(/usr/bin/curl -s https://api.github.com/repos/EOSIO/eosio.cdt/releases/latest | grep "browser_download_url.*deb" | cut -d '"' -f 4) - filename=$(/usr/bin/curl -s https://api.github.com/repos/EOSIO/eosio.cdt/releases/latest | grep "name.*deb" | cut -d '"' -f 4) -else - deb=$(/usr/bin/curl -s https://api.github.com/repos/EOSIO/eosio.cdt/releases/tags/$1 | grep "browser_download_url.*deb" | cut -d '"' -f 4) - filename=$(/usr/bin/curl -s https://api.github.com/repos/EOSIO/eosio.cdt/releases/tags/$1 | grep "name.*deb" | cut -d '"' -f 4) -fi - -if [ "$deb" = "" ]; then - echo "Either $1 is not a valid release, or there is not a published .deb package for the release." - exit 1 -fi - -/usr/bin/wget $deb && /usr/bin/dpkg -i "$filename" && rm -f "$filename" diff --git a/docs.json b/docs.json index 28259a4389..f9fb477152 100644 --- a/docs.json +++ b/docs.json @@ -1,30 +1,24 @@ { "name": "eosio.cdt", "generators": [ - { + { "name": "collate_markdown", "options": { - "docs_dir": "docs", - "disable_default_filters": true, - "filters": [ - { "name": "sort" }, - { "name": "remove_extension" }, - { "name": "sanitize", "options": { "exclude": ["command-reference/eosio-*.md"] } }, - { "name": "capitalize", "options": { "exclude": ["command-reference/eosio-*.md"] } } - ] + "docs_dir": "docs" } }, { "name": "doxygen_to_xml", "options": { "INPUT": "libraries/eosiolib", - "EXCLUDE": "libraries/eosiolib/memory.h libraries/eosiolib/memory.hpp libraries/eosiolib/action.h libraries/eosiolib/permission.h libraries/eosiolib/privileged.h libraries/eosiolib/print.h libraries/eosiolib/system.h", - "EXCLUDE_PATTERNS": "*.cpp *.c *.h" - }, - "disable_default_filters": true, - "filters": [] + "EXCLUDE_PATTERNS": "*.cpp *.c", + "ENABLE_PREPROCESSING": "YES", + "MACRO_EXPANSION": "YES", + "EXPAND_ONLY_PREDEF": "YES", + "PREDEFINED": "__attribute__(x)=" + } }, - { + { "name": "doxybook", "options": {} } diff --git a/docs/02_installation.md b/docs/02_installation.md index 88952be309..a8128eaa99 100644 --- a/docs/02_installation.md +++ b/docs/02_installation.md @@ -7,46 +7,53 @@ EOSIO.CDT currently supports Mac OS X brew, Linux x86_64 Debian packages, and Li **If you have previously installed EOSIO.CDT, run the `uninstall` script (it is in the directory where you cloned EOSIO.CDT) before downloading and using the binary releases.** ## Mac OS X Brew Install + ```sh -$ brew tap eosio/eosio.cdt -$ brew install eosio.cdt +brew tap eosio/eosio.cdt +brew install eosio.cdt ``` ## Mac OS X Brew Uninstall + ```sh -$ brew remove eosio.cdt +brew remove eosio.cdt ``` ## Debian Package Install + ```sh -$ wget https://github.com/eosio/eosio.cdt/releases/download/v1.6.3/eosio.cdt_1.6.3-1-ubuntu-18.04_amd64.deb -$ sudo apt install ./eosio.cdt_1.6.3-1-ubuntu-18.04_amd64.deb +wget https://github.com/EOSIO/eosio.cdt/releases/download/v1.8.0/eosio.cdt_1.8.0-1-ubuntu-18.04_amd64.deb +sudo apt install ./eosio.cdt_1.8.0-1-ubuntu-18.04_amd64.deb ``` ## Debian Package Uninstall + ```sh -$ sudo apt remove eosio.cdt +sudo apt remove eosio.cdt ``` ## RPM Package Install + ```sh -$ wget https://github.com/eosio/eosio.cdt/releases/download/v1.6.3/eosio.cdt-1.6.3-1.el7.x86_64.rpm -$ sudo yum install ./eosio.cdt-1.6.3-1.el7.x86_64.rpm +wget https://github.com/EOSIO/eosio.cdt/releases/download/v1.8.0/eosio.cdt-1.8.0-1.el7.x86_64.rpm +sudo yum install ./eosio.cdt-1.8.0-1.el7.x86_64.rpm ``` ## RPM Package Uninstall + ```sh -$ sudo yum remove eosio.cdt +sudo yum remove eosio.cdt ``` # Guided Installation or Building from Scratch + ```sh -$ git clone --recursive https://github.com/eosio/eosio.cdt -$ cd eosio.cdt -$ mkdir build -$ cd build -$ cmake .. -$ make -j8 +git clone --recursive https://github.com/eosio/eosio.cdt +cd eosio.cdt +mkdir build +cd build +cmake .. +make -j8 ``` From here onward you can build your contracts code by simply exporting the `build` directory to your path, so you don't have to install globally (makes things cleaner). @@ -59,12 +66,11 @@ sudo make install ## Uninstall after manual installation ```sh -$ sudo rm -fr /usr/local/eosio.cdt -$ sudo rm -fr /usr/local/lib/cmake/eosio.cdt -$ sudo rm /usr/local/bin/eosio-* +sudo rm -fr /usr/local/eosio.cdt +sudo rm -fr /usr/local/lib/cmake/eosio.cdt +sudo rm /usr/local/bin/eosio-* ``` - # Installed Tools * eosio-cpp @@ -79,14 +85,13 @@ $ sudo rm /usr/local/bin/eosio-* * eosio-objdump * eosio-readelf -Below tools are not installed after brew install, you get them only by building the repository and installing from scracth, [see here](#guided_installation_or_building_from_scratch) -eosio-abidiff -eosio-ranlib -eosio-ar -eosio-objdump -eosio-readelf +The following tools are not installed after brew install, you get them when you build the repository and install it from scratch, [see here](#guided-installation-or-building-from-scratch): +* eosio-abidiff +* eosio-ranlib +* eosio-ar +* eosio-objdump +* eosio-readelf License - [MIT](../LICENSE) diff --git a/docs/03_command-reference/eosio-abidiff.md b/docs/03_command-reference/eosio-abidiff.md index eb41f9ad45..92f155739f 100644 --- a/docs/03_command-reference/eosio-abidiff.md +++ b/docs/03_command-reference/eosio-abidiff.md @@ -11,7 +11,6 @@ $ eosio-abidiff hello.abi old_hello.abi ``` This will generate dump the report output to the console. - ``` OVERVIEW: eosio-abidiff USAGE: eosio-abidiff [options] ... ... diff --git a/docs/03_command-reference/eosio-abigen.md b/docs/03_command-reference/eosio-abigen.md index 50b3a3a692..3650985d91 100644 --- a/docs/03_command-reference/eosio-abigen.md +++ b/docs/03_command-reference/eosio-abigen.md @@ -13,7 +13,6 @@ $ eosio-abigen hello.cpp --contract=hello --output=hello.abi This will generate one file: * The generated ABI file (hello.abi) - ``` USAGE: eosio-abigen [options] [... ] diff --git a/docs/03_command-reference/eosio-init.md b/docs/03_command-reference/eosio-init.md index 2f83eb4333..c5fa03308e 100644 --- a/docs/03_command-reference/eosio-init.md +++ b/docs/03_command-reference/eosio-init.md @@ -4,13 +4,13 @@ content_title: eosio-init tool This tool is used to generate a skeleton smart contract and directory structure. To generate a new smart contract project you can either generate a "bare" project (no CMake) or the default is to generate a CMake project. + Example: ```bash -$ eosio-abigen hello.cpp --contract=hello --output=hello.abi +$ eosio-init --path=\destination\path\where\to\generate\project\ --project=hello_contract_folder ``` -This will generate one file: -* The generated ABI file (hello.abi) +This will generate a project folder `hello_contract_folder` in the path `\destination\path\where\to\generate\project\` containing the skeleton smart contract and directory structure. ``` USAGE: eosio-init [options] diff --git a/docs/04_upgrading/1.2-to-1.3.md b/docs/04_upgrading/1.2-to-1.3.md index 840c22b6b0..66835191da 100644 --- a/docs/04_upgrading/1.2-to-1.3.md +++ b/docs/04_upgrading/1.2-to-1.3.md @@ -210,7 +210,7 @@ For an example contract of ABI generation see the file ./examples/abigen_test/te ## Fixing an ABI or Writing an ABI Manually - The sections to the ABI are pretty simple to understand and the syntax is purely JSON, so it is reasonable to write an ABI file manually. - The ABI generation will never be completely perfect for every contract written. Advanced features of the newest version of the ABI will require manual construction of the ABI, and odd and advanced C++ patterns could capsize the generator's type deductions. So having a good knowledge of how to write an ABI should be an essential piece of knowledge of a smart contract writer. -- Please refer to [developers.eos.io "How to Write an ABI File"](https://developers.eos.io/eosio-cpp/docs/how-to-write-an-abi) to learn about the different sections of an ABI. +- Read ["How to Write an ABI File"](../06_how-to-guides/70_how-to-write-an-abi-file.md) to learn about the different sections of an ABI. ## Adding Ricardian Contracts and Clauses to ABI - As of EOSIO.CDT v1.4.0, the ABI generator will try to automatically import contracts and clauses into the generated ABI. There are a few caveats to this, one is a strict naming policy of the files and an HTML tag used to mark each Ricardian contract and each clause. @@ -223,5 +223,4 @@ For an example contract of ABI generation see the file ./examples/abigen_test/te License - [MIT](../../LICENSE) diff --git a/docs/04_upgrading/1.5-to-1.6.md b/docs/04_upgrading/1.5-to-1.6.md index 83d438fec6..5571d8bde6 100644 --- a/docs/04_upgrading/1.5-to-1.6.md +++ b/docs/04_upgrading/1.5-to-1.6.md @@ -9,7 +9,7 @@ In `eosio.cdt` v1.6.0, `eosiolib` will now be partitioned into 3 groups. These a - Contracts - Core - The `CAPI` will only be available to contract writers using `eosio-cc` and purely for `C` smart contracts. - - The `Contracts` will be available to `eosio-cpp` for smart contract writing. + - The `Contracts` will be available to `eosio-cpp` for smart contract writing. - `Core` will be available to `eosio-cpp` for any of the modes (present and future). - `eosio-cpp` in `fnative` mode will have access to all of these groups. @@ -125,7 +125,7 @@ This entire API is now only available to C developers (i.e. using `eosio-cc`), a There is no more need to add the `EOSIO_DISPATCH` macro to your smart contracts. The compiler/linker will now automatically generate a dispatcher for you the proper `eosio::contract`, `eosio::action` and `eosio::on_notify` attributes are used. Of course, if you don't have these attributes then you will still need to either use the old macro or hand write the `apply` function yourself. ### How the auto dispatcher will work -Given that you have marked your classes with the `eosio::contract` macro and any sub-contracts with the macro with the same given name (i.e. `eosio::contract("")`) then any actions and notify handlers that are contained within these will be dispatchable by your smart contract. This will allow for aggregate patterns for smart contract development and better separation of concerns. +Given that you have marked your classes with the `eosio::contract` macro and any sub-contracts with the macro with the same given name (i.e. `eosio::contract("")`) then any actions and notify handlers that are contained within these will be dispatchable by your smart contract. This will allow for aggregate patterns for smart contract development and better separation of concerns. In addition to actions and notification handlers, two new "hooks" are available. - `bool pre_dispatch(name self, name first_receiver, name action)` @@ -142,5 +142,4 @@ If the dispatcher is in notification handling mode and if your contract receives For a real world example of this new style of contract in use see `tests/unit/test_contracts/simple_test.cpp`. License - [MIT](../../LICENSE) diff --git a/docs/05_best-practices/02_naming-conventions.md b/docs/05_best-practices/02_naming-conventions.md new file mode 100644 index 0000000000..01a56b694c --- /dev/null +++ b/docs/05_best-practices/02_naming-conventions.md @@ -0,0 +1,54 @@ +--- +content_title: Naming conventions +--- + +When implementing EOSIO smart contracts and when storing data in an EOSIO blockchain, it is important to follow the established EOSIO conventions for naming accounts, actions, tables, etc. + +## EOSIO names + +* Applies to all EOSIO encoded names (accounts, actions, tables, etc.) +* Encoded as a 64-bit unsigned integer (`uint64_t`) on the blockchain. +* First 12 characters, if any, encoded in `base32` using characters: `.`, `1-5`, `a-z` +* 13th character, if applicable, encoded in `base16` using characters: `.`, `1-5`, `a-j` + +### Standard account names + +* Must contain exactly 12 characters from the `base32` set: `.`, `1-5`, `a-z` +* 13th character not allowed or lesser than 12 characters +* Must start with a lowercase letter `a-z` +* Must not end in a dot `.` character + +### Non-standard account names + +* May contain between 1 and 12 characters from the `base32` set: `.`, `1-5`, `a-z` +* 13th character not allowed in account name +* Must not end in a dot `.` character + +### Table, struct, class, function (action) names + +* May contain between 1 and 13 characters. +* First 12 characters, if any, from the `base32` set: `.`, `1-5`, `a-z` +* 13th character, if any, from the `base16` set: `.`, `1-5`, `a-j` + +### Format + +The figure below showcases a 12 character string formatted into a 64-bit unsigned integer. Note: the 13th char, if any, contains 24 = 16 cases per 1 digit (char): 1 (`.`) + 5 (`1-5`) + 10 (`a-j`). + +![](naming-conventions-format.png "EOSIO name format") + +## Encoding and decoding + +EOSIO name objects can be created, encoded, and decoded via the `eosio::name` class. + +1. To encode an `std::string` into an EOSIO name object, use the appropriate `eosio::name()` constructor. +2. To encode a `char *` string literal into an EOSIO name object, you can also use the `""_n` operator. +3. To decode an EOSIO name object into an `std::string`, use the `eosio::to_string()` function. + +### Examples + +```cpp +auto eosio_user = eosio::name{user}; //encodes user string to eosio::name object +auto user_str = user_name_obj.to_string(); //decodes eosio::name obj to string +auto standard_account = "standardname"_n; //encodes literal string to eosio::name +auto non_standard_account = ".standard"_n; //encodes literal string to eosio::name +``` diff --git a/docs/05_best-practices/05_securing_your_contract.md b/docs/05_best-practices/05_securing_your_contract.md index 122e5c52d7..54a5db5ee5 100644 --- a/docs/05_best-practices/05_securing_your_contract.md +++ b/docs/05_best-practices/05_securing_your_contract.md @@ -2,16 +2,35 @@ content_title: Securing your contract --- -These are basic recommendations that should be the foundation of securing your smart contract: +## Basic Recommendations -1. The master git branch has the `has_auth`, `require_auth`, `require_auth2` and `require_recipient` methods available in the EOSIO library. They can be found in detail [here](https://eosio.github.io/eosio.cdt/1.6.0-rc1/group__action.html#function-requirerecipient) and implemented [here](https://github.com/EOSIO/eos/blob/3fddb727b8f3615917707281dfd3dd3cc5d3d66d/libraries/chain/apply_context.cpp#L144) (they end up calling the methods implemented in the `apply_context` class). +The following are basic recommendations which can be the foundation for securing your smart contract. -2. Understand how each of your contracts' actions is impacting the RAM, CPU, and NET consumption, and which account ends up paying for these resources. +### 1. Authorization Checks -3. Have a solid and comprehensive development process that includes security considerations from day one of the product planning and development. +The following methods are available in the `EOSIO` library and they can be used to implemented authorization checks in your smart contracts: -4. Test your smart contracts with every update announced for the blockchain you have deployed to. To ease your work, automate the testing as much as possible so you can run them often, and improve them periodically. +- [`has_auth`](../group__action/#function-has_auth) +- [`require_auth`](../group__action/#function-require_auth) +- [`require_auth2`](../how-to-guides/authorization/how_to_restrict_access_to_an_action_by_user/#3-using-require_auth2) +- [`require_recipient`](../group__action/#function-require_recipient) -5. Conduct independent smart contract audits, at least two from different organizations. +### 2. Resource Management -6. Host periodic bug bounties on your smart contracts and keep a continuous commitment to reward real security problems reported at any time. \ No newline at end of file +Understand how each of your contracts' actions is impacting the RAM, CPU, and NET consumption, and which account ends up paying for these resources. + +### 3. Secure by Default + +Have a solid and comprehensive development process that includes security considerations from day one of the product planning and development. + +### 4. Continuous Integration And Continuous Delivery + +Test your smart contracts with every update announced for the blockchain you have deployed to. To ease your work, automate the testing as much as possible so you can run them often, and improve them periodically. + +### 5. Security Audits + +Conduct independent smart contract audits, at least two from different organizations. + +### 6. Bug Bounties + +Host periodic bug bounties on your smart contracts and keep a continuous commitment to reward real security problems reported at any time. diff --git a/docs/05_best-practices/08_abi/00_understanding-abi-files.md b/docs/05_best-practices/08_abi/00_understanding-abi-files.md new file mode 100644 index 0000000000..245a52e181 --- /dev/null +++ b/docs/05_best-practices/08_abi/00_understanding-abi-files.md @@ -0,0 +1,531 @@ +--- +content_title: "Understanding ABI Files" +link_text: "Understanding ABI Files" +--- +## Introduction + +ABI files can be generated using the `eosio-cpp` utility provided by `eosio.cdt`. However, there are several situations that may cause ABI's generation to malfunction or fail altogether. Advanced C++ patterns can trip it up and custom types can sometimes cause issues for ABI generation. For this reason, it's **imperative** you understand how ABI files work, so you can debug and fix if and when necessary. + +## What is an ABI + +The Application Binary Interface (ABI) is a JSON-based description on how to convert user actions between their JSON and Binary representations. The ABI also describes how to convert the database state to/from JSON. Once you have described your contract via an ABI then developers and users will be able to interact with your contract seamlessly via JSON. + +[[warning | Security Note]] +| ABI can be bypassed when executing transactions. Messages and actions passed to a contract do not have to conform to the ABI. The ABI is a guide, not a gatekeeper. + +## Create an ABI File + +Start with an empty ABI, for exemplification we will work based on the `eosio.token` therefore name it `eosio.token.abi`: + +```text +{ + "version": "eosio::abi/1.0", + "types": [], + "structs": [], + "actions": [], + "tables": [], + "ricardian_clauses": [], + "abi_extensions": [], + "___comment" : "" +} +``` + +## Types + +An ABI enables any client or interface to interpret and even generate a GUI for your contract. For this to work consistently, describe the custom types that are used as a parameter in any public action or struct that needs to be described in the ABI. + +[[info | Built-in Types]] +| EOSIO implements a number of custom built-ins. Built-in types don't need to be described in an ABI file. If you would like to familiarize yourself with EOSIO's built-ins, they are defined [here](https://github.com/EOSIO/eos/blob/release/2.1.x/libraries/chain/abi_serializer.cpp#L85) + + +```json +{ + "new_type_name": "name", + "type": "name" +} +``` +The ABI now looks like this: + +```json +{ + "version": "eosio::abi/1.1", + "types": [{ + "new_type_name": "name", + "type": "name" + }], + "structs": [], + "actions": [], + "tables": [], + "ricardian_clauses": [], + "abi_extensions": [] +} +``` + +## Structs + +Structs that are exposed to the ABI also need to be described. By looking at eosio.token.hpp, it can be quickly determined which structs are utilized by public actions. This is particularly important for the next step. + +A struct's object definition in JSON looks like the following: + +```json +{ + "name": "issue", //The name + "base": "", //Inheritance, parent struct + "fields": [] //Array of field objects describing the struct's fields. +} +``` +## Fields + +```json +{ + "name":"", // The field's name + "type":"" // The field's type +} +``` +In the `eosio.token` contract, there's a number of structs that require definition. Please note, not all of the structs are explicitly defined, some correspond to an actions' parameters. Here's a list of structs that require an ABI description for the `eosio.token` contract: + +## Implicit Structs + +The following structs are implicit in that a struct was never explicitly defined in the contract. Looking at the [create](https://developers.eos.io/manuals/eosio.contracts/latest/action-reference/eosio.token/index/#create) action, you'll find two parameters, `issuer` of type `name ` and `maximum_supply` of type `asset`. For brevity this tutorial won't break down every struct, but applying the same logic, you will end up with the following: + +### [create](https://developers.eos.io/manuals/eosio.contracts/latest/action-reference/eosio.token/index/#create) + +```json +{ + "name": "create", + "base": "", + "fields": [ + { + "name":"issuer", + "type":"name" + }, + { + "name":"maximum_supply", + "type":"asset" + } + ] +} +``` +### [issue](https://developers.eos.io/manuals/eosio.contracts/latest/action-reference/eosio.token/index/#issue) + +```json +{ + "name": "issue", + "base": "", + "fields": [ + { + "name":"to", + "type":"name" + }, + { + "name":"quantity", + "type":"asset" + }, + { + "name":"memo", + "type":"string" + } + ] +} +``` +### [retire](https://developers.eos.io/manuals/eosio.contracts/latest/action-reference/eosio.token/index/#retire) + +```json +{ + "name": "retire", + "base": "", + "fields": [ + { + "name":"quantity", + "type":"asset" + }, + { + "name":"memo", + "type":"string" + } + ] +} +``` + +### [transfer](https://developers.eos.io/manuals/eosio.contracts/latest/action-reference/eosio.token/index/#transfer) + +```json +{ + "name": "transfer", + "base": "", + "fields": [ + { + "name":"from", + "type":"name" + }, + { + "name":"to", + "type":"name" + }, + { + "name":"quantity", + "type":"asset" + }, + { + "name":"memo", + "type":"string" + } + ] +} +``` + +### [close](https://developers.eos.io/manuals/eosio.contracts/latest/action-reference/eosio.token/index/#close) + +```json +{ + "name": "close", + "base": "", + "fields": [ + { + "name":"owner", + "type":"name" + }, + { + "name":"symbol", + "type":"symbol" + } + ] + } +``` + +## Explicit Structs +These structs are explicitly defined, as they are a requirement to instantiate a multi-index table. Describing them is no different than defining the implicit structs as demonstrated above. + +### [account](https://github.com/EOSIO/eosio.contracts/blob/master/contracts/eosio.token/include/eosio.token/eosio.token.hpp#L120) + +```json +{ + "name": "account", + "base": "", + "fields": [ + { + "name":"balance", + "type":"asset" + } + ] +} +``` + +## Actions + +An action's JSON object definition looks like the following: + +```json +{ + "name": "transfer", //The name of the action as defined in the contract + "type": "transfer", //The name of the implicit struct as described in the ABI + "ricardian_contract": "" //An optional ricardian clause to associate to this action describing its intended functionality. +} +``` +Describe the actions of the `eosio.token` contract by aggregating all the public functions described in the `eosio.token` contract's [header file](https://github.com/EOSIO/eosio.contracts/blob/master/contracts/eosio.token/include/eosio.token/eosio.token.hpp). + +Then describe each action's *type* according to its previously described struct. In most situations, the function name and the struct name will be equal, but are not required to be equal. + +Below is a list of actions that link to their source code with example JSON provided for how each action would be described. + +## [create](https://github.com/EOSIO/eosio.contracts/blob/master/contracts/eosio.token/include/eosio.token/eosio.token.hpp#L35-L37) + +```json +{ + "name": "create", + "type": "create", + "ricardian_contract": "" +} +``` + +## [issue](https://github.com/EOSIO/eosio.contracts/blob/master/contracts/eosio.token/include/eosio.token/eosio.token.hpp#L45-L46) + +```json +{ + "name": "issue", + "type": "issue", + "ricardian_contract": "" +} +``` + +## [retire](https://github.com/EOSIO/eosio.contracts/blob/master/contracts/eosio.token/include/eosio.token/eosio.token.hpp#L55-L56) + +```json +{ + "name": "retire", + "type": "retire", + "ricardian_contract": "" +} +``` + +## [transfer](https://github.com/EOSIO/eosio.contracts/blob/master/contracts/eosio.token/include/eosio.token/eosio.token.hpp#L67-L71) + +```json +{ + "name": "transfer", + "type": "transfer", + "ricardian_contract": "" +} +``` + +## [close](https://github.com/EOSIO/eosio.contracts/blob/master/contracts/eosio.token/include/eosio.token/eosio.token.hpp#L96-L97) + +```json +{ + "name": "close", + "type": "close", + "ricardian_contract": "" +} +``` + +## Tables + +Describe the tables. Here's a table's JSON object definition: + +```json +{ + "name": "", //The name of the table, determined during instantiation. + "type": "", //The table's corresponding struct + "index_type": "", //The type of primary index of this table + "key_names" : [], //An array of key names, length must equal length of key_types member + "key_types" : [] //An array of key types that correspond to key names array member, length of array must equal length of key names array. +} +``` + +The eosio.token contract instantiates two tables, [accounts](https://github.com/EOSIO/eosio.contracts/blob/master/contracts/eosio.token/include/eosio.token/eosio.token.hpp#L134) and [stat](https://github.com/EOSIO/eosio.contracts/blob/master/contracts/eosio.token/include/eosio.token/eosio.token.hpp#L135). + +The `accounts` table is an i64 index, based on the [`account` struct](https://github.com/EOSIO/eosio.contracts/blob/master/contracts/eosio.token/include/eosio.token/eosio.token.hpp#L120-L124), has a [`uint64` as it's primary key](https://github.com/EOSIO/eosio.contracts/blob/master/contracts/eosio.token/include/eosio.token/eosio.token.hpp#L123) + +Here's how the accounts table would be described in the ABI + +```json +{ + "name": "accounts", + "type": "account", // Corresponds to previously defined struct + "index_type": "i64", + "key_names" : ["primary_key"], + "key_types" : ["uint64"] +} +``` + +The `stat` table is an i64 index, based on the [`currency_stats` struct](https://github.com/EOSIO/eosio.contracts/blob/master/contracts/eosio.token/include/eosio.token/eosio.token.hpp#L126-L132), has a [`uint64` as it's primary key](https://github.com/EOSIO/eosio.contracts/blob/master/contracts/eosio.token/include/eosio.token/eosio.token.hpp#L131) + +Here's how the stat table would be described in the ABI + +```json +{ + "name": "stat", + "type": "currency_stats", + "index_type": "i64", + "key_names" : ["primary_key"], + "key_types" : ["uint64"] +} +``` +You'll notice the above tables have the same "key name." Naming your keys similar names is symbolic in that it can potentially suggest a subjective relationship. As with this implementation, implying that any given value can be used to query different tables. + +## Putting it all Together + +Finally, an ABI file that accurately describes the `eosio.token` contract. + +```json +{ + "version": "eosio::abi/1.1", + "types": [ + { + "new_type_name": "name", + "type": "name" + } + ], + "structs": [ + { + "name": "create", + "base": "", + "fields": [ + { + "name":"issuer", + "type":"name" + }, + { + "name":"maximum_supply", + "type":"asset" + } + ] + }, + { + "name": "issue", + "base": "", + "fields": [ + { + "name":"to", + "type":"name" + }, + { + "name":"quantity", + "type":"asset" + }, + { + "name":"memo", + "type":"string" + } + ] + }, + { + "name": "retire", + "base": "", + "fields": [ + { + "name":"quantity", + "type":"asset" + }, + { + "name":"memo", + "type":"string" + } + ] + }, + { + "name": "close", + "base": "", + "fields": [ + { + "name":"owner", + "type":"name" + }, + { + "name":"symbol", + "type":"symbol" + } + ] + }, + { + "name": "transfer", + "base": "", + "fields": [ + { + "name":"from", + "type":"name" + }, + { + "name":"to", + "type":"name" + }, + { + "name":"quantity", + "type":"asset" + }, + { + "name":"memo", + "type":"string" + } + ] + }, + { + "name": "account", + "base": "", + "fields": [ + { + "name":"balance", + "type":"asset" + } + ] + }, + { + "name": "currency_stats", + "base": "", + "fields": [ + { + "name":"supply", + "type":"asset" + }, + { + "name":"max_supply", + "type":"asset" + }, + { + "name":"issuer", + "type":"name" + } + ] + } + ], + "actions": [ + { + "name": "transfer", + "type": "transfer", + "ricardian_contract": "" + }, + { + "name": "issue", + "type": "issue", + "ricardian_contract": "" + }, + { + "name": "retire", + "type": "retire", + "ricardian_contract": "" + }, + { + "name": "create", + "type": "create", + "ricardian_contract": "" + }, + { + "name": "close", + "type": "close", + "ricardian_contract": "" + } + ], + "tables": [ + { + "name": "accounts", + "type": "account", + "index_type": "i64", + "key_names" : ["currency"], + "key_types" : ["uint64"] + }, + { + "name": "stat", + "type": "currency_stats", + "index_type": "i64", + "key_names" : ["currency"], + "key_types" : ["uint64"] + } + ], + "ricardian_clauses": [], + "abi_extensions": [] +} +``` + +## Cases not Covered by Token Contract + +## Vectors + +When describing a vector in your ABI file, simply append the type with `[]`, so if you need to describe a vector of permission levels, you would describe it like so: `permission_level[]` + +## Struct Base + +It's a rarely used property worth mentioning. You can use **base** ABI struct property to reference another struct for inheritance, as long as that struct is also described in the same ABI file. Base will do nothing or potentially throw an error if your smart contract logic does not support inheritance. + +You can see an example of base in use in the system contract [source code](https://github.com/EOSIO/eosio.contracts/blob/4e4a3ca86d5d3482dfac85182e69f33c49e62fa9/eosio.system/include/eosio.system/eosio.system.hpp#L46) and [ABI](https://github.com/EOSIO/eosio.contracts/blob/4e4a3ca86d5d3482dfac85182e69f33c49e62fa9/eosio.system/abi/eosio.system.abi#L262) + +## Extra ABI Properties Not Covered Here + +A few properties of the ABI specification were skipped here for brevity, however, there is a pending ABI specification that will outline every property of the ABI in its entirety. + +## Ricardian Clauses + +Ricardian clauses describe the intended outcome of a particular actions. It may also be utilized to establish terms between the sender and the contract. + +## ABI Extensions + +A generic "future proofing" layer that allows old clients to skip the parsing of "chunks" of extension data. For now, this property is unused. In the future each extension would have its own "chunk" in that vector so that older clients skip it and newer clients that understand how to interpret it. + +## Maintenance + +Every time you change a struct, add a table, add an action or add parameters to an action, use a new type, you will need to remember to update your ABI file. In many cases failure to update your ABI file will not produce any error. + +## Troubleshooting + +### Table returns no rows + +Check that your table is accurately described in the ABI file. For example, If you use `cleos` to add a table on a contract with a malformed ABI definition and then get rows from that table, you will receive an empty result. `cleos` will not produce an error when adding a row nor reading a row when a contract has failed to properly describe its tables in its ABI. diff --git a/docs/05_best-practices/08_abi/01_abi-code-generator-attributes-explained.md b/docs/05_best-practices/08_abi/01_abi-code-generator-attributes-explained.md index cf2864d8dc..f4931df137 100644 --- a/docs/05_best-practices/08_abi/01_abi-code-generator-attributes-explained.md +++ b/docs/05_best-practices/08_abi/01_abi-code-generator-attributes-explained.md @@ -1,11 +1,12 @@ --- -content_title: ABI/Code generator attributes explained +content_title: ABI/Code generator attributes +link_text: ABI/Code generator attributes --- The new ABI generator tool uses C++11 or GNU style attributes to mark `actions` and `tables`. ## [[eosio::action]] -This attribute marks either a struct or a method as an action. +This attribute marks a method as an action. Example (four ways to declare an action for ABI generation): ```cpp // this is the C++11 and greater style attribute @@ -31,7 +32,7 @@ struct __attribute__((eosio_action)) testa { }; ``` -If your action name is not a valid [EOSIO name](https://developers.eos.io/eosio-cpp/docs/naming-conventions) you can explicitly specify the name in the attribute ```c++ [[eosio::action("")]]``` +If your action name is not a valid [EOSIO name](../02_naming-conventions.md) you can explicitly specify the name in the attribute ```c++ [[eosio::action("")]]``` ## [[eosio::table]] Example (two ways to declare a table for ABI generation): diff --git a/docs/05_best-practices/08_abi/02_manually_write_an_ABI_file_explained.md b/docs/05_best-practices/08_abi/02_manually_write_an_ABI_file_explained.md index c97a45726a..4b249ac9a6 100644 --- a/docs/05_best-practices/08_abi/02_manually_write_an_ABI_file_explained.md +++ b/docs/05_best-practices/08_abi/02_manually_write_an_ABI_file_explained.md @@ -1,9 +1,10 @@ --- -content_title: Manually write, or fix, an ABI file +content_title: Manually write/edit an ABI file +link_text: Manually write/edit an ABI file --- - Advanced features of the newest version of the ABI will require manual construction of the ABI, and odd and advanced C++ patterns could capsize the generator's type deductions. So having a good knowledge of how to write an ABI should be an essential piece of knowledge of a smart contract writer. -- Please refer to [developers.eos.io "How to Write an ABI File"](https://developers.eos.io/eosio-cpp/docs/how-to-write-an-abi) to learn about the different sections of an ABI. +- Please refer to [Create an ABI File](00_understanding-abi-files.md#create-an-abi-file) to learn about the different sections of an ABI. ## Adding Ricardian Contracts and Clauses to ABI - The ABI generator will try to automatically import contracts and clauses into the generated ABI. There are a few caveats to this, one is a strict naming policy of the files and an HTML tag used to mark each Ricardian contract and each clause. diff --git a/docs/05_best-practices/08_abi/index.md b/docs/05_best-practices/08_abi/index.md new file mode 100644 index 0000000000..1b8648053c --- /dev/null +++ b/docs/05_best-practices/08_abi/index.md @@ -0,0 +1,8 @@ +--- +content_title: ABI +link_text: ABI +--- + +- [Understanding ABI Files](00_understanding-abi-files.md) +- [ABI/Code generator attributes](01_abi-code-generator-attributes-explained.md) +- [Manually write/edit an ABI file](02_manually_write_an_ABI_file_explained.md) diff --git a/docs/05_best-practices/09_deferred_transactions.md b/docs/05_best-practices/09_deferred_transactions.md index 63ca63c2aa..82036a6734 100644 --- a/docs/05_best-practices/09_deferred_transactions.md +++ b/docs/05_best-practices/09_deferred_transactions.md @@ -6,5 +6,10 @@ Deferred communication conceptually takes the form of action notifications sent As already mentioned, deferred communication will get scheduled later at the producer's discretion. From the perspective of the originating transaction, i.e., the transaction that creates the deferred transaction, it can only determine whether the create request was submitted successfully or whether it failed (if it fails, it will fail immediately). Deferred transactions carry the authority of the contract that sends them. A transaction can cancel a deferred transaction. -[[warning | Warning about deferred transaction usage]] -| Because of the above, it is not recommended to use `deferred transactions`. There is consideration to deprecate deferred transactions in a future version. +[[warning | Deferred Transactions Are Deprecated]] +| As of [EOSIO 2.0 RC1](https://github.com/EOSIO/eos/releases/tag/v2.0.0-rc1) deferred transactions are deprecated. + +Due to the above described behaviors it is not recommended to use `deferred transactions`. + +[[warning | Duplicate deferred transaction IDs]] +| In earlier versions, prior to `v1.8.0`, it is possible to observe rare deferred transaction ID collisions because technically the protocol's validation rules allow any number of duplicate deferred transaction IDs. However, the block producing code prior to `v1.8.0` limited this to exactly one duplicate ID in the same block followed by at-most one duplicate ID in the following block. This behavior was mitigated starting with version `v1.8.0` making it functionally impossible to be achieved at the block producing code level. On top of that, the same version `v1.8.0` introduced the optional `NO_DUPLICATE_DEFERRED_ID` protocol feature which, if deployed, it makes this behavior impossible at the protocol layer as well. diff --git a/docs/05_best-practices/12_return_values_from_actions.md b/docs/05_best-practices/12_return_values_from_actions.md new file mode 100644 index 0000000000..37399d8f3e --- /dev/null +++ b/docs/05_best-practices/12_return_values_from_actions.md @@ -0,0 +1,31 @@ +--- +content_title: Return Values From Actions +--- + +## Overview + +`EOSIO` version 2.1 enables you to return any value from any action. This new feature facilitates easier smart contract implementation debugging, and better messaging between a smart contract and its clients. From now on the clients of a smart contract are able to use the value returned by an action, no more string parsing on the client side and no more print statements in the smart contract implementation. + +## Concept + +When you implement an action within a smart contract use the `return` statement and pass any value to the action sender. The returned value can be of any C++ primitive type, any C++ standard library type, or any user defined type. The `EOSIO` framework does all the work necessary to serialize the value returned and send it back to the client. On the client side you deserialize the received value and access it the same way you access any other function’s return value. + +## Details + +The following list provides important details for when you return a value from an action: + +* As mentioned above, the `EOSIO` framework does all the heavy lifting for the return value to convey it to the client. The `EOSIO` framework defines and uses a new intrinsic, namely `set_action_return_value`. To learn more about `EOSIO` returned values functionality, refer to its documentation and [implementation](https://github.com/EOSIO/eosio.cdt/blob/develop/libraries/native/intrinsics.cpp#L295). +* The CPU time and memory limit of your contract (maximum size of wasm), not RAM or NET, defines the limits of the returned value.. +* The action receipt includes a hash of the serialized returned value. +* The action trace includes the serialized returned value. +* If the trace log is enabled, the state history trace logs also store the serialized returned value. +* If the trace api plugin is enabled, the trace api trace logs also store the serialized returned value. +* The returned value is available in the action trace. If the sender is another action, the action trace is not available to the sender action `code`. Therefore the returned value from an inline action can not be read by the action that sent the inline action. +* Also inline actions do not execute synchronously. Inline actions execute at a later time. No return value is available to the sender at the time the inline action is sent. + +## Examples + +For a full working example of a smart contract action which returns a value, refer to the following resources: + +* The [`hello`](https://github.com/EOSIO/eosio.cdt/blob/develop/examples/hello/src/hello.cpp#L14) smart contract example, see action `hello::checkwithrv`. +* The [How-to return values from actions](../06_how-to-guides/60_how-to-return-values-from-actions.md). diff --git a/docs/05_best-practices/12_binary-extension.md b/docs/05_best-practices/13_binary-extension.md similarity index 100% rename from docs/05_best-practices/12_binary-extension.md rename to docs/05_best-practices/13_binary-extension.md diff --git a/docs/05_best-practices/naming-conventions-format.png b/docs/05_best-practices/naming-conventions-format.png new file mode 100644 index 0000000000..3db1f378f2 Binary files /dev/null and b/docs/05_best-practices/naming-conventions-format.png differ diff --git a/docs/06_how-to-guides/01_compile/01_compile-a-contract-via-cli.md b/docs/06_how-to-guides/01_compile/01_compile-a-contract-via-cli.md deleted file mode 100644 index 060efe3869..0000000000 --- a/docs/06_how-to-guides/01_compile/01_compile-a-contract-via-cli.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -content_title: How to compile a contract via CLI ---- - -## Preconditions -- You have the source of your contract saved in one of your local folders, e.g. `./examples/hello` -For details on how to create your first contract follow [this tutorial here](https://developers.eos.io/eosio-home/docs/your-first-contract) - -Follow these steps to compile your contract: - -1. Navigate to the hello folder in examples (./examples/hello), you should then see the ./src/hello.cpp file -2. Now run following commands: -```sh -$ mkdir build -$ cd build -$ eosio-cpp -abigen ../src/hello.cpp -o hello.wasm -I ../include/ -``` -3. This will generate two files: -- The compiled binary wasm, hello.wasm -- The generated ABI file, hello.abi diff --git a/docs/06_how-to-guides/01_compile/02_how-to-configure-cmake.md b/docs/06_how-to-guides/01_compile/02_how-to-configure-cmake.md deleted file mode 100644 index 22549b473c..0000000000 --- a/docs/06_how-to-guides/01_compile/02_how-to-configure-cmake.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -content_title: How to configure CMake ---- - -## CMake Configuration - -### Automatic generation of CMake configuration - -To compile an EOSIO smart contract with CMake, you'll need a CMake file. To use the new `eosio-init` tool to generate the directory structure stub .hpp/.cpp files and the cmake configuration files follow these steps: - -1. cd ~ -2. eosio-init --path=. --project=test_contract -3. cd test_contract -4. cd build -5. cmake .. -6. make -7. ls -al test_contract - -At this point, you'll have the `test_contract.abi` and `test_contract.wasm` files in `~/test_contract/test_contract`. These files are ready to be deployed. - -### Manual generation of CMake configuration - -To create manually the cmake configuration, the template `CMakeLists.txt` in the examples folder is a good boilerplate for manual usage. - -1. In `CMakeLists.txt`: -``` -cmake_minimum_required(VERSION 3.5) -project(test_example VERSION 1.0.0) - -find_package(eosio.cdt) - -add_contract( test test test.cpp ) -``` - -2. In `test.cpp`: -``` -#include -using namespace eosio; - -class [[eosio::contract]] test : public eosio::contract { -public: - using contract::contract; - - [[eosio::action]] void testact( name test ) { - } -}; - -EOSIO_DISPATCH( test, (testact) ) -``` - -3. The following CMake macros are provided: -- `add_contract` is used to build your smart contract and generate an ABI. The first parameter is the contract name, the second is the cmake target name, and the rest are the CPP files needed to build the contract. -- `target_ricardian_directory` can be used to add the directory where your ricardian contracts live to a specific cmake target. -- `add_native_library` and `add_native_executable` are CMake macros for the native tester. They are drop in replacements for `add_library` and `add_executable`. diff --git a/docs/06_how-to-guides/01_compile/03_compiling-contracts-with-cmake.md b/docs/06_how-to-guides/01_compile/03_compiling-contracts-with-cmake.md deleted file mode 100644 index 54bd0814d6..0000000000 --- a/docs/06_how-to-guides/01_compile/03_compiling-contracts-with-cmake.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -content_title: How to compile a smart contract with CMake ---- - -## Preconditions -- You have the source of your contract saved in one of your local folders, e.g. `./examples/hello` -For details on how to create your first contract follow [this tutorial here](https://developers.eos.io/eosio-home/docs/your-first-contract) - -Follow these steps to compile your contract: - -1. Navigate to the hello folder in examples (./examples/hello), you should then see the ./src/hello.cpp file -2. Run following commands: -```sh -$ mkdir build -$ cd build -$ cmake .. -$ make -``` -3. This will generate two files: -- The compiled binary wasm, hello.wasm -- The generated ABI file, hello.abi diff --git a/docs/06_how-to-guides/04_how_to_create_and_use_action_wrappers.md b/docs/06_how-to-guides/04_how_to_create_and_use_action_wrappers.md deleted file mode 100644 index f717d9a960..0000000000 --- a/docs/06_how-to-guides/04_how_to_create_and_use_action_wrappers.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -content_title: How to create and use action wrappers ---- - -1. Start with a contract `multi_index_example` which has an action `mod` defined like below in file `multi_index_example.hpp`; the action modifies the integer value `n` stored for row with key `user`. -```cpp -class [[eosio::contract]] multi_index_example : public contract { - // ... - [[eosio::action]] void mod( name user, uint32_t n ); - // ... -} -``` -2. To define an action wrapper for the `mod` action, make use of the `eosio::action_wrapper` template, with the first parameter the action name as a `eosio::name` and second parameter as the reference to the action method -```diff -class [[eosio::contract]] multi_index_example : public contract { - // ... - [[eosio::action]] void mod(name user); - // ... -+ using mod_action = action_wrapper<"mod"_n, &multi_index_example::mod>; - // ... -} -``` -3. To use the action wrapper, you have to include the header file where the action wrapper is defined -```cpp -#include -``` -4. Then instantiate the `mod_action` defined above, specifying the contract to send the action to as the first argument. In this case, it is assumed the contract is deployed to `multiindexex` account, and a structure which is defined by two parameters: the self account, obtained by `get_self()` call, and the `active` permission (you can modify these two parameters based on your requirements). -```diff -#include - -+multi_index_example::mod_action modaction("multiindexex"_n, {get_self(), "active"_n}); -``` -5. And finally call the `send` method of the action wrapper and pass in the `mod` action's parameters as positional arguments -```diff -#include - -multi_index_example::mod_action modaction("multiindexex"_n, {get_self(), 1}); - -+modaction.send("eostutorial"_n, 1); -``` - -For a full example see the [`multi_index` contract implementation](https://github.com/EOSIO/eosio.cdt/tree/master/examples/multi_index_example). diff --git a/docs/06_how-to-guides/05_authorization/how_to_restrict_access_to_an_action_by_user.md b/docs/06_how-to-guides/05_authorization/how_to_restrict_access_to_an_action_by_user.md deleted file mode 100644 index 4ec1229724..0000000000 --- a/docs/06_how-to-guides/05_authorization/how_to_restrict_access_to_an_action_by_user.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -content_title: How to restrict access to an action by a user ---- - -## Preconditions -- It is assumed you have the sources for a contract and one of the actions defined is getting as a parameter an account name and it is printing the account name. - -To restrict access to the `hi` action, you can do it in two ways: - -1. Using require_auth -The below code is enforcing the action `hi` to be executed only by the account that is sent as parameter to the action, no matter what permission the account is using to sign the transaction (e.g. owner, active, code). - -```cpp -void hi( name user ) { - require_auth( user ); - print( "Hello, ", name{user} ); -} -``` - -2. Or using require_auth2 - -The below code is enforcing the action `hi` to be executed only by the account that is sent as parameter to the action and only if the permission used to sign the transaction is the 'active' one. In other words, if the same user is signing the transaction with a different permission (e.g. code, owner) the execution of the action is halted. - -```cpp -#include - -void hi( name user ) { - require_auth2(nm.value, "active"_n.value); - print( "Hello, ", name{user} ); -} -``` - -An example of this contract can be found [here](https://github.com/EOSIO/eosio.cdt/blob/master/examples/hello/src/hello.cpp) diff --git a/docs/06_how-to-guides/10_compile/01_compile-a-contract-via-cli.md b/docs/06_how-to-guides/10_compile/01_compile-a-contract-via-cli.md new file mode 100644 index 0000000000..115ab29613 --- /dev/null +++ b/docs/06_how-to-guides/10_compile/01_compile-a-contract-via-cli.md @@ -0,0 +1,48 @@ +--- +content_title: How to compile a smart contract via CLI +--- + +## Overview + +This guide provides instructions how to compile a smart contract using the command line interface (CLI). + +## Reference + +See the following code reference: + +* The [`eosio-cpp`](https://developers.eos.io/manuals/eosio.cdt/latest/command-reference/eosio-cpp) tool. + +## Before you begin + +* You have the source of the contract saved in a local folder, e.g. `./examples/hello/` +For details on how to create your first contract follow the [Hello World Contract](https://developers.eos.io/welcome/latest/smart-contract-guides/hello-world) guide. + +## Procedure + +Follow the following steps to compile your contract. + +1. Navigate to the hello folder in examples `./examples/hello`. You should see the `./src/hello.cpp` file. + +2. Run the following commands: + + ```sh + mkdir build + cd build + eosio-cpp -abigen ../src/hello.cpp -o hello.wasm -I ../include/ + ``` + + Where: + - `eosio-cpp` = Is the [`eosio-cpp`](https://developers.eos.io/manuals/eosio.cdt/latest/command-reference/eosio-cpp) tool. + - `-abigen` = It instructs the `eosio-cpp` tool to generate ABI file. + - `../src/hello.cpp` = Is the input cpp source file to be compiled. + - `-o hello.wasm` = It instructs the `eosio-cpp` tool who to name the output wasm file. + - `-I ../include/` = It tells `eosio-cpp` tool what the include folder path is, in this particular case it is relative path. + +3. Verify the following two files were generated: + +* the compiled binary wasm: `hello.wasm`, +* and the generated ABI file: `hello.abi`. + +## Summary + +In conclusion, the above instructions show how to compile a smart contract using the command line interface (CLI). diff --git a/docs/06_how-to-guides/10_compile/02_how-to-configure-cmake.md b/docs/06_how-to-guides/10_compile/02_how-to-configure-cmake.md new file mode 100644 index 0000000000..db9511dc1b --- /dev/null +++ b/docs/06_how-to-guides/10_compile/02_how-to-configure-cmake.md @@ -0,0 +1,78 @@ +--- +content_title: How to configure CMake +--- + +## Overview + +This guide provides instructions how to configure CMake. + +## Before you begin + +* You have installed CMake, for detailed instructions consult the official [CMake installation page](https://CMake.org/install/). + +## Procedure + +The following steps show: + +* [Automatic generation of CMake configuration](#automatic-generation-of-CMake-configuration) +* [Manual generation of CMake configuration](#manual-generation-of-CMake-configuration) + +### Automatic generation of CMake configuration + +To compile an EOSIO smart contract with CMake, you'll need a CMake file. To use the new `eosio-init` tool to generate the directory structure stub `.hpp/.cpp` files and the CMake configuration files follow these steps: + + ```sh + cd ~ + eosio-init --path=. --project=test_contract + cd test_contract + cd build + cmake .. + make + ls -al test_contract + ``` + +At this point, you'll have the `test_contract.abi` and `test_contract.wasm` files in `~/test_contract/test_contract`. These files are ready to be deployed. + +### Manual generation of CMake configuration + +To create manually the CMake configuration, the template `CMakeLists.txt` in the examples folder is a good boilerplate for manual usage. + +1. In `CMakeLists.txt`: + + ```sh + cmake_minimum_required(VERSION 3.5) + project(test_example VERSION 1.0.0) + + find_package(eosio.cdt) + + add_contract( test test test.cpp ) + ``` + +2. In `test.cpp`: + + ```c++ + #include + using namespace eosio; + + class [[eosio::contract]] test : public eosio::contract { + public: + using contract::contract; + + [[eosio::action]] void testact( name test ) { + } + }; + + EOSIO_DISPATCH( test, (testact) ) + ``` + +3. The following CMake macros are provided: + +* `add_contract` is used to build your smart contract and generate an ABI. The first parameter is the contract name, the second is the CMake target name, and the rest are the CPP files needed to build the contract. + +* `target_ricardian_directory` can be used to add the directory where your ricardian contracts live to a specific CMake target. + +* `add_native_library` and `add_native_executable` are CMake macros for the native tester. They are drop in replacements for `add_library` and `add_executable`. + +## Summary + +In conclusion, the above instructions show how to configure CMake . diff --git a/docs/06_how-to-guides/10_compile/03_compiling-contracts-with-cmake.md b/docs/06_how-to-guides/10_compile/03_compiling-contracts-with-cmake.md new file mode 100644 index 0000000000..612c0e3425 --- /dev/null +++ b/docs/06_how-to-guides/10_compile/03_compiling-contracts-with-cmake.md @@ -0,0 +1,35 @@ +--- +content_title: How to compile a smart contract with CMake +--- + +## Overview + +This guide provides instructions on how to compile a smart contract with CMake. + +## Before you begin + +* You have the source of the contract saved in a local folder, e.g. `./examples/hello/` +For details on how to create your first contract follow the [Hello World Contract](https://developers.eos.io/welcome/latest/smart-contract-guides/hello-world) guide. + +## Procedure + +Follow the following steps to compile your contract. + +1. Navigate to the hello folder in examples (./examples/hello), you should then see the ./src/hello.cpp file +2. Run following commands: + + ```sh + mkdir build + cd build + cmake .. + make + ``` + +3. Verify the following two files were generated: + +* the compiled binary wasm: `hello.wasm`, +* and the generated ABI file: `hello.abi`. + +## Summary + +In conclusion, the above instructions show how to compile a smart contract with CMake. diff --git a/docs/06_how-to-guides/20_authorization/how_to_restrict_access_to_an_action_by_user.md b/docs/06_how-to-guides/20_authorization/how_to_restrict_access_to_an_action_by_user.md new file mode 100644 index 0000000000..dc509c2aaa --- /dev/null +++ b/docs/06_how-to-guides/20_authorization/how_to_restrict_access_to_an_action_by_user.md @@ -0,0 +1,82 @@ +--- +content_title: How To Perform Authorization Checks +link_text: How To Perform Authorization Checks +--- + +## Overview + +This guide provides instructions how to perform authorization checks in a smart contract. + +## Before you begin + +1. You have the sources of a contract with a `hi` action defined and implemented. +2. The `hi` action has defined one input parameter `user` of type `name`. + +## Code Reference + +See the following code reference guides for functions which can be used to implement authorization checks in a smart contract: + +* function [has_auth(name n)](https://developers.eos.io/manuals/eosio.cdt/v1.8/namespaceeosio#function-has_auth) +* function [require_auth(name n)](https://developers.eos.io/manuals/eosio.cdt/v1.8/namespaceeosio/#function-require_auth-12) +* function [require_auth2(capi_name name, capi_name permission)](https://developers.eos.io/manuals/eosio.cdt/v1.8/group__action__c#function-require_auth2) +* function [check(bool pred, ...)](https://developers.eos.io/manuals/eosio.cdt/v1.8/group__system/#function-check) + +## Procedure + +The following steps show how to check authorization for `user` account for the `hi` action. There are three ways to accomplish an authorization check in a smart contract action implementation. You can use any of the methods provided below depending on your needs: + +* [Use check(...) in combination with has_auth(...)](#1-use-checkhas_auth) +* [Use require_auth(...)](#2-use-require_auth) +* [Use require_auth2(...)](#3-use-require_auth2) + +### 1. Use check(has_auth(...)...) + +The following code example enforces the action `hi` to be executed only by the account that is sent as parameter to the action, no matter what permission the account uses to sign the transaction (e.g. owner, active, code). + +[[info | Error message is custom]] +| Observe that in this case the yielded error message is a custom one and thus it can be used to provide a better experience for the user. + +```cpp +#include + +void hi( name user ) { + check(has_auth(user), "User is not authorized to perform this action."); + print( "Hello, ", name{user} ); +} +``` + +Another example can be found in the [Tic Tac Toe Tutorial](https://developers.eos.io/welcome/latest/tutorials/tic-tac-toe-game-contract/#action-handler---move). + +### 2. Use require_auth + +The below code enforces the action `hi` to be executed only by the account that is sent as parameter to the action, no matter what permission the account uses to sign the transaction (e.g. owner, active, code). + +```cpp +void hi( name user ) { + require_auth( user ); + print( "Hello, ", name{user} ); +} +``` + +[[info | Error message is not custom]] +| Note that this time you can not customize the yielded error message, it will be a generic authorization error message. + +### 3. Use require_auth2 + +The below code is enforces the action `hi` to be executed only by the account that is sent as parameter to the action and only if the permission used to sign the transaction is the 'active' one. In other words, if the same user uses the transaction with a different permission (e.g. code, owner) the execution of the action is halted. + +```cpp +#include + +void hi( name user ) { + require_auth2(user.value, "active"_n.value); + print( "Hello, ", name{user} ); +} +``` + +[[info | Error message is not custom]] +| Note that this time, as well as previous method, you can not customize the yielded error message, it will be a generic authorization error message. + +## Summary + +In conclusion, the above instructions show how to perform authorization checks in a smart contract. diff --git a/docs/06_how-to-guides/30_key-value-api/index.md b/docs/06_how-to-guides/30_key-value-api/index.md new file mode 100644 index 0000000000..ad16996214 --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/index.md @@ -0,0 +1,27 @@ +--- +content_title: Key-Value API +link_text: "Key-Value API" +--- + +## Overview + +The Key-Value API provides a set of C++ classes and structures which facilitates the creation of datastore key value tables and maps on-chain. It is meant to provide the same functionality the multi-index provides in a `simpler` and more `flexible` API with comparable performance. + +[[caution | Alpha version]] +| `Key-Value Table` is designated as `alpha` and should not be used in production code. + +## Concept + +The Key-Value API, or KV API, is a new option for developers to create datastore key value tables on-chain. KV API is more flexible than multi-index and allows developers to search the table in a more human-readable way, unlike multi-index tables where search is over 64-bit values. + +On top of flexibility, this new API has a simpler interface and it helps the developer to avoid complex C++ templates constructs. + +### Related Terminology and Concepts + +The KV API provides two types of storage: + +* [Key-Value Map](./kv_map/index.md), or shorter `KV Map` +* [Key-Value Table](./kv_table/index.md), or shorter `KV Table` + +[[caution | Alpha version]] +| `Key-Value Table` is designated as `alpha` and should not be used in production code. diff --git a/docs/06_how-to-guides/30_key-value-api/kv_map/10_how-to-use-kv-map.md b/docs/06_how-to-guides/30_key-value-api/kv_map/10_how-to-use-kv-map.md new file mode 100644 index 0000000000..8a88547d92 --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_map/10_how-to-use-kv-map.md @@ -0,0 +1,88 @@ +--- +content_title: How-To Use Key-Value Map +link_text: "How-To Use Key-Value Map" +--- + +## Overview + +This how-to demonstrates how to define and use a `Key-Value Map` (`kv map`) in a smart contract. + +To accomplish this task do the following: + +1. Instantiate an object of type `eosio::kv::map`. +2. Specify the name for the instantiated `eosio::kv::map` object. +3. Specify the type for the map's key. +4. Specify the type for the values stored for each key. +5. The key and the values types can be of any standard type or a user defined type. + +## Reference + +See the following code reference: + +* The [`kv::map`](https://developers.eos.io/manuals/eosio.cdt/v1.8/classeosio_1_1kv_1_1map) class. + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index) +* A smart contract named `smrtcontract` +* A user defined type named `person`, which defines the data stored in the map + +Refer to the following reference implementation for your starting point: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; +}; + +class [[eosio::contract]] smartcontract : public eosio::contract { + public: + using contract::contract; + smartcontract(eosio::name receiver, eosio::name code, eosio::datastream ds) + : contract(receiver, code, ds) {} +}; +``` + +## Procedure + +Complete the following steps to define the `my_map_t` type, based on the `eosio::kv::map`, which stores objects of type `person` with unique keys of type `int` and instantiate a map object of type `my_map_t`: + +* Define the `my_map_t` type based on `eosio::kv::map`. +* Specify `"kvmap"_n`, which is an `eosio::name`, as the first parameter, to name the map object. +* Specify `int` as the second parameter to give the type of the unique keys. +* Specify `person` as the third parameter to give the type of the values stored in the map with each key. +* Declare and instantiate, as a private data member, an instance of the type `my_map_t`, and name it `my_map`. + +Refer below for a possible implementation of the above described steps: + +`smartcontract.hpp file` + +```cpp +class [[eosio::contract]] smartcontract : public eosio::contract { + + using my_map_t = eosio::kv::map<"kvmap"_n, int, person>; + + public: + using contract::contract; + smartcontract(eosio::name receiver, eosio::name code, eosio::datastream ds) + : contract(receiver, code, ds) {} + + private: + my_map_t my_map{}; +}; +``` + +## Summary + +In conclusion, the above instructions show how to define and use a `Key-Value Map` (`kv map`) in a smart contract. + +## Next Steps + +The following option is available when you complete the procedure: + +* You [can add values](30_how-to-upsert-into-kv-map.md) in the map object created. diff --git a/docs/06_how-to-guides/30_key-value-api/kv_map/30_how-to-upsert-into-kv-map.md b/docs/06_how-to-guides/30_key-value-api/kv_map/30_how-to-upsert-into-kv-map.md new file mode 100644 index 0000000000..87447effd0 --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_map/30_how-to-upsert-into-kv-map.md @@ -0,0 +1,117 @@ +--- +content_title: How-To Upsert Into Key-Value Map +link_text: "How-To Upsert Into Key-Value Map" +--- + +## Overview + +This how-to provides instructions to upsert into `Key-Value Map` (`kv map`). Upsert means insert when the item doesn't already exist, and update the item if it already exists in the map. + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index) +* A smart contract named `smrtcontract` +* A user defined type named `person`, which defines the data stored in the map +* A `kv map` object, name `my_map`, which stores objects of type `person`, with unique keys of type `int` + +Refer to the following reference implementation for your starting point: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; +}; + +class [[eosio::contract]] smartcontract : public eosio::contract { + + using my_map_t = eosio::kv::map<"kvmap"_n, int, person>; + + public: + using contract::contract; + smartcontract(eosio::name receiver, eosio::name code, eosio::datastream ds) + : contract(receiver, code, ds) {} + + private: + my_map_t my_map{}; +}; +``` + +## Procedure + +Complete the following steps to insert a new `person` object with a given ID, if it doesn't exist already, or update it in the `kv map` if the `person` with the given ID already exists: + +1. Create a new action in your contract, named `upsert`, which takes as input parameters the person `id`, an `account_name`, a `first_name` and a `last_name`. +2. Create an instance of the `person` class, named `person_upsert`, based on the input parameters: `account_name`, `first_name` and `last_name`. +3. Use the `[]` operator defined for the `kv::map` type, and set the newly created `person_upsert` object as the value for the `id` key. + +Refer to the following reference implementation to insert a new `person` object, and then update it, in the `kv map`: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; +}; + +class [[eosio::contract]] smartcontract : public eosio::contract { + + using my_map_t = eosio::kv::map<"kvmap"_n, int, person>; + + public: + using contract::contract; + smartcontract(eosio::name receiver, eosio::name code, eosio::datastream ds) + : contract(receiver, code, ds) {} + + // inserts a person if not exists, or updates it if already exists. + // the payer for the resources consumed is the account that created the kv::map + // object in the first place, the account that owns this smart contract. + [[eosio::action]] + void upsert(int id, + eosio::name account_name, + std::string first_name, + std::string last_name); + + private: + my_map_t my_map{}; +}; +``` + +`smartcontract.cpp file` + +```cpp +// inserts if not exists, or updates if already exists, a person +[[eosio::action]] +void smartcontract::upsert( + int id, + eosio::name account_name, + std::string first_name, + std::string last_name) { + + // create the person object which will be stored in kv::map + const person& person_upsert = person{ + account_name = account_name, + first_name = first_name, + last_name = last_name}; + + // upsert into kv::map + my_map[id] = person_upsert; +} +``` + +## Summary + +In conclusion, the above instructions show how to upsert into `Key-Value Map` (`kv map`). + +## Next Steps + +The following options are available when you complete the procedure: + +* [Verify](70_how-to-find-in-kv-map.md) if the newly inserted `person` actually exists in the map. To accomplish this task use the `find()` function of the `kv_map`. +* [Delete](40_how-to-delete-from-kv-map.md) the newly created or updated `person` from the map. To accomplish this task, use the `erase()` function of the `kv map`. diff --git a/docs/06_how-to-guides/30_key-value-api/kv_map/40_how-to-delete-from-kv-map.md b/docs/06_how-to-guides/30_key-value-api/kv_map/40_how-to-delete-from-kv-map.md new file mode 100644 index 0000000000..aeb31cc58a --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_map/40_how-to-delete-from-kv-map.md @@ -0,0 +1,110 @@ +--- +content_title: How-To Delete from Key-Value Map +link_text: "How-To Delete from Key-Value Map" +--- + +## Overview + +This how-to provides instructions to delete from `Key-Value Map` (`kv map`) based on the unique key. + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index) +* A smart contract named `smrtcontract` +* A user defined type named `person`, which defines the data stored in the map +* A `kv map` object, name `my_map`, which stores objects of type `person`, with unique keys of type `int` + +Refer to the following reference implementation for your starting point: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; +}; + +class [[eosio::contract]] smartcontract : public eosio::contract { + + using my_map_t = eosio::kv::map<"kvmap"_n, int, person>; + + public: + using contract::contract; + smartcontract(eosio::name receiver, eosio::name code, eosio::datastream ds) + : contract(receiver, code, ds) {} + + private: + my_map_t my_map{}; +}; +``` + +## Procedure + +Complete the following steps to delete a `person` object with a given ID from the `kv map`, and print an informative message otherwise: + +1. Create a new action in your contract, named `delete`, which takes as input parameters the person ID. +2. Use the `find()` function defined for the `kv::map` type, with the give ID as parameter, to find the `person` which is to be deleted. +3. If the `person` with the give ID is found use the `erase()` function defined for the `kv::map`, with the given ID as parameter, to erase the person object from the map. +4. If the `person` with the given ID is not found print an informative error message. + +Refer to the following reference implementation to delete a `person` object from the `kv map`: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; +}; + +class [[eosio::contract]] smartcontract : public eosio::contract { + + using my_map_t = eosio::kv::map<"kvmap"_n, int, person>; + + public: + using contract::contract; + smartcontract(eosio::name receiver, eosio::name code, eosio::datastream ds) + : contract(receiver, code, ds) {} + + // deletes a person based on unique id + [[eosio::action]] + void del(int id); + + private: + my_map_t my_map{}; +}; +``` + +`smartcontract.cpp file` + +```cpp +// deletes a person based on unique id +[[eosio::action]] +void kv_map::delete(int id) { + + // search for person based on unique id + auto itr = my_map.find(id); + + // check if person was found + if (itr != my_map.end()) { + // extract person from iterator and delete it + const auto person_found = itr->second(); + + // delete it from kv::map + my_map.erase(id); + eosio::print_f("Person (%, %, %) was successfully deleted.", + person_found.first_name, person_found.last_name, person_found.personal_id); + } + else { + eosio::print_f("Person with ID % not found.", id); + } +} +``` + +## Summary + +In conclusion, the above instructions show how to delete from `Key-Value Map` (`kv map`) based on the unique key. diff --git a/docs/06_how-to-guides/30_key-value-api/kv_map/50_how-to-iterate-kv-map.md b/docs/06_how-to-guides/30_key-value-api/kv_map/50_how-to-iterate-kv-map.md new file mode 100644 index 0000000000..48a6307da9 --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_map/50_how-to-iterate-kv-map.md @@ -0,0 +1,104 @@ +--- +content_title: How-To Iterate Through Key-Value Map +link_text: "How-To Iterate Through Key-Value Map" +--- + +## Overview + +This how-to provides instructions to iterate through a `Key-Value Map` (`kv map`) and read values from it. + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index) +* A smart contract named `smrtcontract` +* A user defined type named `person`, which defines the data stored in the map +* A `kv map` object, name `my_map`, which stores objects of type `person`, with unique keys of type `int`. + +Refer to the following reference implementation for your starting point: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; +}; + +class [[eosio::contract]] smartcontract : public eosio::contract { + + using my_map_t = eosio::kv::map<"kvmap"_n, int, person>; + + public: + using contract::contract; + smartcontract(eosio::name receiver, eosio::name code, eosio::datastream ds) + : contract(receiver, code, ds) {} + + private: + my_map_t my_map{}; +}; +``` + +## Procedure + +Complete the following steps to implement an action which iterates through the first N `person` objects in the `kv map` and prints their first and last names: + +1. Create a new action `iterate`, which takes as an input parameter the number of iterations to be executed. +2. Refer to the following reference implementation to implement an action which iterates through the first `iterations_count` objects in `my_map` and prints their first and last names: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; +}; + +class [[eosio::contract]] smartcontract : public eosio::contract { + + using my_map_t = eosio::kv::map<"kvmap"_n, int, person>; + + public: + using contract::contract; + smartcontract(eosio::name receiver, eosio::name code, eosio::datastream ds) + : contract(receiver, code, ds) {} + + // iterates over the first iterations_count persons in the table + // and prints their first and last names + [[eosio::action]] + void iterate(int iterations_count); + + private: + my_map_t my_map{}; +}; +``` + +`smartcontract.cpp file` + +```cpp +// iterates over the first iterations_count persons using +// and prints their first and last names +[[eosio::action]] +void kv_map::iterate(int iterations_count) { + + int current_iteration = 0; + for ( const auto& person_detail : my_map ) { + if (current_iteration ++ < iterations_count) { + eosio::print_f( + "Person %: {%, %}. ", + current_iteration, + person_detail.second().first_name, + person_detail.second().last_name); + } + else { + break; + } + } +}``` + +## Summary + +In conclusion, the above instructions show how to iterate through a `Key-Value Map` (`kv map`) and read values from it. \ No newline at end of file diff --git a/docs/06_how-to-guides/30_key-value-api/kv_map/70_how-to-find-in-kv-map.md b/docs/06_how-to-guides/30_key-value-api/kv_map/70_how-to-find-in-kv-map.md new file mode 100644 index 0000000000..ad3ffc4cd6 --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_map/70_how-to-find-in-kv-map.md @@ -0,0 +1,112 @@ +--- +content_title: How-To Find in Key-Value Map +link_text: "How-To Find in Key-Value Map" +--- + +## Overview + +This how-to provides instructions to find an object in `Key-Value Map` (`kv map`) based on the unique key. + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index) +* A smart contract named `smrtcontract` +* A user defined type named `person`, which defines the data stored in the map +* A `kv map` object, name `my_map`, which stores objects of type `person`, with unique keys of type `int`. + +Refer to the following reference implementation for your starting point: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; +}; + +class [[eosio::contract]] smartcontract : public eosio::contract { + + using my_map_t = eosio::kv::map<"kvmap"_n, int, person>; + + public: + using contract::contract; + smartcontract(eosio::name receiver, eosio::name code, eosio::datastream ds) + : contract(receiver, code, ds) {} + + private: + my_map_t my_map{}; +}; +``` + +## Procedure + +Complete the following steps to find a `person` object with a given ID: + +1. Create a new action in your contract, named `delete`, which takes as input parameters the person ID. +2. Use the `find()` function defined for the `kv::map` type, with the give ID as parameter, to find the `person` with the given ID as unique key. + +Refer to the following reference implementation to find `person` object with a given ID as unique key: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; +}; + +class [[eosio::contract]] smartcontract : public eosio::contract { + + using my_map_t = eosio::kv::map<"kvmap"_n, int, person>; + + public: + using contract::contract; + smartcontract(eosio::name receiver, eosio::name code, eosio::datastream ds) + : contract(receiver, code, ds) {} + + // finds a person based on unique id + [[eosio::action]] + void find(int id); + + private: + my_map_t my_map{}; +}; +``` + +`smartcontract.cpp file` + +```cpp +// finds a person based on unique id +[[eosio::action]] +void kv_map::find(int id) { + + auto itr = my_map.find(id); + + // check if person was found + if (itr != my_map.end()) { + // extract person from iterator and delete it + const auto person_found = itr->second(); + + eosio::print_f("Person with unique ID=% was found: %, %, %.", + id, person_found.first_name, person_found.last_name, person_found.personal_id); + } + else { + eosio::print_f("Person with ID % not found.", id); + } +} +``` + +## Summary + +In conclusion, the above instructions show how to find an object in `Key-Value Map` (`kv map`) based on the unique key. + +## Next Steps + +The following options are available when you complete the procedure: + +* [Update](30_how-to-upsert-into-kv-map.md) the `person` found. +* [Delete](40_how-to-delete-from-kv-map.md) the `person` found. diff --git a/docs/06_how-to-guides/30_key-value-api/kv_map/90_how-to-allow-users-to-pay-kv-map.md b/docs/06_how-to-guides/30_key-value-api/kv_map/90_how-to-allow-users-to-pay-kv-map.md new file mode 100644 index 0000000000..ba5943fa1e --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_map/90_how-to-allow-users-to-pay-kv-map.md @@ -0,0 +1,114 @@ +--- +content_title: How-To Create An Action Which Requires The User To Pay +link_text: "How-To Create An Action Which Requires The User To Pay" +--- + +## Overview + +This guide provides instructions which show you how to create an action which requires the user to pay for the resources needed to store data in a `Key-Value Map` (`kv map`). + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index) +* A smart contract named `smrtcontract` +* A user defined type named `person`, which defines the data stored in the map +* A `kv map` object, name `my_map`, which stores objects of type `person`, with unique keys of type `int`. + +Refer to the following reference implementation for your starting point: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; +}; + +class [[eosio::contract]] smartcontract : public eosio::contract { + + using my_map_t = eosio::kv::map<"kvmap"_n, int, person>; + + public: + using contract::contract; + smartcontract(eosio::name receiver, eosio::name code, eosio::datastream ds) + : contract(receiver, code, ds) {} + + private: + my_map_t my_map{}; +}; +``` + +## Procedure + +Complete the following steps to create an action which requires the `person`'s account, which is stored in the map, to pay for the resources needed to store the `person` object in the `my_map` object. + +1. Create a new action in your contract, named `upsert`, which takes as input parameters the person `id`, an `account_name`, a `first_name` and a `last_name`. +2. Create an instance of the `person` class, named `person_upsert`, based on the input parameters: `account_name`, `first_name` and `last_name`. +3. Use the `[]` operator defined for the `kv::map` type. Pass as the input parameter for the `[]` operator an `std::pair` instance with its first parameter the unique `id` and the second parameter the account which is paying for the resources `account_name`. + +Refer to the following reference implementation to insert a new `person` object, and then update it, in the `kv map`: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; +}; + +class [[eosio::contract]] smartcontract : public eosio::contract { + + using my_map_t = eosio::kv::map<"kvmap"_n, int, person>; + + public: + using contract::contract; + smartcontract(eosio::name receiver, eosio::name code, eosio::datastream ds) + : contract(receiver, code, ds) {} + + // inserts a person if not exists, or updates it if already exists. + // the payer is the account_name, specified as input parameter. + [[eosio::action]] + void upsert(int id, + eosio::name account_name, + std::string first_name, + std::string last_name); + + private: + my_map_t my_map{}; +}; +``` + +`smartcontract.cpp file` + +```cpp +// inserts if not exists, or updates if already exists, a person +[[eosio::action]] +void smartcontract::upsert( + int id, + eosio::name account_name, + std::string first_name, + std::string last_name) { + + // create the person object which will be stored in kv::map + const person& person_upsert = person{ + account_name = account_name, + first_name = first_name, + last_name = last_name}; + + // upsert into kv::map and set the payer to be the account_name + my_map[std::pair(id, account_name)] = person_upsert; +} +``` + +## Summary + +In conclusion, the above instructions show how to require the user to pay for the resources needed to store data in a `Key-Value Map` (`kv map`). + +## Next Steps + +The following options are available when you complete the procedure: + +* [Verify](70_how-to-find-in-kv-map.md) if the newly inserted `person` actually exists in the map. To accomplish this task use the `find()` function of the `kv_map`. +* [Delete](40_how-to-delete-from-kv-map.md) the newly created or updated `person` from the map. To accomplish this task, use the `erase()` function of the `kv map`. diff --git a/docs/06_how-to-guides/30_key-value-api/kv_map/index.md b/docs/06_how-to-guides/30_key-value-api/kv_map/index.md new file mode 100644 index 0000000000..488f217744 --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_map/index.md @@ -0,0 +1,15 @@ +--- +content_title: Key-Value Map +link_text: "Key-Value Map" +--- + +The `KV Map` offered by the `KV API` serves as a storage location which is organized as a sorted associative container that contains key-value pairs with unique keys. Keys are sorted lexicographically. Search, removal, and insertion operations have logarithmic complexity, `O(log(n))`. 'KV Map' is designed to offer a comparable interface to `std::map` template class. + +Consult the [KV Map Reference](../../../classeosio_1_1kv_1_1map/#class-eosiokvmap) and the `KV Map How-To`s listed below for details. + +* [How To Use KV Map](./10_how-to-use-kv-map.md) +* [How To Upsert Into KV Map](./30_how-to-upsert-into-kv-map.md) +* [How To Delete From KV Map](./40_how-to-delete-from-kv-map.md) +* [How To Iterate Trough KV Map Keys](./50_how-to-iterate-kv-map.md) +* [How To Find In KV Map](./70_how-to-find-in-kv-map.md) +* [How To Allow Users To Pay](./90_how-to-allow-users-to-pay-kv-map.md) diff --git a/docs/06_how-to-guides/30_key-value-api/kv_table/10_how-to-use-kv-table.md b/docs/06_how-to-guides/30_key-value-api/kv_table/10_how-to-use-kv-table.md new file mode 100644 index 0000000000..60516007aa --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_table/10_how-to-use-kv-table.md @@ -0,0 +1,94 @@ +--- +content_title: How-To Use Key-Value Table +link_text: "How-To Use Key-Value Table" +--- + +## Overview + +This how-to demonstrates how to define and use a `Key-Value Table` (`kv table`) in a smart contract. + +To accomplish this task, define the user type which will be stored in the `kv table`, and extend the `eosio::kv::table` template class with a specialized definition based on the user defined type. + +[[caution | Alpha version]] +| `Key-Value Table` is designated as `alpha` and should not be used in production code. + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index). +* A smart contract named `smrtcontract`. +* A user defined type named `person`, which defines the data stored in the table. + +Refer to the following reference implementation for your starting point: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; + std::string street; + std::string city; + std::string state; + std::string country; + std::string personal_id; +}; +class [[eosio::contract]] smrtcontract : public contract { + public: + using contract::contract; +}; +``` + +## Procedure + +Complete the following steps to define the `address_table` type, based on the `eosio::kv::table`, which stores objects of type `person`: + +1. Define the structure or class `address_table` in the scope of your smart contract class, for the abi generation to find it and place it into the abi file. +2. Derive `address_table` from `eosio::`kv::table` class template. Pass the `person` user defined type as the type parameter for `eosio::`kv::table` base class and the name of the `kv table`, let’s say `kvaddrbook`. +3. Annotate `address_table` type with `[[eosio::table]]`, and make sure it is placed after the `struct` keyword but before the name of the type. +4. Define a primary index `first_name_idx` based on the property `person::first_name`. Every `kv table` requires a primary index to be defined based on a property that stores unique values. +5. In the `address_table` constructor, call the `init(...)` base class method with the two parameters: + 1. The first parameter, of type `eosio::name`, is the contract that owns the table. + 2. The second parameter is the `account_name_uidx` primary index. + +Refer below for a possible implementation of the above described steps: + +`smartcontract.hpp file` + +```cpp +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + + address_table(name contract_name) { + init(contract_name, account_name_uidx) + } + }; + public: + using contract::contract; +}; +``` + +## Summary + +In conclusion, the above instructions show how to define and use a `Key-Value Table` (`kv table`) in a smart contract. + +## Next Steps + +The following options are available when you complete the procedure: + +* You can [create one or more unique indexes](20_how-to-create-indexes-kv-table.md) using the `KV_NAMED_INDEX` macro or the `eosio::kv::table::index` template class. +* You can [create one or more non-unique indexes](20_how-to-create-indexes-kv-table.md) using the `KV_NAMED_INDEX` macro or the `eosio::kv::table::index` template class. +* You can access the defined `kv table` and perform operations on it and its defined indexes: + * [How To Upsert Into KV Table](./30_how-to-upsert-into-kv-table.md) + * [How To Delete From KV Table](./40_how-to-delete-from-kv-table.md) + * [How To Iterate A KV Table](./50_how-to-iterate-kv-table.md) + * [How To Check A Record In KV Table](./60_how-to-check-a-record-kv-table.md) + * [How To Find In KV Table](./70_how-to-find-in-kv-table.md) + * [How To Query Range In KV Table](./80_how-to-query-range-in-kv-table.md) + * [How To Allow Users To Pay](./90_how-to-allow-users-to-pay-kv-table.md) diff --git a/docs/06_how-to-guides/30_key-value-api/kv_table/20_how-to-create-indexes-kv-table.md b/docs/06_how-to-guides/30_key-value-api/kv_table/20_how-to-create-indexes-kv-table.md new file mode 100644 index 0000000000..4e5867bcf8 --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_table/20_how-to-create-indexes-kv-table.md @@ -0,0 +1,193 @@ +--- +content_title: How-To Create Indexes on a Key-Value Table +link_text: "How-To Create Indexes on a Key-Value Table" +--- + +## Overview + +This how-to provides instructions to create the following indexes on a `Key-Value Table` (`kv table`): + +* A unique index using the macro `KV_NAMED_INDEX` +* A unique index using the `eosio::kv::table::index` template class +* A non-unique index using the macro `KV_NAMED_INDEX` +* A non-unique index using the `eosio::kv::table::index` template class + +The `KV_NAMED_INDEX` macro and the `eosio::kv::table::index` template class are provided by the KV API. + +[[caution | Alpha version]] +| `Key-Value Table` is designated as `alpha` and should not be used in production code. + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index) +* A smart contract named `smrtcontract` +* A user defined type which defines the data stored in the table, named `person` +* A `kv table` type which stores objects of type `person`, named `address_table` +* Each `person` object has the following properties: + * `account_name`, + * `first_name`, + * `last_name`, + * `personal_id`. + +Refer to the following reference implementation for your starting point: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; + std::string personal_id; +}; + +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + } + }; + public: + using contract::contract; +}; +``` + +## Procedure + +### Define a unique index on property account_name using the macro KV_NAMED_INDEX + +1. Use the `KV_NAMED_INDEX` macro with two parameters. +2. Pass the name of the index as the first parameter. The parameter must be a qualified `eosio::name`. See documentation for the [eosio name restrictions](https://developers.eos.io/welcome/latest/glossary/index#account-name). +3. Pass the name of the property for which the index is defined as the second parameter. +4. Call `init()` of the base class in the constructor of `address_table` type and pass the contract name as the first parameter and `account_name` index defined previously, by the KV_NAMED_INDEX macro, as the second parameter. + +Refer to the following reference implementation of a unique index on property `account_name` using macro `KV_NAMED_INDEX`: + +`smartcontract.hpp file` + +```cpp +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + KV_NAMED_INDEX("accname"_n, account_name) + + address_table(eosio::name contract_name) { + init(contract_name, account_name); + } + }; + public: + using contract::contract; +}; +``` + +### Define a unique index on property personal_id using the eosio::kv::table::index template class + +1. Use the `eosio::kv::table::index` template class with two parameters. +2. Pass the name of the index as the first parameter. The parameter must be a qualified `eosio:name`. See documentation for the [eosio name restrictions](https://developers.eos.io/welcome/latest/glossary/index#account-name). +3. Pass the reference to the property for which the index is defined, `&person::personal_id`, as the second parameter. +4. Call `init()` of the base class in the constructor of `address_table` type and pass the contract name as the first parameter and the `personal_id_idx` index defined previously as the second parameter. + +Refer to the following reference implementation of a unique index on property `personal_id` using `eosio::kv::table::index` template class: + +`smartcontract.hpp file` + +```cpp +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index personal_id_idx { + name{"persid"_n}, + &person::personal_id }; + + address_table(eosio::name contract_name) { + init(contract_name, personal_id_idx) + } + }; + public: + using contract::contract; +}; +``` + +### Define a non-unique index on property first_name using the macro KV_NAMED_INDEX + +1. Use the `KV_NAMED_INDEX` with two parameters. +2. Pass the name of the index as the first parameter. The parameter must be a qualified `eosio::name`. See documentation for the [eosio name restrictions](https://developers.eos.io/welcome/latest/glossary/index#account-name). +3. Pass the name of the property for which the index is defined as the second parameter. +4. Call `init()` of the base class in the constructor of `address_table` type and pass the contract name as the first parameter and `first_name` index defined previously, by the KV_NAMED_INDEX macro, as the second parameter. + +The property used for the second parameter must be of template type `std::tuple`. The first parameter must be the type of the property indexed non-uniquely, in our case the type `std::string` is used because `first_name` is the property indexed non-uniquely. And the last parameter of the tuple type must be the type of a property name which is unique. In our case the type `eosio::name` is used because property `account_name` is unique. Multiple properties can be indexed non-uniquely as well. In this case the first parameter types correspond to the properties being indexed. And, as previously already mentioned, the last parameter correspond to the type of a property name which is unique. + +Refer to the following reference implementation of a non-unique index on property `account_name` using macro `KV_NAMED_INDEX`: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::tuple first_name; + std::string last_name; + std::string personal_id; +}; + +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + KV_NAMED_INDEX("accname"_n, first_name) + + address_table(eosio::name contract_name) { + init(contract_name, first_name); + } + }; + public: + using contract::contract; +}; +``` + +### Define a non-unique index on property last_name using the eosio::kv::table::index template class + +1. Use the `eosio::kv::table::index` template class. +2. Pass as the first parameter the name of the index. It must be a qualified `eosio:name`, see documentation for the [eosio name restrictions](https://developers.eos.io/welcome/latest/glossary/index#account-name). +3. Pass the reference to the property for which the index is defined, `&person::last_name`, as the second parameter. +4. Call `init()` of the base class in the constructor of `address_table` type and pass the contract name as the first parameter and the `last_name_idx` index defined previously as the second parameter. + + +The property used for the second parameter must be of template type `std::tuple`. The first parameter must be the type of the property indexed non-uniquely, in our case the type `std::string` is used because `first_name` is the property indexed non-uniquely. And the last parameter of the tuple type must be the type of a property name which is unique. In our case the type `eosio::name` is used because property `account_name` is unique. Multiple properties can be indexed non-uniquely as well. In this case the first parameter types correspond to the properties being indexed. And, as previously already mentioned, the last parameter correspond to the type of a property name which is unique. + +Refer to the following reference implementation of a non-unique index on property `last_name` using `eosio::kv::table::index` template class: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::tuple last_name; + std::string personal_id; +}; + +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index> last_name_idx { + name{"persid"_n}, + &person::last_name}; + + address_table(eosio::name contract_name) { + init(contract_name, last_name_idx) + } + }; + public: + using contract::contract; +}; +``` + +## Summary + +In conclusion, the above instructions show how to create indexes on a `Key-Value Table` (`kv table`). + +## Next Steps + +The following options are available when you complete the procedure: + +* [Search](70_how-to-find-in-kv-table.md) by index key for values or range of values in the defined `kv table`. +* [Check](60_how-to-check-a-record-kv-table.md) if a particular key exists in an index. diff --git a/docs/06_how-to-guides/30_key-value-api/kv_table/30_how-to-upsert-into-kv-table.md b/docs/06_how-to-guides/30_key-value-api/kv_table/30_how-to-upsert-into-kv-table.md new file mode 100644 index 0000000000..f9cda5a238 --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_table/30_how-to-upsert-into-kv-table.md @@ -0,0 +1,118 @@ +--- +content_title: How-To Upsert Into Key-Value Table +link_text: "How-To Upsert Into Key-Value Table" +--- + +## Overview + +This how-to provides instructions to upsert into `Key-Value Table` (`kv table`). + +[[caution | Alpha version]] +| `Key-Value Table` is designated as `alpha` and should not be used in production code. + +Use the method `put` defined by the `eosio::kv::table` type to accomplish this task. + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index) +* A smart contract named `smrtcontract` +* A user defined type named `person`, which defines the data stored in the table +* A `kv table` type which stores objects of type `person`, named `address_table`. The primary index of the `kv table` is defined based on the `person::account_name` property. + +Refer to the following reference implementation for your starting point: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; + std::string personal_id; +}; + +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + + address_table(name contract_name) { + init(contract_name, account_name_uidx) + } + }; + public: + using contract::contract; +}; +``` + +## Procedure + +Complete the following steps to insert a new `person` object, and then update it, in the `kv table`: + +1. Create a new action `upsert` in your smart contact class, which takes as input parameters an account name, a first name, a last name and a personal id. +2. In the `upsert` action access the instance of `address_table` by declaring a local variable of `address_table` type. +3. And then call the `put` method of the `address_table` and pass to it a newly created `person` object based on the action’s input parameters. + +Refer to the following reference implementation to insert a new `person` object, and then update it, in the `kv table`: + +`smartcontract.hpp file` + +```cpp +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + + address_table(name contract_name) { + init(contract_name, account_name_uidx) + } + }; + public: + using contract::contract; + + // creates if not exists, or updates if already exists, a person + [[eosio::action]] + void upsert(eosio::name account_name, + string first_name, + string last_name, + string country, + string personal_id); + + using upsert_action = action_wrapper<"upsert"_n, &smrtcontract::upsert>; +}; +``` + +`smartcontract.cpp file` + +```cpp +// creates if not exists, or updates if already exists, a person +[[eosio::action]] +void smrtcontract::upsert( + eosio::name account_name, + string first_name, + string last_name, + string personal_id) { + address_table addresses{"kvaddrbook"_n}; + + // call put which upserts into kv table + addresses.put({account_name, first_name, last_name, personal_id}, get_self()); +} +``` + +## Summary + +In conclusion, the above instructions show how to upsert into `Key-Value Table` (`kv table`). + +## Next Steps + +The following options are available when you complete the procedure: + +* [Check](60_how-to-check-a-record-kv-table.md) if the newly inserted `person` actually exists in the table. To accomplish this task, use the `exists()` function of any index defined for the table. +* [Retrieve](70_how-to-find-in-kv-table.md) the newly inserted or updated `person` from the table. To accomplish this task, use the `find()` function of any index defined for the table. +* [Delete](40_how-to-delete-from-kv-table.md) the newly created or updated `person` from the table. To accomplish this task, use the `erase()` function of the `kv table`. diff --git a/docs/06_how-to-guides/30_key-value-api/kv_table/40_how-to-delete-from-kv-table.md b/docs/06_how-to-guides/30_key-value-api/kv_table/40_how-to-delete-from-kv-table.md new file mode 100644 index 0000000000..37f9e37f58 --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_table/40_how-to-delete-from-kv-table.md @@ -0,0 +1,107 @@ +--- +content_title: How-To Delete from Key-Value Table +link_text: "How-To Delete from Key-Value Table" +--- + +## Overview + +This how-to provides instructions to delete an object from a `Key-Value Table` (`kv table`). + +[[caution | Alpha version]] +| `Key-Value Table` is designated as `alpha` and should not be used in production code. + +Use the method `erase` defined by the `eosio::kv::table` type to accomplish this task. + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index) +* A smart contract named `smrtcontract` +* A user defined type named `person`, which defines the data stored in the table +* A `kv table` type which stores objects of type `person`, named `address_table`. The primary index of the `kv table` is defined based on the `person::account_name` property. + +Refer to the following reference implementation for your starting point: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; + std::string personal_id; +}; + +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + + address_table(name contract_name) { + init(contract_name, account_name_uidx) + } + }; + public: + using contract::contract; +}; +``` + +## Procedure + +Complete the following steps to delete a `person` object from `address_table`: + +1. Create a new action `delete`, in a smart contract class, which takes as input parameters an account name, a first name, a last name and a personal id. +2. In the `delete` action access the instance of `address_table` by declaring a local variable of `address_table` type. +3. Call the `erase` method of the `address_table` and pass to it the primary key for the object which is deleted. If you try to erase an object which is not present in the `kv table` no error will be raised. + +Refer to the following reference implementation to delete a `person` object from `address_table`: + +`smartcontract.hpp file` + +```cpp +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + + address_table(name contract_name) { + init(contract_name, account_name_uidx) + } + }; + public: + using contract::contract; + + // deletes a person based on primary key account_name + [[eosio::action]] + void del(eosio::name account_name); + + using del_action = eosio::action_wrapper<"del"_n, &smrtcontract::del>; +}; +``` + +`smartcontract.cpp file` + +```cpp +[[eosio::action]] +void smrtcontract::delete(eosio::name account_name) { + address_table addresses{"kvaddrbook"_n}; + + // delete from kv table + addresses.erase(account_name, get_self()); +} +``` + +## Summary + +In conclusion, the above instructions show how to delete an object from a `Key-Value Table` (`kv table`). + +## Next Steps + +The following options are available when you complete the procedure: + +* [Check](60_how-to-check-a-record-kv-table.md) if the newly inserted `person` was actually deleted from the table. To accomplish this task, use the `exists()` function of any index defined for the table. diff --git a/docs/06_how-to-guides/30_key-value-api/kv_table/50_how-to-iterate-kv-table.md b/docs/06_how-to-guides/30_key-value-api/kv_table/50_how-to-iterate-kv-table.md new file mode 100644 index 0000000000..0e4575def4 --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_table/50_how-to-iterate-kv-table.md @@ -0,0 +1,124 @@ +--- +content_title: How-To Iterate Through Key-Value Table +link_text: "How-To Iterate Through Key-Value Table" +--- + +## Overview + +This how-to provides instructions to iterate through a `Key-Value Table` (`kv table`) and read values from it. + +[[caution | Alpha version]] +| `Key-Value Table` is designated as `alpha` and should not be used in production code. + +Use the `iterator` defined by the `eosio::kv::table::index` class to accomplish this task. + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index). +* A smart contract named `smrtcontract`. +* A user defined type which defines the data stored in the table, named `person`. +* A `kv table` type which stores objects of type `person`, named `address_table`. +* Each `person` object has the following properties: + * `account_name`, + * `first_name`, + * `last_name`, + * `personal_id`. +* A unique index, named `account_name_uidx`, defined on the `account_name` property.. + +Refer to the following reference implementation for your starting point: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; + std::string personal_id; +}; + +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + + address_table(name contract_name) { + init(contract_name, account_name_uidx) + } + }; + public: + using contract::contract; +}; +``` + +## Procedure + +Complete the following steps to implement an action which iterates through the first N `person` objects in `address_table` and prints their first and last names: + +1. Create a new action `iterate`, which takes as an input parameter the number of iterations to be executed. +2. In the `iterate` action access the instance of `address_table` by declaring a local variable of `address_table` type. +3. Capture the `begin` and the `end` of the `account_name_uidx` index defined. +4. Use the iterator `value` to access the current value of the iterator. +5. And then increment the iterator until the first N `person` objects stored in `address_table` are visited. + +Refer to the following reference implementation to implement an action which iterates through the first N `person` objects in `address_table` and prints their first and last names: + +`smartcontract.hpp` + +```cpp +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + + address_table(name contract_name) { + init(contract_name, account_name_uidx) + } + }; + public: + using contract::contract; + + // iterates over the first n persons in the table + [[eosio::action]] + std::vector iterate(int iterations_count +); + + using iterate_action = action_wrapper<"iterate"_n, &smrtcontract::iterate>; +}; +``` + +`smartcontract.cpp file` + +```cpp +// Iterates over the first iterations_count persons in the table +[[eosio::action]] +std::vector smrtcontract::iterate(int iterations_count) { + address_table addresses{"kvaddrbook"_n}; + + + auto begin_itr = addresses.account_name_uidx.begin(); + auto end_itr = addresses.account_name_uidx.end(); + + int current_iteration = 0; + while (begin_itr != end_itr && current_iteration < iterations_count) + { + eosio::print_f( + "Person found: {%, %}\n", + begin_itr.value().first_name, + begin_itr.value().last_name); + + ++ begin_itr; + ++ current_iteration; + } +} +``` + +## Summary + +In conclusion, the above instructions show how to iterate through a `Key-Value Table` (`kv table`) and read values from it. diff --git a/docs/06_how-to-guides/30_key-value-api/kv_table/60_how-to-check-a-record-kv-table.md b/docs/06_how-to-guides/30_key-value-api/kv_table/60_how-to-check-a-record-kv-table.md new file mode 100644 index 0000000000..58da59df1a --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_table/60_how-to-check-a-record-kv-table.md @@ -0,0 +1,113 @@ +--- +content_title: How-To Check a Record in a Key-Value Table +link_text: "How-To Check a Record in a Key-Value Table" +--- + +## Overview + +This how-to provides instructions to check if a specific object exists in a `Key-Value Table` (`kv table`). + +[[caution | Alpha version]] +| `Key-Value Table` is designated as `alpha` and should not be used in production code. + +Use the method `exists` defined by the `eosio::kv::table::index` class to accomplish this task. + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index). +* A smart contract named `smrtcontract`. +* A user defined type which defines the data stored in the table, named `person`. +* A `kv table` type which stores objects of type `person`, named `address_table`. +* Each `person` object has the following properties: + * `account_name`, + * `first_name`, + * `last_name`, + * `personal_id`. +* A unique index, named `account_name_uidx`, defined on the `account_name` property.. + +Refer to the following reference implementation for your starting point: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; + std::string personal_id; +}; + +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + + address_table(name contract_name) { + init(contract_name, account_name_uidx) + } + }; + public: + using contract::contract; +}; +``` + +## Procedure + +Complete the following steps to implement an action that is verifying whether a particular `person` identified by its `account_name` exists in the `address_table`: + +1. Create a new action `verify`, which takes as an input parameter the account name of the person to be verified. +2. In the `verify` action access the instance of `address_table` by declaring a local variable of `address_table` type. +3. Call the `exists()` method of the `account_name` index defined in the `kv table` class and pass the account name of the person to be verified. + +Refer to the following reference implementation to implement an action that is verifying whether a particular `person` identified by its `account_name` exists in the `address_table`: + +`smartcontract.hpp` + +```cpp +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + + address_table(name contract_name) { + init(contract_name, account_name_uidx) + } + }; + public: + using contract::contract; + + // checks if a person with a specific account name exists in addressbook + [[eosio::action]] + bool verify(eosio::name account_name); + + using verify_action = action_wrapper<"verify"_n, &smrtcontract::verify>; +}; +``` + +`smartcontract.cpp file` + +```cpp +// checks if a person with a specific account name exists in addressbook +[[eosio::action]] +bool smrtcontract::verify(string personal_id, string country) { + address_table addresses{"kvaddrbook"_n}; + + return addresses.account_name_uidx.exists({personal_id, country}); +} +``` + +## Summary + +In conclusion, the above instructions show how to check if a specific object exists in a `Key-Value Table` (`kv table`). + +## Next Steps + +The following options are available when you complete the procedure: + +* Implement business logic and rely on the information that the `person` object exists in the table. diff --git a/docs/06_how-to-guides/30_key-value-api/kv_table/70_how-to-find-in-kv-table.md b/docs/06_how-to-guides/30_key-value-api/kv_table/70_how-to-find-in-kv-table.md new file mode 100644 index 0000000000..d365c73cb8 --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_table/70_how-to-find-in-kv-table.md @@ -0,0 +1,131 @@ +--- +content_title: How-To Find in Key-Value Table +link_text: "How-To Find in Key-Value Table" +--- + +## Overview + +This how-to provides instructions to find a specific object in a `Key-Value Table` (`kv table`). + +[[caution | Alpha version]] +| `Key-Value Table` is designated as `alpha` and should not be used in production code. + +Use the method `find()` defined by the `eosio::kv::table::index` class to accomplish this task. + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index). +* A smart contract named `smrtcontract`. +* A user defined type which defines the data stored in the table, named `person`. +* A `kv table` type which stores objects of type `person`, named `address_table`. +* Each `person` object has the following properties: + * `account_name`, + * `first_name`, + * `last_name`, + * `personal_id`. +* A unique index, named `account_name_uidx`, defined on the `account_name` property.. + +Refer to the following reference implementation for your starting point: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; + std::string personal_id; +}; + +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + + address_table(name contract_name) { + init(contract_name, account_name_uidx) + } + }; + public: + using contract::contract; +}; +``` + +## Procedure + +Complete the following steps to implement an action to find a particular `person` identified by the `account_name` in the `address_table` and returns the person back to the caller: + +1. Create a new action `find`, which takes as input parameters the account name of the person to be verified. +2. In the `find` action access the instance of `address_table` by declaring a local variable of `address_table` type. +3. Call the `find` method of the `account_name` index defined in the table and pass the account name of the person to be verified. +4. Check if the `person` was found and return it back to the caller; if person was not found return an empty `person` object. + +Refer to the following reference implementation to implement an action to find a particular `person` identified by the `account_name` in the `address_table` and returns the person back to the caller: + +`smartcontract.hpp` + +```cpp +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + + address_table(name contract_name) { + init(contract_name, account_name_uidx) + } + }; + public: + using contract::contract; + + // Finds a person based on the primary key account_name. + [[eosio::action]] + bool find(eosio::name account_name); + + using find_action = action_wrapper<"find"_n, &smrtcontract::find>; +}; +``` + +`smartcontract.cpp file` + +```cpp +// Finds a person based on the primary key account_name. +// Makes use of index find function, which returns an iterator, and then uses iterator value. +[[eosio::action]] +person smrtcontract::find(name account_name) { + address_table addresses{"kvaddrbook"_n}; + + // search for person by primary key account_name + auto itr = addresses.account_name_uidx.find(account_name); + + // check if person was found + if (itr != addresses.account_name_uidx.end()) { + // extract person from iterator, print it and return it to the action sender + const auto& person_found = itr.value(); + + // return found person from action + return person_found; + } + else { + eosio::print_f("Person not found in addressbook."); + // return empty person from action + return person{}; + } +} +``` + +## Summary + +In conclusion, the above instructions show how to find a specific object in a `Key-Value Table` (`kv table`). + +## Next Steps + +The following options are available when you complete the procedure: + +* [Update](30_how-to-upsert-into-kv-table.md) the `person` found and returned. +* [Delete](40_how-to-delete-from-kv-table.md) the `person` found and returned. diff --git a/docs/06_how-to-guides/30_key-value-api/kv_table/80_how-to-query-range-in-kv-table.md b/docs/06_how-to-guides/30_key-value-api/kv_table/80_how-to-query-range-in-kv-table.md new file mode 100644 index 0000000000..d7f632b276 --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_table/80_how-to-query-range-in-kv-table.md @@ -0,0 +1,130 @@ +--- +content_title: How-To Query Range in Key-Value Table +link_text: "How-To Query Range in Key-Value Table" +--- + +## Overview + +This how-to provides instructions to retrieve a list of values, from a `Key-Value Table` (`kv table`) index, which share a particular commonality. + +[[caution | Alpha version]] +| `Key-Value Table` is designated as `alpha` and should not be used in production code. + +Use the method `range` defined by the `eosio::kv::table::index` class to accomplish this task. + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index). +* A smart contract named `smrtcontract`. +* A user defined type which defines the data stored in the table, named `person`. +* A `kv table` type which stores objects of type `person`, named `address_table`. +* Each `person` object has the following properties: + * `account_name`, + * `first_name`, + * `last_name`, + * `personal_id`. +* A unique index, named `account_name_uidx`, defined on the `account_name` property.. +* A non-unique index defined on the `last_name` property, named `last_name_idx`. + +Refer to the following reference implementation for your starting point: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; + std::string personal_id; +}; + +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + + index> last_name_idx { + name{"lastname"_n}, + &person::last_name}; + + address_table(name contract_name) { + init(contract_name, account_name_uidx) + } + }; + public: + using contract::contract; +}; +``` + +## Procedure + +Complete the following steps to implement an action to retrieve a list of persons with the same name from `address_table` and return it back to the caller: + +1. Create a new action `getbylastname`, which takes as an input parameter a string which represents the last name to filter by the persons in the `address_table`. +2. Use the `range()` function of the `last_name_idx` index and pass as parameters the beginning and the end of the range to retrieve: + 1. The first parameter is a person object with its account name equal to `the minimum possible value for an account name` and its last name the same value to filter by the persons in the `address_table`. + 2. The second parameter is a person object with its account name equal to `the maximum possible value for an account name` and its last name the same value to filter by the persons in the `address_table`. +3. Return back to the caller the list of persons the `range()` function returns. + +Refer to the following reference implementation to implement an action to retrieve a list of persons with the same name from `address_table` and return it back to the caller: + +`smartcontract.hpp` + +```cpp +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + + index> last_name_idx { + name{"lastname"_n}, + &person::last_name}; + + address_table(name contract_name) { + init(contract_name, account_name_uidx) + } + }; + public: + using contract::contract; + + // retrieves list of persons with the same last name + [[eosio::action]] + std::vector getbylastname(string last_name); +); + using get_by_last_name_action = action_wrapper<"getbylastname"_n, &smrtcontract::getbylastname>; +}; +``` + +`smartcontract.cpp file` + +```cpp +// retrieves list of persons with the same last name +[[eosio::action]] +std::vector smrtcontract::getbylastname(string last_name) { + address_table addresses{"kvaddrbook"_n}; + + eosio::name min_account_name{0}; + eosio::name max_account_name{UINT_MAX}; + auto list_of_persons = addresses.last_name_idx.range( + {min_account_name, last_name}, + {max_account_name, last_name}); + // return found list of person from action + return list_of_persons; +} +``` + +## Summary + +In conclusion, the above instructions show how to retrieve a list of values, from a `Key-Value Table` (`kv table`) index, which share a particular commonality. + +## Next Steps + +The following options are available when you complete the procedure: + +* Access the list of objects returned by the `getbylastname` action. diff --git a/docs/06_how-to-guides/30_key-value-api/kv_table/90_how-to-allow-users-to-pay-kv-table.md b/docs/06_how-to-guides/30_key-value-api/kv_table/90_how-to-allow-users-to-pay-kv-table.md new file mode 100644 index 0000000000..bfe8d133f6 --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_table/90_how-to-allow-users-to-pay-kv-table.md @@ -0,0 +1,120 @@ +--- +content_title: How-To Create An Action Which Requires The User To Pay +link_text: "How-To Create An Action Which Requires The User To Pay" +--- + +## Overview + +This guide provides instructions which show you how to create an action which requires the user to pay for the resources needed to store data in a `Key-Value Table` (`kv table`). + +[[caution | Alpha version]] +| `Key-Value Table` is designated as `alpha` and should not be used in production code. + +Use the method `put` defined by the `eosio::kv::table` type to accomplish this task and provide as the second parameter the account name which is the payer for the resources needed. + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index) +* A smart contract named `smrtcontract` +* A user defined type named `person`, which defines the data stored in the table +* A `kv table` type which stores objects of type `person`, named `address_table`. The primary index of the `kv table` is defined based on the `person::account_name` property. + +Refer to the following reference implementation for your starting point: + +`smartcontract.hpp file` + +```cpp +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; + std::string personal_id; +}; + +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + + address_table(name contract_name) { + init(contract_name, account_name_uidx) + } + }; + public: + using contract::contract; +}; +``` + +## Procedure + +Complete the following steps to allow the `payer` account, to be the payer for the resources needed to store a person object in the `kv table`: + +1. Create a new action `upsert` in your smart contact class, which takes as input parameters an `account name, a first name, a last name, a personal id` which define a person data, and an `account name` for the payer. +2. In the `upsert` action access the instance of `address_table` belonging to this contract by declaring a local variable of `address_table` type and pass the contract name as paramter. +3. And then call the `put` method of the `address_table` and pass to it a newly created `person` object based on the action’s input parameters and the payer account name. + +Refer to the following reference implementation to allow a specific account name to be the payer for the resources needed to store a person object in the `kv table`: + +`smartcontract.hpp file` + +```cpp +class [[eosio::contract]] smrtcontract : public contract { + struct [[eosio::table]] address_table : eosio::kv::table { + + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + + address_table(name contract_name) { + init(contract_name, account_name_uidx) + } + }; + public: + using contract::contract; + + // creates if not exists, or updates if already exists, a person + [[eosio::action]] + void upsert(eosio::name account_name, + string first_name, + string last_name, + string country, + string personal_id, + eosio::name payer); + + using upsert_action = action_wrapper<"upsert"_n, &smrtcontract::upsert>; +}; +``` + +`smartcontract.cpp file` + +```cpp +// creates if not exists, or updates if already exists, a person +[[eosio::action]] +void smrtcontract::upsert( + eosio::name account_name, + string first_name, + string last_name, + string personal_id, + eosio::name payer) { + address_table addresses{"kvaddrbook"_n}; + + // upsert into kv table + addresses.put({account_name, first_name, last_name, personal_id}, payer); +} +``` + +## Summary + +In conclusion, the above instructions show how to create an action which requires the user to pay for the resources needed to store data in a `Key-Value Table` (`kv table`). + +## Next Steps + +The following options are available when you complete the procedure: + +* [Check](60_how-to-check-a-record-kv-table.md) if the newly inserted `person` actually exists in the table. To accomplish this task, use the `exists()` function of any index defined for the table. +* [Retrieve](70_how-to-find-in-kv-table.md) the newly inserted or updated `person` from the table. To accomplish this task, use the `find()` function of any index defined for the table. +* [Delete](40_how-to-delete-from-kv-table.md) the newly created or updated `person` from the table. To accomplish this task, use the `erase()` function of the `kv table`. diff --git a/docs/06_how-to-guides/30_key-value-api/kv_table/index.md b/docs/06_how-to-guides/30_key-value-api/kv_table/index.md new file mode 100644 index 0000000000..2c8dfcea36 --- /dev/null +++ b/docs/06_how-to-guides/30_key-value-api/kv_table/index.md @@ -0,0 +1,49 @@ +--- +content_title: Key-Value Table +link_text: "Key-Value Table" +--- + +#### KV Table + +A `datastore key value table on-chain`, or a `KV Table`, serves as a storage location which is organized as a table of rows where each row stores objects with the same definition. + +[[caution | Alpha version]] +| `Key-Value Table` is designated as `alpha` and should not be used in production code. + +The `object definition` consists of a list of `properties` and each property is stored on the corresponding row in the table. The properties of the objects are also referred to as `fields`. + +A `KV Table` requires one unique index, of any type, that can be serialized to a binary representation. + +A `KV Table` supports zero or more secondary indexes, of any type, that can be serialized to a binary representation. Indexes must be a data member or a function member. + +Two types of indexes can be defined, unique or non-unique. + +A unique index can be defined just for one property, and it will sort the objects stored in the `KV Table` based on the `specified property`. The unique index also ensures only one instance of an object is stored with a particular value for the `specified property`, and thus ensures the uniqueness of the property for which it is defined. + +A non-unique index can be defined for one or multiple properties, and it will sort the objects stored in the `KV Table` based on the `specified property` or the combination of the `specified properties`. Very important though, a non-unique index requires as the last property of its definition a property which has unique values. Therefore although the non-unique index is intended for just one property, its definition will have two properties specified: + +1. the first property, the one for which the non-unique index is built, +2. and the last property which must have unique values. + +The main operations provided by the KV API are the following: + +* Create an instance of `KV Table` +* Insert object in the `KV Table` instance +* Update object in the `KV Table` instance +* Delete object from the `KV Table` instance +* Search an object in the `KV Table` instance +* Verify if an object exists in the `KV Table` instance +* Retrieve a range of objects from the `KV Table` instance +* Iterate through the objects store in the `KV Table` instance + +Consult the [KV Table Reference](../../../classeosio_1_1kv_1_1table/#class-eosiokvtable) and the `KV Table How-To`s listed below for details. + +* [How To Use KV Table](./10_how-to-use-kv-table.md) +* [How To Create Indexes](./20_how-to-create-indexes-kv-table.md) +* [How To Upsert Into KV Table](./30_how-to-upsert-into-kv-table.md) +* [How To Delete From KV Table](./40_how-to-delete-from-kv-table.md) +* [How To Iterate](./50_how-to-iterate-kv-table.md) +* [How To Check A Record](./60_how-to-check-a-record-kv-table.md) +* [How To Find In KV Table](./70_how-to-find-in-kv-table.md) +* [How To Query Range In KV Table](./80_how-to-query-range-in-kv-table.md) +* [How To Allow Users To Pay](./90_how-to-allow-users-to-pay-kv-table.md) diff --git a/docs/06_how-to-guides/02_multi-index/how-to-define-a-primary-index.md b/docs/06_how-to-guides/40_multi-index/how-to-define-a-primary-index.md similarity index 100% rename from docs/06_how-to-guides/02_multi-index/how-to-define-a-primary-index.md rename to docs/06_how-to-guides/40_multi-index/how-to-define-a-primary-index.md diff --git a/docs/06_how-to-guides/02_multi-index/how-to-define-a-secondary-index.md b/docs/06_how-to-guides/40_multi-index/how-to-define-a-secondary-index.md similarity index 100% rename from docs/06_how-to-guides/02_multi-index/how-to-define-a-secondary-index.md rename to docs/06_how-to-guides/40_multi-index/how-to-define-a-secondary-index.md diff --git a/docs/06_how-to-guides/02_multi-index/how-to-define-a-singleton.md b/docs/06_how-to-guides/40_multi-index/how-to-define-a-singleton.md similarity index 79% rename from docs/06_how-to-guides/02_multi-index/how-to-define-a-singleton.md rename to docs/06_how-to-guides/40_multi-index/how-to-define-a-singleton.md index b20846a9bd..f8ee5b51a2 100644 --- a/docs/06_how-to-guides/02_multi-index/how-to-define-a-singleton.md +++ b/docs/06_how-to-guides/40_multi-index/how-to-define-a-singleton.md @@ -19,13 +19,13 @@ struct [[eosio::table]] testtable { }; ``` -3. For ease of use, define a type alias `singleton_type` based on the `eosio::singleton` template type, parametarized with a random name `"testsingletona"` and the `testtable` data structure defined above +3. For ease of use, define a type alias `singleton_type` based on the `eosio::singleton` template type, parametarized with a random name `"testtable"`, which has to respect the EOSIO account name restrictions, and the `testtable` data structure defined above ```diff struct [[eosio::table]] testtable { name primary_value; uint64_t secondary_value; }; -+using singleton_type = eosio::singleton<"testsingletona"_n, testtable>; ++using singleton_type = eosio::singleton<"testtable"_n, testtable>; ``` 4. Define the singleton table instance declared as a data member of type `singleton_type` defined in the privious step @@ -35,11 +35,11 @@ struct [[eosio::table]] testtable { uint64_t secondary_value; }; -using singleton_type = eosio::singleton<"testsingletona"_n, testtable>; +using singleton_type = eosio::singleton<"testtable"_n, testtable>; +singleton_type singleton_instance; ``` -5. Instantiate the data member `singleton_instance` by passing to its constructor the `receiver` and the `code` (in this case `receiver.value`) parameters; these two combined with "testsingletona" provide access to the partition of the RAM cache used by this singleton. In this example you will initialize the `singleton_instance` data member in the smart contract constructor, see below: +5. Instantiate the data member `singleton_instance` by passing to its constructor the `receiver` and the `code` (in this case `receiver.value`) parameters; these two combined with "testtable" provide access to the partition of the RAM cache used by this singleton. In this example you will initialize the `singleton_instance` data member in the smart contract constructor, see below: ```diff // singleton contract constructor singleton_example( name receiver, name code, datastream ds ) : @@ -63,17 +63,20 @@ class [[eosio::contract]] singleton_example : public contract { singleton_example( name receiver, name code, datastream ds ) : contract(receiver, code, ds), singleton_instance(receiver, receiver.value) - { } + {} - [[eosio::action]] void set( name user, uint64_t value ); - [[eosio::action]] void get( ); + [[eosio::action]] + void set( name user, uint64_t value ); + [[eosio::action]] + void get( ); struct [[eosio::table]] testtable { name primary_value; uint64_t secondary_value; - } tt; + uint64_t primary_key() const { return primary_value.value; } + } testtablerow; - using singleton_type = eosio::singleton<"testsingletona"_n, testtable>; + using singleton_type = eosio::singleton<"testtable"_n, testtable>; singleton_type singleton_instance; using set_action = action_wrapper<"set"_n, &singleton_example::set>; @@ -88,11 +91,7 @@ __singleton_example.cpp__ #include [[eosio::action]] void singleton_example::set( name user, uint64_t value ) { - if (!singleton_instance.exists()) - { - singleton_instance.get_or_create(user, tt); - } - auto entry_stored = singleton_instance.get(); + auto entry_stored = singleton_instance.get_or_create(user, testtablerow); entry_stored.primary_value = user; entry_stored.secondary_value = value; singleton_instance.set(entry_stored, user); @@ -111,6 +110,5 @@ __singleton_example.cpp__ } ``` - [[info | Full example location]] | A full example project demonstrating the instantiation and usage of singleton can be found [here](https://github.com/EOSIO/eosio.cdt/tree/master/examples/singleton_example). diff --git a/docs/06_how-to-guides/02_multi-index/how-to-delete-data-from-a-multi-index-table.md b/docs/06_how-to-guides/40_multi-index/how-to-delete-data-from-a-multi-index-table.md similarity index 100% rename from docs/06_how-to-guides/02_multi-index/how-to-delete-data-from-a-multi-index-table.md rename to docs/06_how-to-guides/40_multi-index/how-to-delete-data-from-a-multi-index-table.md diff --git a/docs/06_how-to-guides/02_multi-index/how-to-insert-data-into-a-multi-index-table.md b/docs/06_how-to-guides/40_multi-index/how-to-insert-data-into-a-multi-index-table.md similarity index 93% rename from docs/06_how-to-guides/02_multi-index/how-to-insert-data-into-a-multi-index-table.md rename to docs/06_how-to-guides/40_multi-index/how-to-insert-data-into-a-multi-index-table.md index 4a747e643e..37254edb51 100644 --- a/docs/06_how-to-guides/02_multi-index/how-to-insert-data-into-a-multi-index-table.md +++ b/docs/06_how-to-guides/40_multi-index/how-to-insert-data-into-a-multi-index-table.md @@ -7,7 +7,7 @@ content_title: How to insert data into a multi index table To insert data into a multi index table follow the following steps -1. Make use of the multi index table iterator to find out if the data doesn't already exist +1. Make use of the multi index table iterator to find out if the data doesn't already exist ```cpp [[eosio::action]] void multi_index_example::set( name user ) { // check if the user already exists @@ -33,4 +33,4 @@ To insert data into a multi index table follow the following steps ``` [[info | Full example location]] -| A full example project demonstrating the instantiation and usage of multi index table can be found [here](https://github.com/EOSIO/eosio.cdt/tree/master/examples/multi_index_example). +| A full example project demonstrating the instantiation and usage of multi index table can be found [here](https://github.com/EOSIO/eosio.cdt/tree/master/examples/multi_index_example). \ No newline at end of file diff --git a/docs/06_how-to-guides/02_multi-index/how-to-instantiate-a-multi-index-table.md b/docs/06_how-to-guides/40_multi-index/how-to-instantiate-a-multi-index-table.md similarity index 95% rename from docs/06_how-to-guides/02_multi-index/how-to-instantiate-a-multi-index-table.md rename to docs/06_how-to-guides/40_multi-index/how-to-instantiate-a-multi-index-table.md index 3a2120a6e0..742917f092 100644 --- a/docs/06_how-to-guides/02_multi-index/how-to-instantiate-a-multi-index-table.md +++ b/docs/06_how-to-guides/40_multi-index/how-to-instantiate-a-multi-index-table.md @@ -22,7 +22,7 @@ using namespace eosio; + uint64_t datum; }; ``` -4. Add definition of the primary index for the multi index table. The primary index type must be uint64_t, it must be unique and and it must be named `primary_key()`, if you don't have this the compiler (eosio-cpp) will generate an error saying it can't find the field to use as the primary key: +4. Add definition of the primary index for the multi index table. The primary index type must be uint64_t, it must be unique and it must be named `primary_key()`, if you don't have this the compiler (eosio-cpp) will generate an error saying it can't find the field to use as the primary key: ```diff // the data structure which defines each row of the table struct [[eosio::table]] test_table { @@ -53,7 +53,7 @@ using namespace eosio; + typedef eosio::multi_index<"testtaba"_n, test_table> test_tables; ``` -6. Define the multi index table data member of type `test_tables` defined in the privious step +6. Define the multi index table data member of type `test_tables` defined in the previous step ```diff // the data structure which defines each row of the table struct [[eosio::table]] test_table { @@ -130,4 +130,4 @@ class [[eosio::contract]] multi_index_example : public contract { ``` [[info | Full example location]] -| A full example project demonstrating the instantiation and usage of multi index table can be found [here](https://github.com/EOSIO/eosio.cdt/tree/master/examples/multi_index_example). \ No newline at end of file +| A full example project demonstrating the instantiation and usage of multi index table can be found [here](https://github.com/EOSIO/eosio.cdt/tree/master/examples/multi_index_example). diff --git a/docs/06_how-to-guides/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table-based-on-secondary-index.md b/docs/06_how-to-guides/40_multi-index/how-to-iterate-and-retrieve-a-multi_index-table-based-on-secondary-index.md similarity index 100% rename from docs/06_how-to-guides/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table-based-on-secondary-index.md rename to docs/06_how-to-guides/40_multi-index/how-to-iterate-and-retrieve-a-multi_index-table-based-on-secondary-index.md diff --git a/docs/06_how-to-guides/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table.md b/docs/06_how-to-guides/40_multi-index/how-to-iterate-and-retrieve-a-multi_index-table.md similarity index 100% rename from docs/06_how-to-guides/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table.md rename to docs/06_how-to-guides/40_multi-index/how-to-iterate-and-retrieve-a-multi_index-table.md diff --git a/docs/06_how-to-guides/02_multi-index/how-to-modify-data-in-a-multi-index-table.md b/docs/06_how-to-guides/40_multi-index/how-to-modify-data-in-a-multi-index-table.md similarity index 98% rename from docs/06_how-to-guides/02_multi-index/how-to-modify-data-in-a-multi-index-table.md rename to docs/06_how-to-guides/40_multi-index/how-to-modify-data-in-a-multi-index-table.md index 8450f39cfd..62a0250811 100644 --- a/docs/06_how-to-guides/02_multi-index/how-to-modify-data-in-a-multi-index-table.md +++ b/docs/06_how-to-guides/40_multi-index/how-to-modify-data-in-a-multi-index-table.md @@ -37,5 +37,5 @@ To modify data in the multi index table defined in the above tutorial, you will } ``` -[[info | Full example location] +[[info | Full example location]] | A full example project demonstrating the instantiation and usage of multi index table can be found [here](https://github.com/EOSIO/eosio.cdt/tree/master/examples/multi_index_example). diff --git a/docs/06_how-to-guides/50_how-to-create-and-use-action-wrappers.md b/docs/06_how-to-guides/50_how-to-create-and-use-action-wrappers.md new file mode 100644 index 0000000000..cce2bca91c --- /dev/null +++ b/docs/06_how-to-guides/50_how-to-create-and-use-action-wrappers.md @@ -0,0 +1,94 @@ +--- +content_title: How to create and use action wrappers +link_text: "How to create and use action wrappers" +--- + +## Overview + +This guide provides instructions on how to create and use an action wrapper in a smart contract. + +## Code Reference + +See the following code reference guide for action wrapper: + +* [eosio::action_wrapper](../structeosio_1_1action__wrapper). + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index). +* A smart contract named `multi_index_example`, defined in file `multi_index_example.hpp`. +* An action `mod` which modifies the integer value `n` stored for row with key `user`. + +Refer to the following reference implementation for your starting point: + +```cpp +class [[eosio::contract]] multi_index_example : public contract { + // ... + [[eosio::action]] void mod( name user, uint32_t n ); + // ... +} +``` + +## Procedure + +Complete the following steps to create and use `mod_action` action wrapper for the existing `mod` action in the smart contract: + +1. [Define the action wrapper](#1-define-the-action-wrapper) +2. [Use the action wrapper](#2-use-the-action-wrapper) + * [2.1. Include Header File](#21-include-header-file) + * [2.2. Instantiate The Action Wrapper](#22-instantiate-the-action-wrapper) + * [2.3. Send The Action Using The Action Wrapper](#23-send-the-action-using-the-action-wrapper) + +### 1. Define The Action Wrapper + +To define an action wrapper for the `mod` action, make use of the `eosio::action_wrapper` template, with the first parameter the action name as a `eosio::name` and second parameter as the reference to the action method: + +```diff +class [[eosio::contract]] multi_index_example : public contract { + // ... + [[eosio::action]] void mod(name user); + // ... ++ using mod_action = action_wrapper<"mod"_n, &multi_index_example::mod>; + // ... +} +``` + +### 2. Use The Action Wrapper + +#### 2.1. Include Header File + +To use the action wrapper, you have to include the header file where the action wrapper is defined: + +```cpp +#include +``` + +#### 2.2. Instantiate The Action Wrapper + +Instantiate the `mod_action` defined above, specifying the contract to send the action to as the first argument. In this case, it is assumed the contract is deployed to `multiindexex` account, and a structure which is defined by two parameters: the self account, obtained by `get_self()` call, and the `active` permission (you can modify these two parameters based on your requirements). + +```diff +#include + ++multi_index_example::mod_action modaction("multiindexex"_n, {get_self(), "active"_n}); +``` + +#### 2.3. Send The Action Using The Action Wrapper + +Call the `send` method of the action wrapper and pass in the `mod` action's parameters as positional arguments: + +```diff +#include + +multi_index_example::mod_action modaction("multiindexex"_n, {get_self(), 1}); + ++modaction.send("eostutorial"_n, 1); +``` + +For a full example see the [`multi_index` contract implementation](https://github.com/EOSIO/eosio.cdt/tree/master/examples/multi_index_example). + +## Summary + +In conclusion, the above instructions show how to create and use action wrapper in a smart contract. diff --git a/docs/06_how-to-guides/60_how-to-return-values-from-actions.md b/docs/06_how-to-guides/60_how-to-return-values-from-actions.md new file mode 100644 index 0000000000..6a5c60cf4a --- /dev/null +++ b/docs/06_how-to-guides/60_how-to-return-values-from-actions.md @@ -0,0 +1,82 @@ +--- +content_title: How-To Return Values From Action +--- + +## Overview + +This how-to demonstrates how a smart contract developer implements return values from an action. + +In order to accomplish this, use the `return` statement and pass the desired returned value, which can be of any C++ primitive type, a standard library type, or a user defined type. + +## Before you begin + +Make sure you have the following prerequisites in place: + +* An EOSIO development environment, for details consult the [Get Started Guide](https://developers.eos.io/welcome/latest/getting-started-guide/index). +* A smart contract, let’s call it `smrtcontract`, which builds without error. +* An action, let’s call it `checkwithrv`, from which you want to return a value of a user defined type `action_response`. + +Refer to the following reference implementation for your starting point: + +```cpp +struct action_response +{ + uint16_t id; + std::pair status; +}; + +class [[eosio::contract]] smrtcontract : public contract { + public: + using contract::contract; + + [[eosio::action]] + action_response checkwithrv( name nm ); +}; +``` + +## Procedure + +Complete the following steps to return an instance of `action_response` from the `checkwithrv` action: + +1. Create an instance of the `action_response` C++ user defined structure. +2. Initialize its data members based on the action’s business logic. +3. Use `return` statement and pass as a parameter the instance created and initialized in previous steps. + +```cpp +[[eosio::action]] +action_response smrtcontract::checkwithrv( name nm ) { + print_f("Name : %\n", nm); + + // create instance of the action response structure + action_response results; + + // initialize its data members + results.id = 1; + if (nm == "hello"_n) { + results.status = {0, "Validation has passed."}; + } + else { + results.status = {1, "Input param `name` not equal to `hello`."}; + } + + // use return statement + return results; // the `set_action_return_value` intrinsic is invoked automatically here +} +``` + +For a complete example of a smart contract that implements an action which returns a value see the [hello example smart contract](https://github.com/EOSIO/eosio.cdt/blob/develop/examples/hello/src/hello.cpp#L16). + +## Next Steps + +The following options are available when you complete the procedure: + +* Compile the smart contract and deploy it to the EOSIO testnet or any EOSIO based blockchain. +* Use the `cleos` command to send the `checkwithrv` action to the smart contract and observe the returned value in the `cleos` output. +* Use other means (e.g. programmatically) to send the `checkwithrv` action to the smart contract and observe the returned value in the action trace. + +[[info | Returned values from actions availability]] +The action return values are only available to clients sending the action via the RPC API. Currently, there is no support for an inline action to be able to use the return value, because inline actions don't execute synchronously. + +## Summary + +In conclusion, the above instructions show how to return values from actions. diff --git a/docs/08_troubleshooting.md b/docs/08_troubleshooting.md index b41d979018..e8da1593a9 100644 --- a/docs/08_troubleshooting.md +++ b/docs/08_troubleshooting.md @@ -3,7 +3,9 @@ content_title: Troubleshooting --- ## When sending an action to the blockchain you get the error below -```{ + +```console +{ "code":500, "message":"Internal Service Error", "error":{ @@ -21,76 +23,91 @@ content_title: Troubleshooting } } ``` + __Possible solution__: Verify if you did not forget to set code for contract, is it possible that you only set the `abi` for the contract but not the code as well? -## When sending an action to the blockchain an error similar to the one below is encountered: -```sh +## When sending an action to the blockchain an error similar to the one below is encountered + +```console Error 3015014: Pack data exception Error Details: Unexpected input encountered while processing struct 'action_name_here' ``` -__Possible solution__: You did not specify correctly the parameter when sending the action to the blockchain. When no parameter is needed the command should look like the one below: + +__Possible solution__: You did not specify correctly the parameter when sending the action to the blockchain. When no parameter is needed the command should look like the one below + ```sh cleos push action eostutorial1 get '[]' -p eostutorial1@active ``` + The command above is one way of sending correctly `get` action with no parameters to the blockchain. -## When sending an action to the blockchain an error similar to the one below is encountered: -```sh +## When sending an action to the blockchain an error similar to the one below is encountered + +```console error 2019-09-25T07:38:14.859 thread-0 main.cpp:3449 main ] Failed with error: Assert Exception (10) !action_type.empty(): Unknown action action_name in contract eostutorial1 ``` + __Possible solution__: Verify if the action attribute `[[eosio::action]]` is used when defining and/or declaring the action `action_name` for the contract. -## When deploying a contract code to the blockchain a similar error with the ones below is encountered: -```sh +## When deploying a contract code to the blockchain a similar error with the ones below is encountered + +```console Error 3160010: No abi file found or Error 3160009: No wasm file found ``` + __Possible solution__: Verify that `abi` and `wasm` files exist in the directory specified in the `cleos set contract` command, and that their names match the directory name. ## Action triggers ram charge which cannot be initiated from a notification. __Possible solution__: The reason for this error is because the notification action doesn't have authorization to buy the needed RAM. In the context of multi index tables, there’s a table payer and a row payer. Only the contract can modify rows. The contract can create rows with a payer that didn’t authorize the action if the total amount of ram charged that payer doesn’t increase (e.g. delete a row and add another with the same payer). The table payer can’t change until the last row is deleted. For the purposes of billing, a table is identified by the tuple `contract, scope, table`. When you create a row for a `contract, scope, table` tuple that doesn’t exist, you create a table with the same payer. This can outlive the original row which created it, if other rows were created with that combination and this prevents the original payer from getting their ram back. Secondary indexes throw in more complexity since they use the lower 4 bits of the table name, producing additional `contract, scope, table` tuples combinations. Key takeaway: payer is about billing, not access control -## You successfully re-deployed the contract code, but when you query the table you get the custom message that you coded when the table is not initialized (doesn't exist), or the system error message below in case you do not have code that checks first if table exist: -```sh +## You successfully re-deployed the contract code, but when you query the table you get the custom message that you coded when the table is not initialized (doesn't exist), or the system error message below in case you do not have code that checks first if table exist + +```console Error 3050003: eosio_assert_message assertion failure Error Details: assertion failure with message: singleton does not exist pending console output: ``` + __Possible solution__: It is possible that you changed the table name? That is the first, of `eosio::name` type, parameter which you passed to the `eosio::template` type alias definition. Or did you change the table structure definition at all? If you need to change the table structure definition there are some limitations and a couple of ways to do it which are explained in the [Data Design and Migration](./05_best-practices/04_data-design-and-migration.md) section. -## You successfully re-deployed the contract code, but when you query the table you get the fields of the row values swapped, that is, it appears the values stored in table rows are the same only that they are swapped between fields/columns. +## You successfully re-deployed the contract code, but when you query the table you get the fields of the row values swapped, that is, it appears the values stored in table rows are the same only that they are swapped between fields/columns __Possible solution__: It is possible that you changed the order of the fields the table struct definition? If you change the order of the table struct definition, if the swapped fields have the same type you will see the data in the fields correctly, however if the types of the fields are different the results could be of something undefined. If you need to change the table structure definition there are some limitations and a couple of ways to do it which are explained in the [Data Design and Migration](./05_best-practices/04_data-design-and-migration.md) section. -## You successfully re-deployed the contract code, but when you query the table you get a parse error, like the one below, or the returned data seems to be garbage. -```sh +## You successfully re-deployed the contract code, but when you query the table you get a parse error, like the one below, or the returned data seems to be garbage + +```console error 2019-09-26T07:05:54.825 thread-0 main.cpp:3449 main ] Failed with error: Parse Error (4) Couldn't parse type_name ``` + __Possible solution__: It is possible that you changed the type of the fields for the table struct definition? If you need to change the table structure definition there are some limitations and a couple of ways to do it which are explained in the [Data Design and Migration](./05_best-practices/04_data-design-and-migration.md) section. -## eosio-cpp process never completes. +## eosio-cpp process never completes __Possible solution__: make sure you have at least 2 cores on the host that executes the eosio-cpp (e.g. docker container, VM, local sub-system) -## You can not find the `now()` time function, or the result of the `current_time_point` functions are not what you expected them to be. +## You can not find the `now()` time function, or the result of the `current_time_point` functions are not what you expected them to be __Possible solution__: The `now()` function has been replaced by `current_time_point().sec_since_epoch()`, it returns the time in microseconds from 1970 of the `current block` as a time_point. There's also available `current_block_time()` which returns the time in microseconds from 1970 of the `current block` as a `block_timestamp`. Be aware that for time base functions, the assumption is when you call something like `now()` or `current_time()` you will get the exact now/current time, however that is not the case with EOSIO, you get __the block time__, and only ever get __the block time__ from the available `sec_since_epoch()` or `current_block_time()` no matter how many times you call it. -## You successfully re-deployed the contract code, but when you broadcast one of the contracts methods to the blockchain you get below error message: -```sh +## You successfully re-deployed the contract code, but when you broadcast one of the contracts methods to the blockchain you get below error message + +```console Error 3050004: eosio_assert_code assertion failure Error Details: assertion failure with error code: 8000000000000000000 ``` + __Possible solution__: If you are referencing a smart contract from another smart contract and each of them have at least one action with the same name you will experience the above error when sending to the blockchain one of those actions, so what you have to do is to make sure the action names between those two contracts are not common. -## Print statements from smart contract code are not seen in the output. +## Print statements from smart contract code are not seen in the output __Possible solution__: There are a few reasons print statements do not show up in the output. One reason could be because an error occurs, in which case the whole transaction is rolled back and the print statements output is replaced by the error that occurs instead; Another reason is if you are in a loop, iterating through a table's rows for example and for each row you have a print statement that prints also the new line char at the `'\n'` only the chars before the new line char from the first iteration will be printed, nothing else after that, nothing from the second iteration onwards either. @@ -105,6 +122,7 @@ The below code will print just the first line of the iteration. ``` The below code will print all lines of the iteration separated by `'|'` char. + ```cpp auto index=0; for (auto& item : testtab) @@ -113,7 +131,7 @@ The below code will print all lines of the iteration separated by `'|'` char. } ``` -## Print statements from smart contract code are not shown in the `expected order`. +## Print statements from smart contract code are not shown in the `expected order` __Possible solution__: The key point here is the `expected order` and what you think it should be. Although the EOSIO is single threaded, when looking at your smart contract action code implementation, which let's say it has a series of `print` (either `print_f` or `printf`) statements, they might not necessarily be outputted in the order the `apparent` code workflow is. One example is when inline transactions are sent from your smart contract action code, and you expect to see the `print` statements from within the inline action code outputted before the `print` statements made after the inline action `send` statement. For better exemplification let's look at the code below: @@ -131,4 +149,19 @@ __Possible solution__: The key point here is the `expected order` and what you t } ``` -The code above has one `print` statement before the `singleton_set.send` and another one after the `singleton_set.send`. If you wrote some more `print` statements in the code that implements the `singleton_set.send` action and expect to see them before the second `print` statement then it is a wrong assumption. The inline actions are broadcasted to the network and they are executed at a different time, asynchronous of the current execution thread of the current `multi_index_example::mod` action, therefor it is impossible to predict when the `print` statements from inline action code will be outputted. \ No newline at end of file +The code above has one `print` statement before the `singleton_set.send` and another one after the `singleton_set.send`. If you wrote some more `print` statements in the code that implements the `singleton_set.send` action and expect to see them before the second `print` statement then it is a wrong assumption. The inline actions are broadcasted to the network and they are executed at a different time, asynchronous of the current execution thread of the current `multi_index_example::mod` action, therefor it is impossible to predict when the `print` statements from inline action code will be outputted. + +## Assertion failure while creating an account after eosio.system was installed + +```sh +cleos create account eosio bob EOS5HUanbay86UUnr1d4fuBsQ3ksjfgZYoLUVvrYVLy6pj4i8xqVY +``` + +```console +Error 3050003: eosio_assert_message assertion failure +Error Details: +assertion failure with message: system contract must first be initialized +``` + +The failure is stating that `eosio.system` `init` action was not called yet. The `init` action is implemented by the `void init(uint64_t, symbol)` function. The first parameter is the version, this should always be `0` for now, until a new version of `init` will be created that handles more information. +The second parameter is the system's symbol (i.e. for main net this is `EOS`). If you followed the [BIOS Boot Sequence](https://developers.eos.io/welcome/latest/tutorials/bios-boot-sequence) tutorial and created a system with the default symbol `SYS` then `SYS` shall be used as the system's symbol in the `init` action. It is whatever symbol you as the chain creator want to use in your `EOSIO` based blockchain. diff --git a/docs/09_tutorials/02_abi-variants.md b/docs/09_tutorials/02_abi-variants.md index e20f4bb461..2d286c4d39 100644 --- a/docs/09_tutorials/02_abi-variants.md +++ b/docs/09_tutorials/02_abi-variants.md @@ -1,5 +1,6 @@ --- content_title: ABI variants +link_text: ABI variants --- ABI variants give the flexibility of using more than one type for a defined variable or data member. @@ -105,7 +106,7 @@ Now you can deploy the contract and it will be backwards compatible with the pre ## Use variant when changing an already deployed multi index table ### Preconditions -- It is assumed you deployed the contract defined in [this section](../06_how-to-guides/02_multi-index/how-to-instantiate-a-multi-index-table.md) and now you are going to change its table structure. +- It is assumed you deployed the contract defined in [this section](../06_how-to-guides/40_multi-index/how-to-instantiate-a-multi-index-table.md) and now you are going to change its table structure. To change the existing table structure, you will use the `std::variant` in conjunction with ABI extensions; you can read a tutorial on abi extensions [here](./01_binary-extension.md). You will add another field to the table called `variant_field` which can store either of the following data `int8_t`, `int16_t`, and `int32_t`. You can do it by adding below data member to the table structure: @@ -153,7 +154,7 @@ class [[eosio::contract]] multi_index_example : public contract { ``` [[warning | Not recommended warning]] -| Be aware, it is not recommend to use `eosio::binary_extension` inside variant definition, this can lead to data corruption unless one is very careful in understanding how these two templates work and how to ABI gets generated! +| Be aware, it is not recommend to use `eosio::binary_extension` inside variant definition, this can lead to data corruption unless one is very careful in understanding how these two templates work and how the ABI gets generated! -[[info | Implemenatation location]] -| The implementation for ABI `variants' section can be found [here](https://github.com/EOSIO/eos/pull/5652). \ No newline at end of file +[[info | Implementation location]] +| The implementation for ABI `variants` can be found [here](https://github.com/EOSIO/eos/pull/5652). diff --git a/docs/09_tutorials/03_create-an-abi-file.md b/docs/09_tutorials/03_create-an-abi-file.md new file mode 100644 index 0000000000..42537a1ebc --- /dev/null +++ b/docs/09_tutorials/03_create-an-abi-file.md @@ -0,0 +1,786 @@ +--- +content_title: Create an ABI File +--- + +## Overview + +This tutorial provides instructions on how to create an ABI file. + +[[warning]] +| As of v1.2.0, the eosio.wasmsdk was decoupled from the core repository. This change has introduced an eosio-cpp regression where the legacy eosio-abigen is no longer bundled with eosio-cpp. Until a new ABI generator is introduced, you will need to hand-write your ABI files. + +## Introduction + +The Application Binary Interface (ABI) is a JSON-based description on how to convert user actions between their JSON and Binary representations. The ABI also describes how to convert the database state to/from JSON. Once you have described your contract via an ABI then developers and users will be able to interact with your contract seamlessly via JSON. + +This tutorial will use the [eosio.token](https://github.com/EOSIO/eosio.contracts/tree/master/eosio.token) contract as an example. *eosio.token contract does not cover every possible permutation of an ABI definition. + +To make things easy, we will start with an empty ABI. + +```text + +{ + "version": "eosio::abi/1.0", + "types": [], + "structs": [], + "actions": [], + "tables": [], + "ricardian_clauses": [], + "abi_extensions": [], + "___comment" : "" +} +``` + +## Types + +An ABI enables any client or interface to interpret and even generate an GUI for you contract. For this to work in a consistent manner, we'll need to describe the custom types that are used as a parameter in any public action or struct that we would like to describe in the ABI. + +[[info]] +|Built-in Types + +EOSIO implements a number of custom built-ins. Built-in types don't need to be described in an ABI file. If you would like to familiarize yourself with EOSIO's built-ins, they are defined [here](https://github.com/EOSIO/eos/blob/master/libraries/chain/abi_serializer.cpp#L65-L103). + +Using **eosio.token** as an example, the only type that requires a description in the ABI file is `account_name`. The ABI uses "new_type_name" to describe explicit types, in this case `account_name`, and `account_name` is an alias of `name` type. + +So in the ABI file we'll add the following object + +```json +{ + "new_type_name": "account_name", + "type": "name" + } +``` + +Our ABI now looks like this: + +```json +{ + "version": "eosio::abi/1.0", + "types": [{ + "new_type_name": "account_name", + "type": "name" + }], + "structs": [], + "actions": [], + "tables": [], + "ricardian_clauses": [], + "abi_extensions": [] +} +``` + +## Structs + +We now need to describe the structs of the token contract. By looking at eosio.token.hpp, we can quickly determine which structs are utilized by public actions. This is particularly important for when we describe our actions in the the ABI file in the next step. + +A struct's object definition in JSON looks like the following: + +```json +{ + "name": "issue", //The name + "base": "", //Inheritance, parent struct + "fields": [] //Array of field objects describing the struct's fields. + } +``` + +```json +{ + "name":"", // The field's name + "type":"" // The field's type +} +``` + +Looking through the `eosio.token` contract, we see a number of structs that require definition. Please note, not all of the structs are explicitly defined, some correspond to an actions' parameters. Here's a list of structs that require an ABI description for the `eosio.token` contract: + +## Implicit Structs + +The following structs are implicit in that a struct was never explicitly defined in the contract. Looking at the [create](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L24) action, you'll find two parameters, `issuer` of type `account_name ` and `maximum_supply` of type `asset`. For brevity this tutorial won't break down every struct, but applying the same logic, you will end up with the following: + +### [create](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L24) + +```json +{ + "name": "create", + "base": "", + "fields": [ + { + "name":"issuer", + "type":"account_name" + }, + { + "name":"maximum_supply", + "type":"asset" + } + ] +} +``` + +### [issue](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L27) + +```json +{ + "name": "issue", + "base": "", + "fields": [ + { + "name":"to", + "type":"account_name" + }, + { + "name":"quantity", + "type":"asset" + }, + { + "name":"memo", + "type":"string" + } + ] +} +``` + +### [retire](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L29) + +```json +{ + "name": "retire", + "base": "", + "fields": [ + { + "name":"quantity", + "type":"asset" + }, + { + "name":"memo", + "type":"string" + } + ] +} +``` + +### [transfer](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L31-L34) + +```json +{ + "name": "transfer", + "base": "", + "fields": [ + { + "name":"from", + "type":"account_name" + }, + { + "name":"to", + "type":"account_name" + }, + { + "name":"quantity", + "type":"asset" + }, + { + "name":"memo", + "type":"string" + } + ] +} +``` + +### [close](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L36) + +```json +{ + "name": "close", + "base": "", + "fields": [ + { + "name":"owner", + "type":"account_name" + }, + { + "name":"symbol", + "type":"symbol" + } + ] + } +``` + +## Explicit Structs +These structs are explicitly defined, as they are a requirement to instantiate a multi-index table. Describing them is no different than defining the implicit structs as demonstrated above. + +### [account](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L43-L47) + +```json +{ + "name": "account", + "base": "", + "fields": [ + { + "name":"balance", + "type":"asset" + } + ] +} +``` + +### [currency_stats](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L49-L55) + +```json +{ + "name": "currency_stats", + "base": "", + "fields": [ + { + "name":"supply", + "type":"asset" + }, + { + "name":"max_supply", + "type":"asset" + }, + { + "name":"issuer", + "type":"account_name" + } + ] +} +``` + +## Actions + +An action's JSON object definition looks like the following: + +```json +{ + "name": "transfer", //The name of the action as defined in the contract + "type": "transfer", //The name of the implicit struct as described in the ABI + "ricardian_contract": "" //An optional ricardian clause to associate to this action describing its intended functionality. +} +``` + +Next, we'll describe the actions of the `eosio.token` contract by aggregating all the public functions describe in the `eosio.token` contract's [header file](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L24-L36). We'll then describe each action's *type* to their previously described struct. In most situations, the function name and the struct name will be equal, but are not required to be equal. + +Below is a list of actions that link to their source code with example JSON provided for how each action would be described. + +## [create](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L24-L25) + +```json +{ + "name": "create", + "type": "create", + "ricardian_contract": "" +} +``` + +## [issue](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L27) + +```json +{ + "name": "issue", + "type": "issue", + "ricardian_contract": "" +} +``` + +## [retire](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L31-L34) + +```json +{ + "name": "retire", + "type": "retire", + "ricardian_contract": "" +} +``` + +## [transfer](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L31-L34) + +```json +{ + "name": "transfer", + "type": "transfer", + "ricardian_contract": "" +} +``` + +## [close](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L36) + +```json +{ + "name": "close", + "type": "close", + "ricardian_contract": "" +} +``` + +You will notice we previously described all of these in the "structs" array of the ABI definition. + +## Tables + +Finally, we need to describe our tables. Here's a table's JSON object definition: + +```json +{ + "name": "", // The name of the table, determined during instantiation. + "type": "", // The table's corresponding struct + "index_type": "", // The type of primary index of this table + "key_names" : [], // An array of key names, length must equal length of key_types member + "key_types" : [] // An array of key types that correspond to key names array member, length of array must equal length of key names array. +} +``` + +The eosio.token contract instantiates two tables, [accounts](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L57) and [stat](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L58). + +The accounts table is an i64 index, based on the [`account` struct](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L43-L47), has a [`uint64` as it's primary key](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L46) and it's key been arbitrarily named "currency". + +Here's how the accounts table would be described in the ABI + +```text +{ + "name": "accounts", + "type": "account", // Corresponds to previously defined struct + "index_type": "i64", + "key_names" : ["currency"], + "key_types" : ["uint64"] +} +``` + +The stat table is an i64 index, based on the [`currenct_stats` struct](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L49-L55), has a [`uint64` as it's primary key](https://github.com/EOSIO/eosio.contracts/blob/master/eosio.token/include/eosio.token/eosio.token.hpp#L54) and it's key been arbitrarily named "currency" + +Here's how the stat table would be described in the ABI + +```text +{ + "name": "stat", + "type": "currency_stats", + "index_type": "i64", + "key_names" : ["currency"], + "key_types" : ["uint64"] +} +``` + +You'll notice the above tables have the same "key name." Naming your keys similar names is symbolic in that it can potentially suggest a subjective relationship. As with this implementation, implying that any given value can be used to query different tables. + +## Putting it all Together + +Finally, once all the pieces are strewn together, we have ourselves a ABI file that accurately describes the `eosio.token` contract. + +```json +{ + "version": "eosio::abi/1.0", + "types": [ + { + "new_type_name": "account_name", + "type": "name" + } + ], + "structs": [ + { + "name": "create", + "base": "", + "fields": [ + { + "name":"issuer", + "type":"account_name" + }, + { + "name":"maximum_supply", + "type":"asset" + } + ] + }, + { + "name": "issue", + "base": "", + "fields": [ + { + "name":"to", + "type":"account_name" + }, + { + "name":"quantity", + "type":"asset" + }, + { + "name":"memo", + "type":"string" + } + ] + }, + { + "name": "retire", + "base": "", + "fields": [ + { + "name":"quantity", + "type":"asset" + }, + { + "name":"memo", + "type":"string" + } + ] + }, + { + "name": "close", + "base": "", + "fields": [ + { + "name":"owner", + "type":"account_name" + }, + { + "name":"symbol", + "type":"symbol" + } + ] + }, + { + "name": "transfer", + "base": "", + "fields": [ + { + "name":"from", + "type":"account_name" + }, + { + "name":"to", + "type":"account_name" + }, + { + "name":"quantity", + "type":"asset" + }, + { + "name":"memo", + "type":"string" + } + ] + }, + { + "name": "account", + "base": "", + "fields": [ + { + "name":"balance", + "type":"asset" + } + ] + }, + { + "name": "currency_stats", + "base": "", + "fields": [ + { + "name":"supply", + "type":"asset" + }, + { + "name":"max_supply", + "type":"asset" + }, + { + "name":"issuer", + "type":"account_name" + } + ] + } + ], + "actions": [ + { + "name": "transfer", + "type": "transfer", + "ricardian_contract": "" + }, + { + "name": "issue", + "type": "issue", + "ricardian_contract": "" + }, + { + "name": "retire", + "type": "retire", + "ricardian_contract": "" + }, + { + "name": "create", + "type": "create", + "ricardian_contract": "" + }, + { + "name": "close", + "type": "close", + "ricardian_contract": "" + } + ], + "tables": [ + { + "name": "accounts", + "type": "account", + "index_type": "i64", + "key_names" : ["currency"], + "key_types" : ["uint64"] + }, + { + "name": "stat", + "type": "currency_stats", + "index_type": "i64", + "key_names" : ["currency"], + "key_types" : ["uint64"] + } + ], + "ricardian_clauses": [], + "abi_extensions": [] +} +``` + +## Cases not Covered by Token Contract + +### Vectors + +When describing a vector in your ABI file, simply append the type with `[]`, so if you need to describe a vector of permission levels, you would describe it like so: `permission_level[]` + +### Struct Base + +It's a rarely used property worth mentioning. You can use **base** ABI struct property to reference another struct for inheritance, as long as that struct is also described in the same ABI file. Base will do nothing or potentially throw an error if your smart contract logic does not support inheritance. + +You can see an example of base in use in the system contract [source code](https://github.com/EOSIO/eosio.contracts/blob/4e4a3ca86d5d3482dfac85182e69f33c49e62fa9/eosio.system/include/eosio.system/eosio.system.hpp#L46) and [ABI](https://github.com/EOSIO/eosio.contracts/blob/4e4a3ca86d5d3482dfac85182e69f33c49e62fa9/eosio.system/abi/eosio.system.abi#L262) + +### Key-Value Table + +The `EOSIO` version 2.1 introduced the `Key-Value API` which provides a set of classes to facilitate the on-chain storage in form of `Key-Value Tables`. + +A new “kv_tables” object is defined which contains objects corresponding to each `Key-Value Table` ('KV-Table'). The format of that object is below where “name” is an `eosio::name`, and `"type"` corresponds to a `"struct"` definition similar to `multi_index`. The `"primary_index"` is an object representing the primary index and secondary_indices is an object that represents 0 or more secondary indices. + +```json +"kv_tables": { + "tablea": { + "type": "", + "primary_index": {}, + "secondary_indices": {} + } +} +``` + +The primary index object stores both the name and the type of the index. The secondary indices store objects whose key is the `name`, and whose `value` currently contains just the type. This format is chosen for ease of extensibility in the future. + +```json +... + "primary_index": { + "name": "bcd", + "type": "" + }, + "secondary_indices": { + "a1": { + "type": "" + }, + "b2": { + ... + } +... +} +``` + +A full sample ABI is depicted below: + +```json +"kv_tables": { + "testtable": { + "type": "my_struct", + "primary_index": { + "name": "myindex", + "type": "name" + }, + "secondary_indices": { + "foo": { + "type": "string" + }, + "bar": { + "type": "uint64" + }, + "nonuniqname": { + "type": "tuple_string_bool" + }, + "age": { + "type": "uint32" + }, + "tst": { + "type": "int32[]" + } + } + } +} +``` + +Refer below for the full `KV Table ABI`. + +```json +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.2", + "types": [], + "structs": [ + { + "name": "get", + "base": "", + "fields": [] + }, + { + "name": "iteration", + "base": "", + "fields": [] + }, + { + "name": "my_struct", + "base": "", + "fields": [ + { + "name": "primary_key", + "type": "name" + }, + { + "name": "foo", + "type": "string" + }, + { + "name": "bar", + "type": "uint64" + }, + { + "name": "fullname", + "type": "string" + }, + { + "name": "age", + "type": "uint32" + }, + { + "name": "non_unique_name", + "type": "tuple_string_uint32" + } + ] + }, + { + "name": "nonunique", + "base": "", + "fields": [] + }, + { + "name": "setup", + "base": "", + "fields": [] + }, + { + "name": "tuple_string_uint32", + "base": "", + "fields": [ + { + "name": "field_0", + "type": "string" + }, + { + "name": "field_1", + "type": "uint32" + } + ] + }, + { + "name": "update", + "base": "", + "fields": [] + }, + { + "name": "updateerr1", + "base": "", + "fields": [] + }, + { + "name": "updateerr2", + "base": "", + "fields": [] + } + ], + "actions": [ + { + "name": "get", + "type": "get", + "ricardian_contract": "" + }, + { + "name": "iteration", + "type": "iteration", + "ricardian_contract": "" + }, + { + "name": "nonunique", + "type": "nonunique", + "ricardian_contract": "" + }, + { + "name": "setup", + "type": "setup", + "ricardian_contract": "" + }, + { + "name": "update", + "type": "update", + "ricardian_contract": "" + }, + { + "name": "updateerr1", + "type": "updateerr1", + "ricardian_contract": "" + }, + { + "name": "updateerr2", + "type": "updateerr2", + "ricardian_contract": "" + } + ], + "tables": [], + "kv_tables": { + "testtable": { + "type": "my_struct", + "primary_index": { + "name": "primarykey", + "type": "name" + }, + "secondary_indices": { + "foo": { + "type": "string" + }, + "bar": { + "type": "uint64" + }, + "nonuniqname": { + "type": "tuple_string_uint32" + }, + "age": { + "type": "uint32" + } + } + } + }, + "ricardian_clauses": [], + "variants": [], + "action_results": [] +} +``` + +## ABI Properties Not Covered Here + +A few properties of the ABI specification were skipped here for brevity, however, there is a pending ABI specification that will outline every property of the ABI in its entirety. + +## Ricardian Clauses + +Ricardian clauses describe the intended outcome of a particular actions. It may also be utilized to establish terms between the sender and the contract. + +## ABI Extensions + +A generic "future proofing" layer that allows old clients to skip the parsing of "chunks" of extension data. For now, this property is unused. In the future each extension would have its own "chunk" in that vector so that older clients skip it and newer clients that understand how to interpret it. + +## Maintenance + +Every time you change a struct, add a table, add an action or add parameters to an action, use a new type, you will need to remember to update your ABI file. In many cases failure to update your ABI file will not produce any error. + +## Troubleshooting + +### Table returns no rows + +Check that your table is accurately described in the <> file. For example, If you use `cleos` to add a table on a contract with a malformed <> definition and then get rows from that table, you will recieve an empty result. `cleos` will not produce an error when adding a row nor reading a row when a contract has failed to properly describe its tables in its <> File. diff --git a/docs/09_tutorials/index.md b/docs/09_tutorials/index.md new file mode 100644 index 0000000000..95c7f23191 --- /dev/null +++ b/docs/09_tutorials/index.md @@ -0,0 +1,8 @@ +--- +content_title: Tutorials +link_text: Tutorials +--- + +- [Binary Extension](01_binary-extension.md) +- [ABI Variants](02_abi-variants.md) +- [Create An ABI File](03_create-an-abi-file.md) diff --git a/docs/index.md b/docs/index.md index 87b023e2e3..57d2dcbd1d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,24 +1,24 @@ # EOSIO.CDT (Contract Development Toolkit) -## Version : 1.7.0 - EOSIO.CDT is a toolchain for WebAssembly (WASM) and set of tools to facilitate smart contract development for the EOSIO platform. In addition to being a general purpose WebAssembly toolchain, [EOSIO](https://github.com/eosio/eos) specific optimizations are available to support building EOSIO smart contracts. This new toolchain is built around [Clang 7](https://github.com/eosio/llvm), which means that EOSIO.CDT has the most currently available optimizations and analyses from LLVM, but as the WASM target is still considered experimental, some optimizations are incomplete or not available. ## New Introductions -As of this release two new repositories are under the suite of tools provided by **EOSIO.CDT**. These are the [Ricardian Template Toolkit](https://github.com/eosio/ricardian-template-toolkit) and the [Ricardian Specification](https://github.com/eosio/ricardian-spec). The **Ricardian Template Toolkit** is a set of libraries to assist smart contract developers in craftinng their Ricardian contracts. The Ricardian specification is the working specification for the above mentioned toolkit. Please note that both projects are **alpha** releases and are subject to change. + +As of this release two new repositories are under the suite of tools provided by **EOSIO.CDT**. These are the [Ricardian Template Toolkit](https://github.com/eosio/ricardian-template-toolkit) and the [Ricardian Specification](https://github.com/eosio/ricardian-spec). The **Ricardian Template Toolkit** is a set of libraries to facilitate smart contract writers in crafting their Ricardian contracts. The Ricardian specification is the working specification for the above mentioned toolkit. Please note that both projects are **alpha** releases and are subject to change. ## Upgrading -There's been a round of breaking changes, if you are upgrading please read the [Upgrade guide from 1.2 to 1.3](./04_upgrading/1.2-to-1.3.md) and [Upgrade guide from 1.5 to 1.6](./04_upgrading/1.5-to-1.6.md). + +There's been a round of braking changes, if you are upgrading from older version please read the [Upgrade guide from 1.2 to 1.3](./04_upgrading/1.2-to-1.3.md) and [Upgrade guide from 1.5 to 1.6](./04_upgrading/1.5-to-1.6.md). ## Contributing -[Contributing Guide](../CONTRIBUTING.md) +[Contributing Guide](https://github.com/EOSIO/eosio.cdt/blob/develop/CONTRIBUTING.md) -[Code of Conduct](../CONTRIBUTING.md#conduct) +[Code of Conduct](https://github.com/EOSIO/eosio.cdt/blob/develop/CONTRIBUTING.md#conduct) ## License -[MIT](../LICENSE) +[MIT](https://github.com/EOSIO/eosio.cdt/blob/develop/LICENSE) ## Important diff --git a/eosio_llvm b/eosio_llvm index 3c026f8b78..423a4ac7a7 160000 --- a/eosio_llvm +++ b/eosio_llvm @@ -1 +1 @@ -Subproject commit 3c026f8b7813ad693749d3551353a554c5ca33e7 +Subproject commit 423a4ac7a7074e139c4f4c2f786d6b71d1b0965e diff --git a/examples/hello/README.txt b/examples/hello/README.txt index 069019e07e..62876f1852 100644 --- a/examples/hello/README.txt +++ b/examples/hello/README.txt @@ -1,7 +1,6 @@ --- hello Project --- -- How to Build with CMake and Make -- - - mkdir build - cd into the 'build' directory - run the command 'cmake ..' - run the command 'make' @@ -13,7 +12,6 @@ - Additions to cmake should be done to the CMakeLists.txt in the './src' directory and not in the top level CMakeLists.txt -- How to build with eosio-cpp -- - - mkdir build - cd into the 'build' directory - run the command 'eosio-cpp -abigen ../src/hello.cpp -o hello.wasm -I ../include/' diff --git a/examples/hello/include/hello.hpp b/examples/hello/include/hello.hpp index 3019d18a30..c8992b3a88 100644 --- a/examples/hello/include/hello.hpp +++ b/examples/hello/include/hello.hpp @@ -9,7 +9,10 @@ class [[eosio::contract]] hello : public contract { void hi( name nm ); [[eosio::action]] void check( name nm ); + [[eosio::action]] + std::pair checkwithrv( name nm ); using hi_action = action_wrapper<"hi"_n, &hello::hi>; using check_action = action_wrapper<"check"_n, &hello::check>; + using checkwithrv_action = action_wrapper<"checkwithrv"_n, &hello::checkwithrv>; }; diff --git a/examples/hello/src/hello.cpp b/examples/hello/src/hello.cpp index 8c858132d7..cc9821cc64 100644 --- a/examples/hello/src/hello.cpp +++ b/examples/hello/src/hello.cpp @@ -1,4 +1,5 @@ #include + [[eosio::action]] void hello::hi( name nm ) { print_f("Name : %\n", nm); @@ -9,3 +10,18 @@ void hello::check( name nm ) { print_f("Name : %\n", nm); eosio::check(nm == "hello"_n, "check name not equal to `hello`"); } + +// Checks the input param `nm` and returns serialized std::pair instance. +[[eosio::action]] +std::pair hello::checkwithrv( name nm ) { + print_f("Name : %\n", nm); + + std::pair results = {0, "NOP"}; + if (nm == "hello"_n) { + results = {0, "Validation has passed."}; + } + else { + results = {1, "Input param `name` not equal to `hello`."}; + } + return results; // the `set_action_return_value` intrinsic is invoked automatically here +} diff --git a/examples/kv_addr_book/CMakeLists.txt b/examples/kv_addr_book/CMakeLists.txt new file mode 100644 index 0000000000..6d56214253 --- /dev/null +++ b/examples/kv_addr_book/CMakeLists.txt @@ -0,0 +1,17 @@ +include(ExternalProject) +# if no cdt root is given use default path +if(EOSIO_CDT_ROOT STREQUAL "" OR NOT EOSIO_CDT_ROOT) + find_package(eosio.cdt) +endif() + +ExternalProject_Add( + kv_addr_book_project + SOURCE_DIR ${CMAKE_SOURCE_DIR}/src + BINARY_DIR ${CMAKE_BINARY_DIR}/kv_addr_book + CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${EOSIO_CDT_ROOT}/lib/cmake/eosio.cdt/EosioWasmToolchain.cmake + UPDATE_COMMAND "" + PATCH_COMMAND "" + TEST_COMMAND "" + INSTALL_COMMAND "" + BUILD_ALWAYS 1 +) \ No newline at end of file diff --git a/examples/kv_addr_book/README.txt b/examples/kv_addr_book/README.txt new file mode 100644 index 0000000000..86f52df49f --- /dev/null +++ b/examples/kv_addr_book/README.txt @@ -0,0 +1,20 @@ +--- kv_addr_book Project --- + + -- How to Build with CMake and Make -- + - cd into the 'build' directory + - run the command 'cmake ..' + - run the command 'make' + + - After build - + - The built smart contract is under the 'kv_addr_book' directory in the 'build' directory + - You can then do a 'set contract' action with 'cleos' and point to the './build/kv_addr_book' directory + + - Additions to CMake should be done to the CMakeLists.txt in the './src' directory and not in the top level CMakeLists.txt + + -- How to build with eosio-cpp -- + - cd into the 'build' directory + - run the command 'eosio-cpp -abigen ../src/kv_addr_book.cpp -o kv_addr_book.wasm -I ../include/' + + - After build - + - The built smart contract is in the 'build' directory + - You can then do a 'set contract' action with 'cleos' and point to the 'build' directory diff --git a/examples/kv_addr_book/include/kv_addr_book.hpp b/examples/kv_addr_book/include/kv_addr_book.hpp new file mode 100644 index 0000000000..d032a11879 --- /dev/null +++ b/examples/kv_addr_book/include/kv_addr_book.hpp @@ -0,0 +1,160 @@ +#include +#include + + +using namespace eosio; +using namespace std; + +using fullname_t = std::tuple; +using address_t = std::tuple; +using country_personal_id_t = std::pair; + +// this structure defines the data stored in the kv::table +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; + std::string street; + std::string city; + std::string state; + std::string country; + std::string personal_id; + + // data members supporting the indexes built for this structure + fullname_t full_name_first_last; + fullname_t full_name_last_first; + address_t address; + country_personal_id_t country_personal_id; +}; + +// helper factory to easily build person objects +struct person_factory { + static person get_person( + eosio::name account_name, + std::string first_name, + std::string last_name, + std::string street, + std::string city, + std::string state, + std::string country, + std::string personal_id) { + return person { + .account_name = account_name, + .first_name = first_name, + .last_name = last_name, + .street = street, + .city = city, + .state = state, + .country = country, + .personal_id = personal_id, + .full_name_first_last = {first_name, last_name, account_name}, + .full_name_last_first = {last_name, first_name, account_name}, + .address = {street, city, state, country, account_name}, + .country_personal_id = {country, personal_id}, + }; + } +}; + +class [[eosio::contract]] kv_addr_book : public eosio::contract { + + struct [[eosio::table]] address_table : eosio::kv::table { + // unique indexes definitions + // 1. they are defined for just one property of the kv::table parameter type (person) + // 2. unique indexes for multiple properties of the kv::table parameter type + // are defined with the help of a pair or a tuple; a pair if the index has + // two properties or a tuple in case of more than two + index account_name_uidx { + name{"accname"_n}, + &person::account_name }; + index country_personal_id_uidx { + name{"cntrypersid"_n}, + &person::country_personal_id }; + + // non-unique indexes definitions + // 1. non unique indexes need to be defined for at least two properties, + // 2. first properties, besides the last, are the ones wanted to be indexed non-uniquely + // 3. the last one needs to be a property that stores unique values, because + // under the hood every index (non-unique or unique) is stored as an unique + // index, and by providing as the last property one that has unique values + // it ensures the uniques of the values combined (including non-unique ones) + index full_name_first_last_idx { + name{"frstnameidx"_n}, + &person::full_name_first_last}; + index full_name_last_first_idx { + name{"lastnameidx"_n}, + &person::full_name_last_first}; + index address_idx { + name{"addressidx"_n}, + &person::address}; + + address_table(eosio::name contract_name) { + init(contract_name, + account_name_uidx, + country_personal_id_uidx, + full_name_first_last_idx, + full_name_last_first_idx, + address_idx); + } + }; + + public: + using contract::contract; + kv_addr_book(eosio::name receiver, eosio::name code, eosio::datastream ds) + : contract(receiver, code, ds) {} + + // retrieves a person based on primary key account_name + [[eosio::action]] + person get(eosio::name account_name); + + // retrieves a person based on unique index defined by country and personal_id + [[eosio::action]] + person getbycntrpid(std::string country, std::string personal_id); + + // retrieves list of persons with the same last name + [[eosio::action]] + std::vector getbylastname(std::string last_name); + + [[eosio::action]] + std::vector getbyaddress( + std::string street, + std::string city, + std::string state, + std::string country); + + // creates if not exists, or updates if already exists, a person + [[eosio::action]] + void upsert(eosio::name account_name, + std::string first_name, + std::string last_name, + std::string street, + std::string city, + std::string state, + std::string country, + std::string personal_id); + + // deletes a person based on primary key account_name + [[eosio::action]] + void del(eosio::name account_name); + + // checks if a person exists in addressbook with a specific personal_id and country + [[eosio::action]] + bool checkpidcntr(std::string personal_id, std::string country); + + // iterates over the first iterations_count persons in the table + // and prints their first and last names + [[eosio::action]] + void iterate(int iterations_count); + + using get_action = eosio::action_wrapper<"get"_n, &kv_addr_book::get>; + using get_by_cntry_pers_id_action = eosio::action_wrapper<"getbycntrpid"_n, &kv_addr_book::getbycntrpid>; + using get_by_last_name_action = eosio::action_wrapper<"getbylastname"_n, &kv_addr_book::getbylastname>; + using get_buy_address_action = eosio::action_wrapper<"getbyaddress"_n, &kv_addr_book::getbyaddress>; + using upsert_action = eosio::action_wrapper<"upsert"_n, &kv_addr_book::upsert>; + using del_action = eosio::action_wrapper<"del"_n, &kv_addr_book::del>; + using is_pers_id_in_cntry_action = eosio::action_wrapper<"checkpidcntr"_n, &kv_addr_book::checkpidcntr>; + using iterate_action = eosio::action_wrapper<"iterate"_n, &kv_addr_book::iterate>; + + private: + void print_person(const person& person, bool new_line = true); + address_table addresses{"kvaddrbook"_n}; +}; diff --git a/examples/kv_addr_book/ricardian/kv_example.contracts.md b/examples/kv_addr_book/ricardian/kv_example.contracts.md new file mode 100644 index 0000000000..5c7d6556a7 --- /dev/null +++ b/examples/kv_addr_book/ricardian/kv_example.contracts.md @@ -0,0 +1,3 @@ +

hi

+ +Stub for hi action's ricardian contract \ No newline at end of file diff --git a/examples/kv_addr_book/src/CMakeLists.txt b/examples/kv_addr_book/src/CMakeLists.txt new file mode 100644 index 0000000000..47b491f013 --- /dev/null +++ b/examples/kv_addr_book/src/CMakeLists.txt @@ -0,0 +1,8 @@ +project(kv_addr_book) + +set(EOSIO_WASM_OLD_BEHAVIOR "Off") +find_package(eosio.cdt) + +add_contract( kv_addr_book kv_addr_book kv_addr_book.cpp ) +target_include_directories( kv_addr_book PUBLIC ${CMAKE_SOURCE_DIR}/../include ) +target_ricardian_directory( kv_addr_book ${CMAKE_SOURCE_DIR}/../ricardian ) \ No newline at end of file diff --git a/examples/kv_addr_book/src/kv_addr_book.cpp b/examples/kv_addr_book/src/kv_addr_book.cpp new file mode 100644 index 0000000000..09688fef75 --- /dev/null +++ b/examples/kv_addr_book/src/kv_addr_book.cpp @@ -0,0 +1,198 @@ +#include + +void kv_addr_book::print_person(const person& person, bool new_line) { + // when using "/n" in the string printed by eosio everything else + // printed after this print for the current action will not be shown + // in the console, therefore use it as the last print statement only. + eosio::print_f( + new_line + ? "Person found: {%, %, %, %, %, %, %}\n" + : "Person found: {%, %, %, %, %, %, %}. ", + person.first_name, + person.last_name, + person.street, + person.city, + person.state, + person.country, + person.personal_id); +} + +// retrieves a person based on primary key account_name +// we make use of index find function, which returns an iterator, +// and then use iterator value +[[eosio::action]] +person kv_addr_book::get(eosio::name account_name) { + address_table addresses{"kvaddrbook"_n}; + + // search for person by primary key account_name + auto itr = addresses.account_name_uidx.find(account_name); + + // check if person was found + if (itr != addresses.account_name_uidx.end()) { + // extract person from iterator, print it and return it to the action sender + const auto& person_found = itr.value(); + + print_person(person_found); + + // return found person from action + return person_found; + } + else { + eosio::print_f("Person not found in addressbook."); + // return empty person from action + return person{}; + } +} + +// retrieves a person based on unique index defined by country and personal_id +[[eosio::action]] +person kv_addr_book::getbycntrpid(std::string country, std::string personal_id) { + address_table addresses{"kvaddrbook"_n}; + + const auto& person_found = addresses.country_personal_id_uidx.get({country, personal_id}); + if (person_found.has_value()) { + print_person(person_found.value()); + // return found person from action + return person_found.value(); + } + else { + eosio::print_f("Person not found in addressbook."); + // return empty person from action + return person{}; + } +} + +// retrieves list of persons with the same last name +[[eosio::action]] +std::vector kv_addr_book::getbylastname(std::string last_name) { + address_table addresses{"kvaddrbook"_n}; + + std::string min_first_name("A"); + std::string max_first_name(100, 'z'); + eosio::name min_account_name{0}; + eosio::name max_account_name{UINT_MAX}; + + auto list_of_persons = addresses.full_name_last_first_idx.range( + {last_name, min_first_name, min_account_name}, + {last_name, max_first_name, max_account_name}); + + eosio::print_f("Found % person(s). ", list_of_persons.size()); + + for (auto& person : list_of_persons) { + print_person(person, false); + } + // return found list of person from action + return list_of_persons; +} + +// retrieves list of persons with the same address +[[eosio::action]] +std::vector kv_addr_book::getbyaddress( + std::string street, std::string city, std::string state, std::string country) { + address_table addresses{"kvaddrbook"_n}; + + eosio::name min_account_name{0}; + eosio::name max_account_name{UINT_MAX}; + + auto list_of_persons = addresses.address_idx.range( + {street, city, state, country, min_account_name}, + {street, city, state, country, max_account_name}); + + eosio::print_f("Found % person(s). ", list_of_persons.size()); + + for (auto& person : list_of_persons) { + print_person(person, false); + } + // return found list of persons from action + return list_of_persons; +} + +// creates if not exists, or updates if already exists, a person +[[eosio::action]] +void kv_addr_book::upsert( + eosio::name account_name, + std::string first_name, + std::string last_name, + std::string street, + std::string city, + std::string state, + std::string country, + std::string personal_id) { + address_table addresses{"kvaddrbook"_n}; + + // create the person object which will be stored in kv::table + const person& person_update = person_factory::get_person( + account_name, + first_name, + last_name, + street, + city, + state, + country, + personal_id); + + // retreive the person by account name, if it doesn't exist we get an emtpy person + const person& existing_person = get(person_update.account_name); + + // upsert into kv::table + addresses.put(person_update, get_self()); + + // print customized message for insert vs update + if (existing_person.account_name.value == 0) { + eosio::print_f("Person was successfully added to addressbook."); + } + else { + eosio::print_f("Person was successfully updated in addressbook."); + } +} + +// deletes a person based on primary key account_name +[[eosio::action]] +void kv_addr_book::del(eosio::name account_name) { + address_table addresses{"kvaddrbook"_n}; + + // search for person by primary key account_name + auto itr = addresses.account_name_uidx.find(account_name); + + // check if person was found + if (itr != addresses.account_name_uidx.end()) { + // extract person from iterator and delete it + const auto& person_found = itr.value(); + + // delete it from kv::table + addresses.erase(person_found); + eosio::print_f("Person was successfully deleted from addressbook."); + } + else { + eosio::print_f("Person not found in addressbook."); + } +} + +// checks if a person exists in addressbook with a specific personal_id and country +[[eosio::action]] +bool kv_addr_book::checkpidcntr(std::string personal_id, std::string country) { + address_table addresses{"kvaddrbook"_n}; + + return addresses.country_personal_id_uidx.exists({personal_id, country}); +} + +// iterates over the first iterations_count persons in the table +// and prints their first and last names +[[eosio::action]] +void kv_addr_book::iterate(int iterations_count) { + address_table addresses{"kvaddrbook"_n}; + + auto begin_itr = addresses.account_name_uidx.begin(); + auto end_itr = addresses.account_name_uidx.end(); + + int current_iteration = 0; + while (begin_itr != end_itr && current_iteration < iterations_count) { + eosio::print_f( + "Person found: {%, %}. ", + begin_itr.value().first_name, + begin_itr.value().last_name); + + ++ begin_itr; + ++ current_iteration; + } +} diff --git a/examples/kv_map/CMakeLists.txt b/examples/kv_map/CMakeLists.txt new file mode 100644 index 0000000000..93853c64b6 --- /dev/null +++ b/examples/kv_map/CMakeLists.txt @@ -0,0 +1,17 @@ +include(ExternalProject) +# if no cdt root is given use default path +if(EOSIO_CDT_ROOT STREQUAL "" OR NOT EOSIO_CDT_ROOT) + find_package(eosio.cdt) +endif() + +ExternalProject_Add( + kv_map_project + SOURCE_DIR ${CMAKE_SOURCE_DIR}/src + BINARY_DIR ${CMAKE_BINARY_DIR}/kv_map + CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${EOSIO_CDT_ROOT}/lib/cmake/eosio.cdt/EosioWasmToolchain.cmake + UPDATE_COMMAND "" + PATCH_COMMAND "" + TEST_COMMAND "" + INSTALL_COMMAND "" + BUILD_ALWAYS 1 +) \ No newline at end of file diff --git a/examples/kv_map/README.txt b/examples/kv_map/README.txt new file mode 100644 index 0000000000..e0b1e12983 --- /dev/null +++ b/examples/kv_map/README.txt @@ -0,0 +1,20 @@ +--- kv_map Project --- + + -- How to Build with CMake and Make -- + - cd into the 'build' directory + - run the command 'cmake ..' + - run the command 'make' + + - After build - + - The built smart contract is under the 'kv_map' directory in the 'build' directory + - You can then do a 'set contract' action with 'cleos' and point to the './build/kv_map' directory + + - Additions to CMake should be done to the CMakeLists.txt in the './src' directory and not in the top level CMakeLists.txt + + -- How to build with eosio-cpp -- + - cd into the 'build' directory + - run the command 'eosio-cpp -abigen ../src/kv_map.cpp -o kv_map.wasm -I ../include/' + + - After build - + - The built smart contract is in the 'build' directory + - You can then do a 'set contract' action with 'cleos' and point to the 'build' directory diff --git a/examples/kv_map/include/kv_map.hpp b/examples/kv_map/include/kv_map.hpp new file mode 100644 index 0000000000..4fafe3eb63 --- /dev/null +++ b/examples/kv_map/include/kv_map.hpp @@ -0,0 +1,118 @@ +#include +#include + +using namespace eosio; +using namespace std; + +// this structure defines the data stored in the kv::map +struct person { + eosio::name account_name; + std::string first_name; + std::string last_name; + std::string street; + std::string city; + std::string state; + std::string country; + std::string personal_id; +}; + +// helper factory to easily build person objects +struct person_factory { + static person get_person( + eosio::name account_name, + std::string first_name, + std::string last_name, + std::string street, + std::string city, + std::string state, + std::string country, + std::string personal_id) { + return person { + .account_name = account_name, + .first_name = first_name, + .last_name = last_name, + .street = street, + .city = city, + .state = state, + .country = country, + .personal_id = personal_id + }; + } +}; + +class [[eosio::contract]] kv_map : public eosio::contract { + + using my_map_t = eosio::kv::map<"kvmap"_n, int, person>; + + public: + using contract::contract; + kv_map(eosio::name receiver, eosio::name code, eosio::datastream ds) + : contract(receiver, code, ds) {} + + // retrieves a person based on map key. + [[eosio::action]] + person get(int id); + + // inserts a person if not exists, or updates it if already exists. + // the payer for the resources consumed is the account that created the kv::map + // object in the first place, the account that owns this smart contract. + [[eosio::action]] + void upsert(int id, + eosio::name account_name, + std::string first_name, + std::string last_name, + std::string street, + std::string city, + std::string state, + std::string country, + std::string personal_id); + + // same as upsert only that it takes a user defined type as input parameter + [[eosio::action]] + void upsert2(int id, person pers); + + // inserts a person if not exists, or updates it if already exists. + // the payer is the account_name, specified as input parameter. + [[eosio::action]] + void upsertwpayer(int id, + eosio::name account_name, + std::string first_name, + std::string last_name, + std::string street, + std::string city, + std::string state, + std::string country, + std::string personal_id); + + // deletes a person based on unique id + [[eosio::action]] + void erase(int id); + + // checks if a person exists with a given personal_id and country + [[eosio::action]] + bool checkpidcntr(std::string personal_id, std::string country); + + // iterates over the first iterations_count persons in the table using for loop + // and prints their first and last names + [[eosio::action]] + void fiterate(int iterations_count); + + // iterates over the first iterations_count persons in the table using while loop + // and prints their first and last names + [[eosio::action]] + void witerate(int iterations_count); + + using get_action = eosio::action_wrapper<"get"_n, &kv_map::get>; + using upsert_action = eosio::action_wrapper<"upsert"_n, &kv_map::upsert>; + using upsert_action2 = eosio::action_wrapper<"upsert2"_n, &kv_map::upsert2>; + using upsertwpayer_action = eosio::action_wrapper<"upsertwpayer"_n, &kv_map::upsertwpayer>; + using erase_action = eosio::action_wrapper<"erase"_n, &kv_map::erase>; + using is_pers_id_in_cntry_action = eosio::action_wrapper<"checkpidcntr"_n, &kv_map::checkpidcntr>; + using witerate_action = eosio::action_wrapper<"witerate"_n, &kv_map::witerate>; + using fiterate_action = eosio::action_wrapper<"fiterate"_n, &kv_map::fiterate>; + + private: + void print_person(const person& person, bool new_line = true); + + my_map_t my_map{}; +}; diff --git a/examples/kv_map/ricardian/kv_example.contracts.md b/examples/kv_map/ricardian/kv_example.contracts.md new file mode 100644 index 0000000000..5c7d6556a7 --- /dev/null +++ b/examples/kv_map/ricardian/kv_example.contracts.md @@ -0,0 +1,3 @@ +

hi

+ +Stub for hi action's ricardian contract \ No newline at end of file diff --git a/examples/kv_map/src/CMakeLists.txt b/examples/kv_map/src/CMakeLists.txt new file mode 100644 index 0000000000..fa3fd4e29e --- /dev/null +++ b/examples/kv_map/src/CMakeLists.txt @@ -0,0 +1,8 @@ +project(kv_map) + +set(EOSIO_WASM_OLD_BEHAVIOR "Off") +find_package(eosio.cdt) + +add_contract( kv_map kv_map kv_map.cpp ) +target_include_directories( kv_map PUBLIC ${CMAKE_SOURCE_DIR}/../include ) +target_ricardian_directory( kv_map ${CMAKE_SOURCE_DIR}/../ricardian ) \ No newline at end of file diff --git a/examples/kv_map/src/kv_map.cpp b/examples/kv_map/src/kv_map.cpp new file mode 100644 index 0000000000..74be0f0c42 --- /dev/null +++ b/examples/kv_map/src/kv_map.cpp @@ -0,0 +1,231 @@ +#include + +void kv_map::print_person(const person& person, bool new_line) { + // when using "/n" in the string printed by eosio everything else + // printed after this print for the current action will not be shown + // in the console, therefore use it as the last print statement only. + eosio::print_f( + new_line + ? "Person found: {%, %, %, %, %, %, %}\n" + : "Person found: {%, %, %, %, %, %, %}. ", + person.first_name, + person.last_name, + person.street, + person.city, + person.state, + person.country, + person.personal_id); +} + +// retrieves a person based on unique id +[[eosio::action]] +person kv_map::get(int id) { + + auto itr = my_map.find(id); + + // check if person was found + if (itr != my_map.end()) { + // extract person from iterator, print it and return it to the action sender + const auto& person_found = itr->second(); + + print_person(person_found, false); + + // return found person from action + return person_found; + } + else { + eosio::print_f("Person with ID % not found.", id); + // return empty person from action + return person{}; + } +} + +// inserts a person if not exists, or updates it if already exists. +// the payer for the resources consumed is the account that created the kv::map +// object in the first place, the account that owns this smart contract. +[[eosio::action]] +void kv_map::upsert( + int id, + eosio::name account_name, + std::string first_name, + std::string last_name, + std::string street, + std::string city, + std::string state, + std::string country, + std::string personal_id) { + + const person& person_upsert = person_factory::get_person( + account_name, + first_name, + last_name, + street, + city, + state, + country, + personal_id); + + // retrieve the person by account name, if it doesn't exist we get an emtpy person + const person& existing_person = get(id); + + // upsert into kv::map, the payer is the account owning the kv::map, owning the smart contract + my_map[id] = person_upsert; + + // print customized message for insert vs update + if (existing_person.account_name.value == 0) { + eosio::print_f("Person (%, %, %) was successfully added.", + person_upsert.first_name, person_upsert.last_name, person_upsert.personal_id); + } + else { + eosio::print_f("Person with ID % was successfully updated.", id); + } +} + +[[eosio::action]] +void kv_map::upsert2( + int id, person pers) { + + const person& person_upsert = person_factory::get_person( + pers.account_name, + pers.first_name, + pers.last_name, + pers.street, + pers.city, + pers.state, + pers.country, + pers.personal_id); + + // retrieve the person by account name, if it doesn't exist we get an emtpy person + const person& existing_person = get(id); + + // upsert into kv::map, the payer is the account owning the kv::map, owning the smart contract + my_map[id] = person_upsert; + + // print customized message for insert vs update + if (existing_person.account_name.value == 0) { + eosio::print_f("Person (%, %, %) was successfully added.", + person_upsert.first_name, person_upsert.last_name, person_upsert.personal_id); + } + else { + eosio::print_f("Person with ID % was successfully updated.", id); + } +} + +// inserts a person if not exists, or updates it if already exists. +// the payer is the account_name, specified as input parameter. +[[eosio::action]] +void kv_map::upsertwpayer( + int id, + eosio::name account_name, + std::string first_name, + std::string last_name, + std::string street, + std::string city, + std::string state, + std::string country, + std::string personal_id) { + + const person& person_upsert = person_factory::get_person( + account_name, + first_name, + last_name, + street, + city, + state, + country, + personal_id); + + // retrieve the person by account name, if it doesn't exist we get an emtpy person + const person& existing_person = get(id); + + // upsert into kv::map and set the payer to be the account_name + my_map[std::pair(id, account_name)] = person_upsert; + + // print customized message for insert vs update + if (existing_person.account_name.value == 0) { + eosio::print_f("Person (%, %, %) was successfully added.", + person_upsert.first_name, person_upsert.last_name, person_upsert.personal_id); + } + else { + eosio::print_f("Person with ID % was successfully updated.", id); + } +} + +// deletes a person based on unique id +[[eosio::action]] +void kv_map::erase(int id) { + + // search for person by unique id + auto itr = my_map.find(id); + + // check if person was found + if (itr != my_map.end()) { + // extract person from iterator and delete it + const auto person_found = itr->second(); + + // delete it from kv::map + my_map.erase(id); + eosio::print_f("Person (%, %, %) was successfully deleted.", + person_found.first_name, person_found.last_name, person_found.personal_id); + } + else { + eosio::print_f("Person with ID % not found.", id); + } +} + +// checks if a person exists with a given personal_id and country. +[[eosio::action]] +bool kv_map::checkpidcntr(std::string personal_id, std::string country) { + + for ( const auto& person_detail : my_map ) { + if (person_detail.second().country == country && + person_detail.second().personal_id == personal_id) { + eosio::print_f("Person with personal id % and country % found in db with ID %d.", + personal_id, + country, + person_detail.key); + return true; + } + } + eosio::print_f("Person with personal id % not found in country %.", personal_id, country); + return false; +} + +// iterates over the first iterations_count persons using while loop, +// and prints their first and last names. +[[eosio::action]] +void kv_map::witerate(int iterations_count) { + + auto begin_itr = my_map.begin(); + auto end_itr = my_map.end(); + + int current_iteration = 0; + while (begin_itr != end_itr && current_iteration < iterations_count) { + eosio::print_f( + "Person %: {%, %}. ", + current_iteration + 1, + begin_itr->second().first_name, + begin_itr->second().last_name); + + ++ begin_itr; + ++ current_iteration; + } +} + +// iterates over the first iterations_count persons using for loop, +// and prints their first and last names. +[[eosio::action]] +void kv_map::fiterate(int iterations_count) { + + int current_iteration = 0; + for ( const auto& person_detail : my_map ) { + if (current_iteration ++ < iterations_count) { + eosio::print_f( + "Person %: {%, %}. ", + current_iteration, + person_detail.second().first_name, + person_detail.second().last_name); + } + else break; + } +} diff --git a/examples/kv_table_tests/CMakeLists.txt b/examples/kv_table_tests/CMakeLists.txt new file mode 100644 index 0000000000..58709c2971 --- /dev/null +++ b/examples/kv_table_tests/CMakeLists.txt @@ -0,0 +1,17 @@ +include(ExternalProject) +# if no cdt root is given use default path +if(EOSIO_CDT_ROOT STREQUAL "" OR NOT EOSIO_CDT_ROOT) + find_package(eosio.cdt) +endif() + +ExternalProject_Add( + kvtest_project + SOURCE_DIR ${CMAKE_SOURCE_DIR}/src + BINARY_DIR ${CMAKE_BINARY_DIR}/kvtest + CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${EOSIO_CDT_ROOT}/lib/cmake/eosio.cdt/EosioWasmToolchain.cmake + UPDATE_COMMAND "" + PATCH_COMMAND "" + TEST_COMMAND "" + INSTALL_COMMAND "" + BUILD_ALWAYS 1 +) \ No newline at end of file diff --git a/examples/kv_table_tests/README.txt b/examples/kv_table_tests/README.txt new file mode 100644 index 0000000000..ad12f42fe3 --- /dev/null +++ b/examples/kv_table_tests/README.txt @@ -0,0 +1,16 @@ +--- kv_tests Project --- + +--backing-store chainbase -- How to Build with CMake and Make -- + - cd into the 'build' directory + - run the command 'cmake .. -DEOSIO_CONTRACTS_ROOT=/path/to//eosio.contracts' + - run the command 'make' + + - After build - + - The built smart contract is under the 'kvtest' directory in the 'build' directory + - You can then do a 'set contract' action with 'cleos' and point to the './build/kvtest' directory + + - Additions to CMake should be done to the CMakeLists.txt in the './src' directory and not in the top level CMakeLists.txt + + - After build - + - The built smart contract is in the 'build' directory + - You can then do a 'set contract' action with 'cleos' and point to the 'build' directory diff --git a/examples/kv_table_tests/ricardian/kv_example.contracts.md b/examples/kv_table_tests/ricardian/kv_example.contracts.md new file mode 100644 index 0000000000..5c7d6556a7 --- /dev/null +++ b/examples/kv_table_tests/ricardian/kv_example.contracts.md @@ -0,0 +1,3 @@ +

hi

+ +Stub for hi action's ricardian contract \ No newline at end of file diff --git a/examples/kv_table_tests/src/CMakeLists.txt b/examples/kv_table_tests/src/CMakeLists.txt new file mode 100644 index 0000000000..e76b6fcb48 --- /dev/null +++ b/examples/kv_table_tests/src/CMakeLists.txt @@ -0,0 +1,7 @@ +project(kvtest) + +set(EOSIO_WASM_OLD_BEHAVIOR "Off") +find_package(eosio.cdt) + +add_contract( kvtest kvtest kvtest.cpp ) +target_ricardian_directory( kvtest ${CMAKE_SOURCE_DIR}/../ricardian ) \ No newline at end of file diff --git a/examples/kv_table_tests/src/kvtest.cpp b/examples/kv_table_tests/src/kvtest.cpp new file mode 100644 index 0000000000..6378116c6c --- /dev/null +++ b/examples/kv_table_tests/src/kvtest.cpp @@ -0,0 +1,76 @@ +#include +#include +#include + +extern "C" __attribute__((eosio_wasm_import)) void set_kv_parameters_packed(const void* params, uint32_t size); + +using namespace eosio; + +struct kvtest_record { + std::string id; + std::string data; +}; + +class [[eosio::contract]] kvtest : public contract { + struct [[eosio::table]] kvtest_table : eosio::kv::table { + KV_NAMED_INDEX("by.id", id); + kvtest_table(name contract) { init(contract, id); } + }; + + public: + using contract::contract; + + kvtest(eosio::name receiver, eosio::name code, eosio::datastream ds) : + contract(receiver, code, ds) + { + uint32_t limits[4]; + limits[0] = 0; + limits[1] = 1024 * 1024; + limits[2] = 9 * 1024 * 1024; + limits[3] = 10; + set_kv_parameters_packed(limits, sizeof(limits)); + } + + [[eosio::action]] + void smalltest() { + require_auth(_self); + kvtest_table table{"eosio"_n}; + + uint64_t id = 1; + std::string data = "test"; + + for (; id < 5000; ++id) { + table.put({std::to_string(id), data + std::to_string(id)}, get_self()); + } + + for (id = 1; id < 5000; ++id) { + auto itr = table. id.find(std::to_string(id)); + if (itr != table.id.end()) { + std::string val = itr.value().data; + eosio::check(val.compare(data + std::to_string(id)) == 0, "The value for the key " + std::to_string(id) + " is " + val); + } + } + } + + [[eosio::action]] + void largetest() { + require_auth(_self); + + kvtest_table table{"eosio"_n}; + + std::string data = std::string(8 * 1024 * 1024, 't'); + + uint64_t id = 1; + for (; id < 200; ++id) + table.put({std::to_string(id), std::to_string(id) + data}, get_self()); + + for (id = 1; id < 200; ++id) { + auto itr = table.id.find(std::to_string(id)); + if (itr != table.id.end()) { + std::string val = itr.value().data.substr(0, 10); + std::string expected = (std::to_string(id) + data).substr(0, 10); + eosio::check(val.compare(expected) == 0, "The value for the key " + std::to_string(id) + " is " + val + "..."); + } + } + } +}; diff --git a/examples/kv_todo/CMakeLists.txt b/examples/kv_todo/CMakeLists.txt new file mode 100644 index 0000000000..10574f38d8 --- /dev/null +++ b/examples/kv_todo/CMakeLists.txt @@ -0,0 +1,17 @@ +include(ExternalProject) +# if no cdt root is given use default path +if(EOSIO_CDT_ROOT STREQUAL "" OR NOT EOSIO_CDT_ROOT) + find_package(eosio.cdt) +endif() + +ExternalProject_Add( + kv_todo_project + SOURCE_DIR ${CMAKE_SOURCE_DIR}/src + BINARY_DIR ${CMAKE_BINARY_DIR}/kv_todo + CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${EOSIO_CDT_ROOT}/lib/cmake/eosio.cdt/EosioWasmToolchain.cmake + UPDATE_COMMAND "" + PATCH_COMMAND "" + TEST_COMMAND "" + INSTALL_COMMAND "" + BUILD_ALWAYS 1 +) diff --git a/examples/kv_todo/README.txt b/examples/kv_todo/README.txt new file mode 100644 index 0000000000..364ef6d118 --- /dev/null +++ b/examples/kv_todo/README.txt @@ -0,0 +1,20 @@ +--- kv_todo Project --- + + -- How to Build with CMake and Make -- + - cd into the 'build' directory + - run the command 'cmake ..' + - run the command 'make' + + - After build - + - The built smart contract is under the 'kv_todo' directory in the 'build' directory + - You can then do a 'set contract' action with 'cleos' and point to the './build/kv_todo' directory + + - Additions to CMake should be done to the CMakeLists.txt in the './src' directory and not in the top level CMakeLists.txt + + -- How to build with eosio-cpp -- + - cd into the 'build' directory + - run the command 'eosio-cpp -abigen ../src/kv_todo.cpp -o kv_todo.wasm -I ../include/' + + - After build - + - The built smart contract is in the 'build' directory + - You can then do a 'set contract' action with 'cleos' and point to the 'build' directory diff --git a/examples/kv_todo/include/kv_todo.hpp b/examples/kv_todo/include/kv_todo.hpp new file mode 100644 index 0000000000..dea54c66ea --- /dev/null +++ b/examples/kv_todo/include/kv_todo.hpp @@ -0,0 +1,73 @@ +#include +#include +#include +using namespace eosio; + +struct todo_entry { + std::string uuid; + std::tuple account_name; + std::tuple task; + std::tuple checked; + std::tuple created; + + std::string get_uuid() const { return uuid; } + eosio::name get_account_name() const { return std::get<0>(account_name); } + std::string get_task() const { return std::get<0>(task); } + bool get_checked() const { return std::get<0>(checked); } + int get_created() const { return std::get<0>(created); } +}; + +class [[eosio::contract]] kv_todo : public contract { + struct [[eosio::table]] todo_table : eosio::kv::table { + KV_NAMED_INDEX("uuid"_n, uuid); + KV_NAMED_INDEX("accname"_n, account_name); + KV_NAMED_INDEX("task"_n, task) + KV_NAMED_INDEX("checked"_n, checked) + KV_NAMED_INDEX("created"_n, created) + + // constructor for our `kvtodo` table to setup and initialize it + todo_table(eosio::name contract_name) { init(contract_name, uuid, account_name, task, checked, created); } + }; + public: + using contract::contract; + + [[eosio::action]] + std::vector getbyaccname(name account_name); + + [[eosio::action]] + size_t cntbyaccname(name account_name); + + [[eosio::action]] + std::vector getbytask(std::string task); + + [[eosio::action]] + size_t cntbytask(std::string name); + + [[eosio::action]] + std::vector getbychecked(bool value); + + [[eosio::action]] + size_t cntbychecked(bool checked); + + [[eosio::action]] + todo_entry upsert(const std::string& uuid, + eosio::name account_name, + const std::string& task, + bool checked); + + [[eosio::action]] + void del(const std::string& uuid); + + using get_by_account_name_action = action_wrapper<"getbyaccname"_n, &kv_todo::getbyaccname>; + using cnt_by_account_name_action = action_wrapper<"cntbyaccname"_n, &kv_todo::cntbyaccname>; + using get_by_task_action = action_wrapper<"getbytask"_n, &kv_todo::getbytask>; + using cnt_by_task_action = action_wrapper<"cntbytask"_n, &kv_todo::cntbytask>; + using get_by_checked_action = action_wrapper<"getbychecked"_n, &kv_todo::getbychecked>; + using cnt_by_checked_action = action_wrapper<"cntbychecked"_n, &kv_todo::cntbychecked>; + + using upsert_action = action_wrapper<"upsert"_n, &kv_todo::upsert>; + using del_action = action_wrapper<"del"_n, &kv_todo::del>; + + private: + todo_table todo_entries{"todo"_n}; +}; diff --git a/examples/kv_todo/ricardian/kv_todo.contracts.md b/examples/kv_todo/ricardian/kv_todo.contracts.md new file mode 100644 index 0000000000..5c7d6556a7 --- /dev/null +++ b/examples/kv_todo/ricardian/kv_todo.contracts.md @@ -0,0 +1,3 @@ +

hi

+ +Stub for hi action's ricardian contract \ No newline at end of file diff --git a/examples/kv_todo/src/CMakeLists.txt b/examples/kv_todo/src/CMakeLists.txt new file mode 100644 index 0000000000..381dd34f25 --- /dev/null +++ b/examples/kv_todo/src/CMakeLists.txt @@ -0,0 +1,8 @@ +project(kv_todo) + +set(EOSIO_WASM_OLD_BEHAVIOR "Off") +find_package(eosio.cdt) + +add_contract( kv_todo kv_todo kv_todo.cpp ) +target_include_directories( kv_todo PUBLIC ${CMAKE_SOURCE_DIR}/../include ) +target_ricardian_directory( kv_todo ${CMAKE_SOURCE_DIR}/../ricardian ) diff --git a/examples/kv_todo/src/kv_todo.cpp b/examples/kv_todo/src/kv_todo.cpp new file mode 100644 index 0000000000..e931378bf7 --- /dev/null +++ b/examples/kv_todo/src/kv_todo.cpp @@ -0,0 +1,96 @@ +#include + +// example) cleos push action todo getbyaccname '["todo"]' -p todo@active +[[eosio::action]] +std::vector kv_todo::getbyaccname(name account_name) { + std::string min_uuid = "00000000-0000-0000-0000-000000000000"; + std::string max_uuid = "ffffffff-ffff-ffff-ffff-ffffffffffff"; + + return todo_entries.account_name.range({account_name, min_uuid}, {account_name, max_uuid}); +} + +// example) cleos push action todo cntbyaccname '["todo"]' -p todo@active +[[eosio::action]] +size_t kv_todo::cntbyaccname(name account_name) { + std::string min_uuid = "00000000-0000-0000-0000-000000000000"; + std::string max_uuid = "ffffffff-ffff-ffff-ffff-ffffffffffff"; + + return todo_entries.account_name.range({account_name, min_uuid}, {account_name, max_uuid}).size(); +} + +// example) cleos push action todo getbytask '["Start Blockchain"]' -p todo@active +[[eosio::action]] +std::vector kv_todo::getbytask(std::string task) { + std::string min_uuid = "00000000-0000-0000-0000-000000000000"; + std::string max_uuid = "ffffffff-ffff-ffff-ffff-ffffffffffff"; + + return todo_entries.task.range({task, min_uuid}, {task, max_uuid}); +} + +// example) cleos push action todo cntbytask '["Start Blockchain"]' -p todo@active +[[eosio::action]] +size_t kv_todo::cntbytask(std::string task) { + std::string min_uuid = "00000000-0000-0000-0000-000000000000"; + std::string max_uuid = "ffffffff-ffff-ffff-ffff-ffffffffffff"; + + return todo_entries.task.range({task, min_uuid}, {task, max_uuid}).size(); +} + +// example) cleos push action todo getbychecked '["false"]' -p todo@active +[[eosio::action]] +std::vector kv_todo::getbychecked(bool checked) { + std::string min_uuid = "00000000-0000-0000-0000-000000000000"; + std::string max_uuid = "ffffffff-ffff-ffff-ffff-ffffffffffff"; + + return todo_entries.checked.range({checked, min_uuid}, {checked, max_uuid}); +} + +// example) cleos push action todo cntbychecked '["false"]' -p todo@active +[[eosio::action]] +size_t kv_todo::cntbychecked(bool checked) { + std::string min_uuid = "00000000-0000-0000-0000-000000000000"; + std::string max_uuid = "ffffffff-ffff-ffff-ffff-ffffffffffff"; + + return todo_entries.checked.range({checked, min_uuid}, {checked, max_uuid}).size(); +} + +/** +examples) +cleos push action todo upsert '["ac8acfe7-cd4e-4d22-8400-218b697a4517", "todo", "Deploy Hello World Contract", false]' -p todo@active +cleos push action todo upsert '["b7b0d09d-a82b-44d9-b067-3bae2d02917e", "todo", "Start Blockchain", false]' -p todo@active +cleos push action todo upsert '["bf581bee-9f2c-447b-94ad-78e4984b6f51", "todo", "Write Hello World Contract", false]' -p todo@active +*/ +[[eosio::action]] +todo_entry kv_todo::upsert(const std::string& uuid, + eosio::name account_name, + const std::string& task, + bool checked) { + + require_auth(account_name); + + auto itr = todo_entries.uuid.find(uuid); + if (itr != todo_entries.uuid.end()) { + check(account_name == itr.value().get_account_name(), "Unauthorized"); + todo_entry todo_entry_update = {itr.value().get_uuid(), {itr.value().get_account_name(), uuid}, {task, uuid}, {checked, uuid}, {itr.value().get_created(), uuid}}; + todo_entries.put(todo_entry_update, get_self()); + } else { + uint32_t created = eosio::current_time_point().sec_since_epoch(); + todo_entry todo_entry_insert = {uuid, {account_name, uuid}, {task,uuid}, {checked, uuid}, {created, uuid}}; + todo_entries.put(todo_entry_insert, get_self()); + } + + itr = todo_entries.uuid.find(uuid); + return itr.value(); +} + +[[eosio::action]] +void kv_todo::del(const std::string& uuid) { + auto itr = todo_entries.uuid.find(uuid); + + if (itr != todo_entries.uuid.end()) { + todo_entries.erase(itr.value()); + eosio::print_f("todo_entry was successfully deleted from table."); + } else { + eosio::print_f("todo_entry not found in table."); + } +} diff --git a/examples/multi_index_example/README.txt b/examples/multi_index_example/README.txt index 2f9e854dc8..6472e46aeb 100644 --- a/examples/multi_index_example/README.txt +++ b/examples/multi_index_example/README.txt @@ -1,7 +1,6 @@ --- multi_index_example Project --- -- How to Build with CMake and Make -- - - mkdir build - cd into the 'build' directory - run the command 'cmake ..' - run the command 'make' @@ -13,7 +12,6 @@ - Additions to CMake should be done to the CMakeLists.txt in the './src' directory and not in the top level CMakeLists.txt -- How to build with eosio-cpp -- - - mkdir build - cd into the 'build' directory - run the command 'eosio-cpp -abigen ../src/multi_index_example.cpp -o multi_index_example.wasm -I ../include/' diff --git a/examples/multi_index_example/include/multi_index_example.hpp b/examples/multi_index_example/include/multi_index_example.hpp index bd08eb9a7e..33806f0e3d 100644 --- a/examples/multi_index_example/include/multi_index_example.hpp +++ b/examples/multi_index_example/include/multi_index_example.hpp @@ -15,7 +15,10 @@ class [[eosio::contract]] multi_index_example : public contract { uint64_t by_secondary()const { return secondary.value; } }; - typedef eosio::multi_index<"testtaba"_n, test_table, eosio::indexed_by<"secid"_n, eosio::const_mem_fun>> test_tables; + typedef eosio::multi_index<"testtaba"_n, test_table, + eosio::indexed_by<"secid"_n, + eosio::const_mem_fun>> + test_tables; test_tables testtab; diff --git a/examples/multi_index_large/CMakeLists.txt b/examples/multi_index_large/CMakeLists.txt new file mode 100644 index 0000000000..82e2c2895e --- /dev/null +++ b/examples/multi_index_large/CMakeLists.txt @@ -0,0 +1,17 @@ +include(ExternalProject) +# if no cdt root is given use default path +if(EOSIO_CDT_ROOT STREQUAL "" OR NOT EOSIO_CDT_ROOT) + find_package(eosio.cdt) +endif() + +ExternalProject_Add( + multi_index_example_project + SOURCE_DIR ${CMAKE_SOURCE_DIR}/src + BINARY_DIR ${CMAKE_BINARY_DIR}/multi_index_large + CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${EOSIO_CDT_ROOT}/lib/cmake/eosio.cdt/EosioWasmToolchain.cmake + UPDATE_COMMAND "" + PATCH_COMMAND "" + TEST_COMMAND "" + INSTALL_COMMAND "" + BUILD_ALWAYS 1 +) diff --git a/examples/multi_index_large/README.txt b/examples/multi_index_large/README.txt new file mode 100644 index 0000000000..770e7371d4 --- /dev/null +++ b/examples/multi_index_large/README.txt @@ -0,0 +1,37 @@ +--- multi_index_large Project --- + + -- How to Build with CMake and Make -- + - cd into the 'build' directory + - run the command 'cmake ..' + - run the command 'make' + + - After build - + - The built smart contract is under the 'multi_index_large' directory in the 'build' directory + - You can then do a 'set contract' action with 'cleos' and point to the './build/multi_index_large' directory + + - Additions to CMake should be done to the CMakeLists.txt in the './src' directory and not in the top level CMakeLists.txt + + -- How to build with eosio-cpp -- + - cd into the 'build' directory + - run the command 'eosio-cpp -abigen ../src/multi_index_large.cpp -o multi_index_large.wasm -I ../include/' + + - After build - + - The built smart contract is in the 'build' directory + - You can then do a 'set contract' action with 'cleos' and point to the 'build' directory + +cleos create account eosio testtaba your_public_key +cleos set contract testtaba multi_index_large_wasm_directory multi_index_large.wasm multi_index_large.abi -p testtaba + +- example cleos actions +cleos push action testtaba set '{"id":1, "u64": 1, "u128":"0x00000000000000000000000000000000E", "f64":1.1, "f128":"0x00000000000000000000000000000001", "i128":"0x000000000000000000000000000000001", "chk256":"0987654321ABCDEF0987654321FFFF1234567890ABCDEF001234567890ABCDE0"}' -p testtaba +cleos push action testtaba set '{"id":2, "u64": 2, "u128":"0x00000000000000000000000000000001E", "f64":2.1, "f128":"0x00000000000000000000000000000002", "i128":"0x000000000000000000000000000000002", "chk256":"0987654321ABCDEF0987654321FFFF1234567890ABCDEF001234567890ABCDE1"}' -p testtaba +testtaba +cleos push action testtaba print '{"id":1}' -p testtaba +cleos push action testtaba print '{"id":2}' -p testtaba +cleos push action testtaba mod '{"id":2, "u64": 22, "u128":"0x00000000000000000000000000000021E", "f64":20.1, "f128":"0x00000000000000000000000000000021", "i128":"0x000000000000000000000000000000022", "chk256":"0987654321ABCDEF0987654321FFFF1234567890ABCDEF001234567890ABCDE1"}' -p testtaba +cleos push action testtaba print '{"id":2}' -p testtaba +cleos push action testtaba del '{"id":2}' -p testtaba +cleos push action testtaba print '{"id":2}' -p testtaba +cleos push action testtaba byf '{"f64":5.1}' -p testtaba +cleos push action testtaba byff '{"f128":"0x00000000000000000000000000000005"}' -p testtaba +cleos push action testtaba byuuuu '{"u128":"0xF0000000000000000000000000000004E"}' -p testtaba diff --git a/examples/multi_index_large/include/multi_index_large.hpp b/examples/multi_index_large/include/multi_index_large.hpp new file mode 100644 index 0000000000..82db57a4e1 --- /dev/null +++ b/examples/multi_index_large/include/multi_index_large.hpp @@ -0,0 +1,68 @@ +#include +using namespace eosio; + +class [[eosio::contract]] multi_index_large : public contract { + public: + using contract::contract; + multi_index_large( name receiver, name code, datastream ds ) + : contract(receiver, code, ds), testtab(receiver, receiver.value) {} + + struct [[eosio::table("data")]] main_record { + uint64_t id = 0; + + uint64_t u64 = 0; + uint128_t u128 = 0ULL; + double f64 = 0.0L; + long double f128 = 0.0L; + eosio::checksum256 chk256; + + uint64_t primary_key()const { return id; } + uint64_t get_u64()const { return u64; } + uint128_t get_u128()const { return u128; } + double get_f64()const { return f64; } + long double get_f128()const { return f128; } + const eosio::checksum256& get_chk256()const { return chk256; } + + EOSLIB_SERIALIZE( main_record, (id)(u64)(u128)(f64)(f128)(chk256)) + }; + + using test_tables = eosio::multi_index<"testtaba"_n, main_record, + eosio::indexed_by< "byuu"_n, eosio::const_mem_fun< main_record, uint64_t, + &main_record::get_u64 > >, + eosio::indexed_by< "byuuuu"_n, eosio::const_mem_fun< main_record, uint128_t, + &main_record::get_u128 > >, + eosio::indexed_by< "byf"_n, eosio::const_mem_fun< main_record, double, + &main_record::get_f64> >, + eosio::indexed_by< "byff"_n, eosio::const_mem_fun< main_record, long double, + &main_record::get_f128> >, + eosio::indexed_by< "bychkb"_n, eosio::const_mem_fun< main_record, const eosio::checksum256&, + &main_record::get_chk256 > > + >; + test_tables testtab; + + [[eosio::action]] + void set( uint64_t id, uint64_t u64, uint128_t u128, + double f64, long double f128, eosio::checksum256 chk256 ); + + [[eosio::action]] + void print( uint64_t id ); + + [[eosio::action]] + void byf( double f64); + + [[eosio::action]] + void byff( long double f128 ); + + [[eosio::action]] + void byuuuu( uint128_t u128 ); + + [[eosio::action]] + void bychkb(eosio::checksum256 chk256); + + [[eosio::action]] + void mod( uint64_t id, uint64_t u64, uint128_t u128, + double f64, long double f128, eosio::checksum256 chk256 ); + + [[eosio::action]] + void del( uint64_t id ); +}; diff --git a/examples/multi_index_large/ricardian/multi_index_large.contracts.md b/examples/multi_index_large/ricardian/multi_index_large.contracts.md new file mode 100644 index 0000000000..5e54f8c112 --- /dev/null +++ b/examples/multi_index_large/ricardian/multi_index_large.contracts.md @@ -0,0 +1,3 @@ +

hi

+ +Stub for multi_index_large action's ricardian contract diff --git a/examples/multi_index_large/src/CMakeLists.txt b/examples/multi_index_large/src/CMakeLists.txt new file mode 100644 index 0000000000..dfaa0f3760 --- /dev/null +++ b/examples/multi_index_large/src/CMakeLists.txt @@ -0,0 +1,8 @@ +project(multi_index_large) + +set(EOSIO_WASM_OLD_BEHAVIOR "Off") +find_package(eosio.cdt) + +add_contract( multi_index_large multi_index_large multi_index_large.cpp ) +target_include_directories( multi_index_large PUBLIC ${CMAKE_SOURCE_DIR}/../include ) +target_ricardian_directory( multi_index_large ${CMAKE_SOURCE_DIR}/../ricardian ) diff --git a/examples/multi_index_large/src/multi_index_large.cpp b/examples/multi_index_large/src/multi_index_large.cpp new file mode 100644 index 0000000000..2f5a3ac281 --- /dev/null +++ b/examples/multi_index_large/src/multi_index_large.cpp @@ -0,0 +1,91 @@ +#include + +[[eosio::action]] +void multi_index_large::set( uint64_t id, uint64_t u64, uint128_t u128, + double f64, long double f128, eosio::checksum256 chk256 ) { + auto itr = testtab.find(id); + if ( itr == testtab.end() ) { + testtab.emplace( _self, [&]( auto& u ) { + u.id = id; + u.u64 = u64; + u.u128 = u128; + u.f128 = f128; + u.chk256 = chk256; + }); + } +} + +[[eosio::action]] +void multi_index_large::print( uint64_t id ) { + auto itr = testtab.find(id); + check( itr != testtab.end(), "id does not exist in table" ); + eosio::print_f("Test Table : {%, %, %, %}\n", itr->id, itr->f128, itr->u128, itr->chk256); +} + +[[eosio::action]] +void multi_index_large::byf( double f64 ) { + auto idx = testtab.get_index<"byf"_n>(); + for ( auto itr = idx.begin(); itr != idx.end(); itr++ ) { + if( itr->f64 == f64) { + print( itr->id ); + } + } +} + +[[eosio::action]] +void multi_index_large::byff( long double f128 ) { + auto idx = testtab.get_index<"byff"_n>(); + for ( auto itr = idx.begin(); itr != idx.end(); itr++ ) { + if( itr->f128 == f128) { + print( itr->id ); + } + } +} + +[[eosio::action]] +void multi_index_large::byuuuu( uint128_t u128 ) { + auto idx = testtab.get_index<"byuuuu"_n>(); + for ( auto itr = idx.begin(); itr != idx.end(); itr++ ) { + if( itr->u128 == u128 ) { + print( itr->id ); + } + } +} + +[[eosio::action]] +void multi_index_large::bychkb( eosio::checksum256 chk256 ) { + auto idx = testtab.get_index<"bychkb"_n>(); + for ( auto itr = idx.begin(); itr != idx.end(); itr++ ) { + if( itr->chk256 == chk256 ) { + print( itr->id ); + } + } +} + +[[eosio::action]] +void multi_index_large::mod( uint64_t id, uint64_t u64, uint128_t u128, + double f64, long double f128, eosio::checksum256 chk256 ) { + auto itr = testtab.find(id); + check( itr != testtab.end(), "id does not exist in table" ); + testtab.modify( itr, _self, [&]( auto& row ) { + row.u64 = u64; + row.u128 = u128; + row.f128 = f128; + row.chk256 = chk256; + }); +} + +[[eosio::action]] +void multi_index_large::del( uint64_t id ) { + // check if the user already exists + auto itr = testtab.find(id); + if ( itr == testtab.end() ) { + printf("user does not exist in table, nothing to delete" ); + return; + } + testtab.erase( itr ); +} + + + + diff --git a/examples/send_inline/README.txt b/examples/send_inline/README.txt index 9f2183e211..cc86cbc385 100644 --- a/examples/send_inline/README.txt +++ b/examples/send_inline/README.txt @@ -1,7 +1,6 @@ --- send_inline Project --- -- How to Build with CMake and Make -- - - mkdir build - cd into the 'build' directory - run the command 'cmake ..' - run the command 'make' @@ -13,7 +12,6 @@ - Additions to CMake should be done to the CMakeLists.txt in the './src' directory and not in the top level CMakeLists.txt -- How to build with eosio-cpp -- - - mkdir build - cd into the 'build' directory - run the command 'eosio-cpp -abigen ../src/send_inline.cpp -o send_inline.wasm -I ../include/ -I ../../hello/include/' diff --git a/examples/singleton_example/include/singleton_example.hpp b/examples/singleton_example/include/singleton_example.hpp index 8b67639c62..26de5c5cbe 100644 --- a/examples/singleton_example/include/singleton_example.hpp +++ b/examples/singleton_example/include/singleton_example.hpp @@ -19,7 +19,7 @@ class [[eosio::contract]] singleton_example : public contract { name primary_value; uint64_t secondary_value; uint64_t primary_key() const { return primary_value.value; } - } tt; + } testtablerow; using singleton_type = eosio::singleton<"testtable"_n, testtable>; singleton_type singleton_instance; diff --git a/examples/singleton_example/src/singleton_example.cpp b/examples/singleton_example/src/singleton_example.cpp index 4e896bb25a..01472b4cc2 100644 --- a/examples/singleton_example/src/singleton_example.cpp +++ b/examples/singleton_example/src/singleton_example.cpp @@ -2,11 +2,7 @@ [[eosio::action]] void singleton_example::set( name user, uint64_t value ) { - if (!singleton_instance.exists()) - { - singleton_instance.get_or_create(user, tt); - } - auto entry_stored = singleton_instance.get(); + auto entry_stored = singleton_instance.get_or_create(user, testtablerow); entry_stored.primary_value = user; entry_stored.secondary_value = value; singleton_instance.set(entry_stored, user); @@ -22,5 +18,5 @@ void singleton_example::get( ) { singleton_instance.get().secondary_value, "\n"); else - eosio::print("Singleton is empty\n"); + eosio::print("Singleton is empty.\n"); } diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 0649c46049..66a6980bde 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -11,12 +11,20 @@ endif() list(APPEND CMAKE_MODULE_PATH ${EOSIO_CDT_BIN}) include(EosioCDTMacros) -set(CMAKE_C_FLAGS " ${CMAKE_C_FLAGS} -O3 -Wall ") -set(CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -O3 -Wall ") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -Wall") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -Wall -no-missing-ricardian-clause") + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_EXTENSIONS ON) add_subdirectory(libc) add_subdirectory(libc++) add_subdirectory(eosiolib) add_subdirectory(boost) -add_subdirectory(native) add_subdirectory(rt) + +if (ENABLE_NATIVE_COMPILER) + add_subdirectory(native) +endif() + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/meta_refl/include/bluegrass DESTINATION ${BASE_BINARY_DIR}/include) diff --git a/libraries/boost/include/boost/hana/functional/curry.hpp b/libraries/boost/include/boost/hana/functional/curry.hpp index 74ec09459a..8d98b10fab 100644 --- a/libraries/boost/include/boost/hana/functional/curry.hpp +++ b/libraries/boost/include/boost/hana/functional/curry.hpp @@ -116,7 +116,7 @@ BOOST_HANA_NAMESPACE_BEGIN constexpr make_curry_t curry_or_call{}; template <> - constexpr auto curry_or_call<0> = apply; + inline constexpr auto curry_or_call<0> = apply; } template diff --git a/libraries/boost/include/boost/hana/fwd/type.hpp b/libraries/boost/include/boost/hana/fwd/type.hpp index c3c220db78..2f3e84cf3d 100644 --- a/libraries/boost/include/boost/hana/fwd/type.hpp +++ b/libraries/boost/include/boost/hana/fwd/type.hpp @@ -165,7 +165,7 @@ BOOST_HANA_NAMESPACE_BEGIN //! ### Rationale for stripping the references //! The rules for template argument deduction are such that a perfect //! solution that always matches `decltype` is impossible. Hence, we - //! have to settle on a solution that's good and and consistent enough + //! have to settle on a solution that's good and consistent enough //! for our needs. One case where matching `decltype`'s behavior is //! impossible is when the argument is a plain, unparenthesized variable //! or function parameter. In that case, `decltype_`'s argument will be diff --git a/libraries/boost/include/boost/move/algo/adaptive_sort.hpp b/libraries/boost/include/boost/move/algo/adaptive_sort.hpp index 2026f9c1b5..696d21078c 100644 --- a/libraries/boost/include/boost/move/algo/adaptive_sort.hpp +++ b/libraries/boost/include/boost/move/algo/adaptive_sort.hpp @@ -261,7 +261,7 @@ bool adaptive_sort_combine_all_blocks // Implies l_block == l_intbuf && use_internal_buf == true //If l_intbuf is zero, see if half keys can be reused as a reduced emergency buffer, // Implies l_block == n_keys/2 && use_internal_buf == true - //Otherwise, just give up and and use all keys to merge using rotations (use_internal_buf = false) + //Otherwise, just give up and use all keys to merge using rotations (use_internal_buf = false) bool use_internal_buf = false; size_type const l_block = lblock_for_combine(l_intbuf, n_keys, 2*l_merged, use_internal_buf); BOOST_ASSERT(!l_intbuf || (l_block == l_intbuf)); diff --git a/libraries/boost/include/boost/pfr/detail/config.hpp b/libraries/boost/include/boost/pfr/detail/config.hpp index 5a36de47d1..339df5e952 100644 --- a/libraries/boost/include/boost/pfr/detail/config.hpp +++ b/libraries/boost/include/boost/pfr/detail/config.hpp @@ -24,7 +24,7 @@ # define BOOST_PFR_USE_LOOPHOLE 1 #endif -#define BOOST_PFR_USE_CPP17 0 +#define BOOST_PFR_USE_CPP17 1 #ifndef BOOST_PFR_USE_CPP17 # ifdef __cpp_structured_bindings diff --git a/libraries/eosiolib/CMakeLists.txt b/libraries/eosiolib/CMakeLists.txt index 6622b9f4f4..345506346d 100644 --- a/libraries/eosiolib/CMakeLists.txt +++ b/libraries/eosiolib/CMakeLists.txt @@ -18,11 +18,6 @@ add_library(eosio_cmem memory.cpp ${HEADERS}) -add_native_library(native_eosio - eosiolib.cpp - crypto.cpp - malloc.cpp - ${HEADERS}) set_target_properties(eosio_malloc PROPERTIES LINKER_LANGUAGE C) @@ -36,12 +31,20 @@ target_include_directories(eosio PUBLIC ${CMAKE_SOURCE_DIR}/boost/include) target_link_libraries( eosio c c++ ) -add_dependencies( native_eosio eosio ) - add_custom_command( TARGET eosio POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${BASE_BINARY_DIR}/lib ) add_custom_command( TARGET eosio_malloc POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${BASE_BINARY_DIR}/lib ) add_custom_command( TARGET eosio_dsm POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${BASE_BINARY_DIR}/lib ) add_custom_command( TARGET eosio_cmem POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${BASE_BINARY_DIR}/lib ) -add_custom_command( TARGET native_eosio POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${BASE_BINARY_DIR}/lib ) + +if (ENABLE_NATIVE_COMPILER) + add_native_library(native_eosio + eosiolib.cpp + crypto.cpp + malloc.cpp + ${HEADERS}) + + add_dependencies( native_eosio eosio ) + add_custom_command( TARGET native_eosio POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${BASE_BINARY_DIR}/lib ) +endif() file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../eosiolib DESTINATION ${BASE_BINARY_DIR}/include FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp") diff --git a/libraries/eosiolib/capi/eosio/action.h b/libraries/eosiolib/capi/eosio/action.h index abec96698b..58d345848a 100644 --- a/libraries/eosiolib/capi/eosio/action.h +++ b/libraries/eosiolib/capi/eosio/action.h @@ -83,7 +83,7 @@ uint32_t read_action_data( void* msg, uint32_t len ); * @return the length of the current action's data field */ __attribute__((eosio_wasm_import)) -uint32_t action_data_size(); +uint32_t action_data_size( void ); /** * Add the specified account to set of accounts to be notified @@ -158,7 +158,7 @@ void send_context_free_inline(char *serialized_action, size_t size); * @return the time in microseconds from 1970 of the publication_time */ __attribute__((eosio_wasm_import)) -uint64_t publication_time(); +uint64_t publication_time( void ); /** * Get the current receiver of the action @@ -166,7 +166,17 @@ uint64_t publication_time(); * @return the account which specifies the current receiver of the action */ __attribute__((eosio_wasm_import)) -capi_name current_receiver(); +capi_name current_receiver( void ); + +/** + * Set the action return value which will be included in the action_receipt + * @brief Set the action return value + * @param return_value - serialized return value + * @param size - size of serialized return value in bytes + * @pre `return_value` is a valid pointer to an array at least `size` bytes long + */ +__attribute__((eosio_wasm_import)) +void set_action_return_value(void *return_value, size_t size); #ifdef __cplusplus } diff --git a/libraries/eosiolib/capi/eosio/crypto.h b/libraries/eosiolib/capi/eosio/crypto.h index e5d1ad0b8e..6f60fbe7d8 100644 --- a/libraries/eosiolib/capi/eosio/crypto.h +++ b/libraries/eosiolib/capi/eosio/crypto.h @@ -38,7 +38,7 @@ extern "C" { * @endcode */ __attribute__((eosio_wasm_import)) -void assert_sha256( const char* data, uint32_t length, const capi_checksum256* hash ); +void assert_sha256( const char* data, uint32_t length, const struct capi_checksum256* hash ); /** * Tests if the sha1 hash generated from data matches the provided checksum. @@ -63,7 +63,7 @@ void assert_sha256( const char* data, uint32_t length, const capi_checksum256* h * @endcode */ __attribute__((eosio_wasm_import)) -void assert_sha1( const char* data, uint32_t length, const capi_checksum160* hash ); +void assert_sha1( const char* data, uint32_t length, const struct capi_checksum160* hash ); /** * Tests if the sha512 hash generated from data matches the provided checksum. @@ -88,7 +88,7 @@ void assert_sha1( const char* data, uint32_t length, const capi_checksum160* has * @endcode */ __attribute__((eosio_wasm_import)) -void assert_sha512( const char* data, uint32_t length, const capi_checksum512* hash ); +void assert_sha512( const char* data, uint32_t length, const struct capi_checksum512* hash ); /** * Tests if the ripemod160 hash generated from data matches the provided checksum. @@ -112,7 +112,7 @@ void assert_sha512( const char* data, uint32_t length, const capi_checksum512* h * @endcode */ __attribute__((eosio_wasm_import)) -void assert_ripemd160( const char* data, uint32_t length, const capi_checksum160* hash ); +void assert_ripemd160( const char* data, uint32_t length, const struct capi_checksum160* hash ); /** * Hashes `data` using `sha256` and stores result in memory pointed to by hash. @@ -130,7 +130,7 @@ void assert_ripemd160( const char* data, uint32_t length, const capi_checksum160 * @endcode */ __attribute__((eosio_wasm_import)) -void sha256( const char* data, uint32_t length, capi_checksum256* hash ); +void sha256( const char* data, uint32_t length, struct capi_checksum256* hash ); /** * Hashes `data` using `sha1` and stores result in memory pointed to by hash. @@ -148,7 +148,7 @@ void sha256( const char* data, uint32_t length, capi_checksum256* hash ); * @endcode */ __attribute__((eosio_wasm_import)) -void sha1( const char* data, uint32_t length, capi_checksum160* hash ); +void sha1( const char* data, uint32_t length, struct capi_checksum160* hash ); /** * Hashes `data` using `sha512` and stores result in memory pointed to by hash. @@ -166,7 +166,7 @@ void sha1( const char* data, uint32_t length, capi_checksum160* hash ); * @endcode */ __attribute__((eosio_wasm_import)) -void sha512( const char* data, uint32_t length, capi_checksum512* hash ); +void sha512( const char* data, uint32_t length, struct capi_checksum512* hash ); /** * Hashes `data` using `ripemod160` and stores result in memory pointed to by hash. @@ -184,7 +184,7 @@ void sha512( const char* data, uint32_t length, capi_checksum512* hash ); * @endcode */ __attribute__((eosio_wasm_import)) -void ripemd160( const char* data, uint32_t length, capi_checksum160* hash ); +void ripemd160( const char* data, uint32_t length, struct capi_checksum160* hash ); /** * Calculates the public key used for a given signature and hash used to create a message. @@ -202,7 +202,7 @@ void ripemd160( const char* data, uint32_t length, capi_checksum160* hash ); * @endcode */ __attribute__((eosio_wasm_import)) -int recover_key( const capi_checksum256* digest, const char* sig, size_t siglen, char* pub, size_t publen ); +int recover_key( const struct capi_checksum256* digest, const char* sig, size_t siglen, char* pub, size_t publen ); /** * Tests a given public key with the generated key from digest and the signature. @@ -230,7 +230,7 @@ int recover_key( const capi_checksum256* digest, const char* sig, size_t siglen, * @endcode */ __attribute__((eosio_wasm_import)) -void assert_recover_key( const capi_checksum256* digest, const char* sig, size_t siglen, const char* pub, size_t publen ); +void assert_recover_key( const struct capi_checksum256* digest, const char* sig, size_t siglen, const char* pub, size_t publen ); #ifdef __cplusplus } diff --git a/libraries/eosiolib/capi/eosio/db.h b/libraries/eosiolib/capi/eosio/db.h index d0a2ee8883..e21f20caf7 100644 --- a/libraries/eosiolib/capi/eosio/db.h +++ b/libraries/eosiolib/capi/eosio/db.h @@ -30,9 +30,6 @@ extern "C" { */ /** - * - * Store a record in a primary 64-bit integer index table - * * @brief Store a record in a primary 64-bit integer index table * @param scope - The scope where the table resides (implied to be within the code of the current receiver) * @param table - The table name @@ -49,9 +46,6 @@ __attribute__((eosio_wasm_import)) int32_t db_store_i64(uint64_t scope, capi_name table, capi_name payer, uint64_t id, const void* data, uint32_t len); /** - * - * Update a record in a primary 64-bit integer index table - * * @brief Update a record in a primary 64-bit integer index table * @param iterator - Iterator to the table row containing the record to update * @param payer - The account that pays for the storage costs (use 0 to continue using current payer) @@ -61,14 +55,16 @@ int32_t db_store_i64(uint64_t scope, capi_name table, capi_name payer, uint64_t * @pre `*((uint64_t*)data)` stores the primary key * @pre `iterator` points to an existing table row in the table * @post the record contained in the table row pointed to by `iterator` is replaced with the new updated record + * @remark This function does not allow changing the primary key of a + * table row. The serialized data that is stored in the table row of a + * primary table may include a primary key and that primary key value + * could be changed by the contract calling the db_update_i64 intrinsic; + * but that does not change the actual primary key of the table row. */ __attribute__((eosio_wasm_import)) void db_update_i64(int32_t iterator, capi_name payer, const void* data, uint32_t len); /** - * - * Remove a record from a primary 64-bit integer index table - * * @brief Remove a record from a primary 64-bit integer index table * @param iterator - Iterator to the table row to remove * @pre `iterator` points to an existing table row in the table @@ -86,9 +82,6 @@ __attribute__((eosio_wasm_import)) void db_remove_i64(int32_t iterator); /** - * - * Get a record in a primary 64-bit integer index table - * * @brief Get a record in a primary 64-bit integer index table * @param iterator - The iterator to the table row containing the record to retrieve * @param data - Pointer to the buffer which will be filled with the retrieved record @@ -111,9 +104,6 @@ __attribute__((eosio_wasm_import)) int32_t db_get_i64(int32_t iterator, const void* data, uint32_t len); /** - * - * Find the table row following the referenced table row in a primary 64-bit integer index table - * * @brief Find the table row following the referenced table row in a primary 64-bit integer index table * @param iterator - The iterator to the referenced table row * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the next table row @@ -135,9 +125,6 @@ __attribute__((eosio_wasm_import)) int32_t db_next_i64(int32_t iterator, uint64_t* primary); /** - * - * Find the table row preceding the referenced table row in a primary 64-bit integer index table - * * @brief Find the table row preceding the referenced table row in a primary 64-bit integer index table * @param iterator - The iterator to the referenced table row * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the previous table row @@ -156,9 +143,6 @@ __attribute__((eosio_wasm_import)) int32_t db_previous_i64(int32_t iterator, uint64_t* primary); /** - * - * Find a table row in a primary 64-bit integer index table by primary key - * * @brief Find a table row in a primary 64-bit integer index table by primary key * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -176,11 +160,8 @@ __attribute__((eosio_wasm_import)) int32_t db_find_i64(capi_name code, uint64_t scope, capi_name table, uint64_t id); /** - * - * Find the table row in a primary 64-bit integer index table that matches the lowerbound condition for a given primary key - * The table row that matches the lowerbound condition is the first table row in the table with the lowest primary key that is >= the given key - * * @brief Find the table row in a primary 64-bit integer index table that matches the lowerbound condition for a given primary key + * @details The table row that matches the lowerbound condition is the first table row in the table with the lowest primary key that is >= the given key * @param code - The name of the owner of the table * @param scope - The scope where the table resides * @param table - The table name @@ -191,11 +172,8 @@ __attribute__((eosio_wasm_import)) int32_t db_lowerbound_i64(capi_name code, uint64_t scope, capi_name table, uint64_t id); /** - * - * Find the table row in a primary 64-bit integer index table that matches the upperbound condition for a given primary key - * The table row that matches the upperbound condition is the first table row in the table with the lowest primary key that is > the given key - * * @brief Find the table row in a primary 64-bit integer index table that matches the upperbound condition for a given primary key + * @details The table row that matches the upperbound condition is the first table row in the table with the lowest primary key that is > the given key * @param code - The name of the owner of the table * @param scope - The scope where the table resides * @param table - The table name @@ -206,9 +184,6 @@ __attribute__((eosio_wasm_import)) int32_t db_upperbound_i64(capi_name code, uint64_t scope, capi_name table, uint64_t id); /** - * - * Get an iterator representing just-past-the-end of the last table row of a primary 64-bit integer index table - * * @brief Get an iterator representing just-past-the-end of the last table row of a primary 64-bit integer index table * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -219,9 +194,6 @@ __attribute__((eosio_wasm_import)) int32_t db_end_i64(capi_name code, uint64_t scope, capi_name table); /** - * - * Store an association of a 64-bit integer secondary key to a primary key in a secondary 64-bit integer index table - * * @brief Store an association of a 64-bit integer secondary key to a primary key in a secondary 64-bit integer index table * @param scope - The scope where the table resides (implied to be within the code of the current receiver) * @param table - The table name @@ -235,9 +207,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx64_store(uint64_t scope, capi_name table, capi_name payer, uint64_t id, const uint64_t* secondary); /** - * - * Update an association for a 64-bit integer secondary key to a primary key in a secondary 64-bit integer index table - * * @brief Update an association for a 64-bit integer secondary key to a primary key in a secondary 64-bit integer index table * @param iterator - The iterator to the table row containing the secondary key association to update * @param payer - The account that pays for the storage costs (use 0 to continue using current payer) @@ -249,9 +218,6 @@ __attribute__((eosio_wasm_import)) void db_idx64_update(int32_t iterator, capi_name payer, const uint64_t* secondary); /** - * - * Remove a table row from a secondary 64-bit integer index table - * * @brief Remove a table row from a secondary 64-bit integer index table * @param iterator - Iterator to the table row to remove * @pre `iterator` points to an existing table row in the table @@ -261,9 +227,6 @@ __attribute__((eosio_wasm_import)) void db_idx64_remove(int32_t iterator); /** - * - * Find the table row following the referenced table row in a secondary 64-bit integer index table - * * @brief Find the table row following the referenced table row in a secondary 64-bit integer index table * @param iterator - The iterator to the referenced table row * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the next table row @@ -275,9 +238,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx64_next(int32_t iterator, uint64_t* primary); /** - * - * Find the table row preceding the referenced table row in a secondary 64-bit integer index table - * * @brief Find the table row preceding the referenced table row in a secondary 64-bit integer index table * @param iterator - The iterator to the referenced table row * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the previous table row @@ -289,9 +249,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx64_previous(int32_t iterator, uint64_t* primary); /** - * - * Find a table row in a secondary 64-bit integer index table by primary key - * * @brief Find a table row in a secondary 64-bit integer index table by primary key * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -305,9 +262,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx64_find_primary(capi_name code, uint64_t scope, capi_name table, uint64_t* secondary, uint64_t primary); /** - * - * Find a table row in a secondary 64-bit integer index table by secondary key - * * @brief Find a table row in a secondary 64-bit integer index table by secondary key * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -321,11 +275,8 @@ __attribute__((eosio_wasm_import)) int32_t db_idx64_find_secondary(capi_name code, uint64_t scope, capi_name table, const uint64_t* secondary, uint64_t* primary); /** - * - * Find the table row in a secondary 64-bit integer index table that matches the lowerbound condition for a given secondary key - * The table row that matches the lowerbound condition is the first table row in the table with the lowest secondary key that is >= the given key - * * @brief Find the table row in a secondary 64-bit integer index table that matches the lowerbound condition for a given secondary key + * @details The table row that matches the lowerbound condition is the first table row in the table with the lowest secondary key that is >= the given key * @param code - The name of the owner of the table * @param scope - The scope where the table resides * @param table - The table name @@ -339,11 +290,8 @@ __attribute__((eosio_wasm_import)) int32_t db_idx64_lowerbound(capi_name code, uint64_t scope, capi_name table, uint64_t* secondary, uint64_t* primary); /** - * - * Find the table row in a secondary 64-bit integer index table that matches the upperbound condition for a given secondary key - * The table row that matches the upperbound condition is the first table row in the table with the lowest secondary key that is > the given key - * * @brief Find the table row in a secondary 64-bit integer index table that matches the upperbound condition for a given secondary key + * @details The table row that matches the upperbound condition is the first table row in the table with the lowest secondary key that is > the given key * @param code - The name of the owner of the table * @param scope - The scope where the table resides * @param table - The table name @@ -357,9 +305,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx64_upperbound(capi_name code, uint64_t scope, capi_name table, uint64_t* secondary, uint64_t* primary); /** - * - * Get an end iterator representing just-past-the-end of the last table row of a secondary 64-bit integer index table - * * @brief Get an end iterator representing just-past-the-end of the last table row of a secondary 64-bit integer index table * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -372,9 +317,6 @@ int32_t db_idx64_end(capi_name code, uint64_t scope, capi_name table); /** - * - * Store an association of a 128-bit integer secondary key to a primary key in a secondary 128-bit integer index table - * * @brief Store an association of a 128-bit integer secondary key to a primary key in a secondary 128-bit integer index table * @param scope - The scope where the table resides (implied to be within the code of the current receiver) * @param table - The table name @@ -388,9 +330,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx128_store(uint64_t scope, capi_name table, capi_name payer, uint64_t id, const uint128_t* secondary); /** - * - * Update an association for a 128-bit integer secondary key to a primary key in a secondary 128-bit integer index table - * * @brief Update an association for a 128-bit integer secondary key to a primary key in a secondary 128-bit integer index table * @param iterator - The iterator to the table row containing the secondary key association to update * @param payer - The account that pays for the storage costs (use 0 to continue using current payer) @@ -402,9 +341,6 @@ __attribute__((eosio_wasm_import)) void db_idx128_update(int32_t iterator, capi_name payer, const uint128_t* secondary); /** - * - * Remove a table row from a secondary 128-bit integer index table - * * @brief Remove a table row from a secondary 128-bit integer index table * @param iterator - Iterator to the table row to remove * @pre `iterator` points to an existing table row in the table @@ -414,9 +350,6 @@ __attribute__((eosio_wasm_import)) void db_idx128_remove(int32_t iterator); /** - * - * Find the table row following the referenced table row in a secondary 128-bit integer index table - * * @brief Find the table row following the referenced table row in a secondary 128-bit integer index table * @param iterator - The iterator to the referenced table row * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the next table row @@ -428,9 +361,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx128_next(int32_t iterator, uint64_t* primary); /** - * - * Find the table row preceding the referenced table row in a secondary 128-bit integer index table - * * @brief Find the table row preceding the referenced table row in a secondary 128-bit integer index table * @param iterator - The iterator to the referenced table row * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the previous table row @@ -442,9 +372,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx128_previous(int32_t iterator, uint64_t* primary); /** - * - * Find a table row in a secondary 128-bit integer index table by primary key - * * @brief Find a table row in a secondary 128-bit integer index table by primary key * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -458,9 +385,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx128_find_primary(capi_name code, uint64_t scope, capi_name table, uint128_t* secondary, uint64_t primary); /** - * - * Find a table row in a secondary 128-bit integer index table by secondary key - * * @brief Find a table row in a secondary 128-bit integer index table by secondary key * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -474,11 +398,8 @@ __attribute__((eosio_wasm_import)) int32_t db_idx128_find_secondary(capi_name code, uint64_t scope, capi_name table, const uint128_t* secondary, uint64_t* primary); /** - * - * Find the table row in a secondary 128-bit integer index table that matches the lowerbound condition for a given secondary key - * The table row that matches the lowerbound condition is the first table row in the table with the lowest secondary key that is >= the given key - * * @brief Find the table row in a secondary 128-bit integer index table that matches the lowerbound condition for a given secondary key + * @details The table row that matches the lowerbound condition is the first table row in the table with the lowest secondary key that is >= the given key * @param code - The name of the owner of the table * @param scope - The scope where the table resides * @param table - The table name @@ -492,11 +413,8 @@ __attribute__((eosio_wasm_import)) int32_t db_idx128_lowerbound(capi_name code, uint64_t scope, capi_name table, uint128_t* secondary, uint64_t* primary); /** - * - * Find the table row in a secondary 128-bit integer index table that matches the upperbound condition for a given secondary key - * The table row that matches the upperbound condition is the first table row in the table with the lowest secondary key that is > the given key - * * @brief Find the table row in a secondary 128-bit integer index table that matches the upperbound condition for a given secondary key + * @details The table row that matches the upperbound condition is the first table row in the table with the lowest secondary key that is > the given key * @param code - The name of the owner of the table * @param scope - The scope where the table resides * @param table - The table name @@ -510,9 +428,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx128_upperbound(capi_name code, uint64_t scope, capi_name table, uint128_t* secondary, uint64_t* primary); /** - * - * Get an end iterator representing just-past-the-end of the last table row of a secondary 128-bit integer index table - * * @brief Get an end iterator representing just-past-the-end of the last table row of a secondary 128-bit integer index table * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -523,9 +438,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx128_end(capi_name code, uint64_t scope, capi_name table); /** - * - * Store an association of a 256-bit secondary key to a primary key in a secondary 256-bit index table - * * @brief Store an association of a 256-bit secondary key to a primary key in a secondary 256-bit index table * @param scope - The scope where the table resides (implied to be within the code of the current receiver) * @param table - The table name @@ -540,9 +452,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx256_store(uint64_t scope, capi_name table, capi_name payer, uint64_t id, const uint128_t* data, uint32_t data_len ); /** - * - * Update an association for a 256-bit secondary key to a primary key in a secondary 256-bit index table - * * @brief Update an association for a 256-bit secondary key to a primary key in a secondary 256-bit index table * @param iterator - The iterator to the table row containing the secondary key association to update * @param payer - The account that pays for the storage costs (use 0 to continue using current payer) @@ -555,9 +464,6 @@ __attribute__((eosio_wasm_import)) void db_idx256_update(int32_t iterator, capi_name payer, const uint128_t* data, uint32_t data_len); /** - * - * Remove a table row from a secondary 256-bit index table - * * @brief Remove a table row from a secondary 256-bit index table * @param iterator - Iterator to the table row to remove * @pre `iterator` points to an existing table row in the table @@ -567,9 +473,6 @@ __attribute__((eosio_wasm_import)) void db_idx256_remove(int32_t iterator); /** - * - * Find the table row following the referenced table row in a secondary 256-bit index table - * * @brief Find the table row following the referenced table row in a secondary 256-bit index table * @param iterator - The iterator to the referenced table row * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the next table row @@ -581,9 +484,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx256_next(int32_t iterator, uint64_t* primary); /** - * - * Find the table row preceding the referenced table row in a secondary 256-bit index table - * * @brief Find the table row preceding the referenced table row in a secondary 256-bit index table * @param iterator - The iterator to the referenced table row * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the previous table row @@ -595,9 +495,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx256_previous(int32_t iterator, uint64_t* primary); /** - * - * Find a table row in a secondary 256-bit index table by primary key - * * @brief Find a table row in a secondary 128-bit integer index table by primary key * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -612,9 +509,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx256_find_primary(capi_name code, uint64_t scope, capi_name table, uint128_t* data, uint32_t data_len, uint64_t primary); /** - * - * Find a table row in a secondary 256-bit index table by secondary key - * * @brief Find a table row in a secondary 256-bit index table by secondary key * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -629,11 +523,8 @@ __attribute__((eosio_wasm_import)) int32_t db_idx256_find_secondary(capi_name code, uint64_t scope, capi_name table, const uint128_t* data, uint32_t data_len, uint64_t* primary); /** - * - * Find the table row in a secondary 256-bit index table that matches the lowerbound condition for a given secondary key - * The table row that matches the lowerbound condition is the first table row in the table with the lowest secondary key that is >= the given key (uses lexicographical ordering on the 256-bit keys) - * * @brief Find the table row in a secondary 256-bit index table that matches the lowerbound condition for a given secondary key + * @details The table row that matches the lowerbound condition is the first table row in the table with the lowest secondary key that is >= the given key (uses lexicographical ordering on the 256-bit keys) * @param code - The name of the owner of the table * @param scope - The scope where the table resides * @param table - The table name @@ -648,11 +539,8 @@ __attribute__((eosio_wasm_import)) int32_t db_idx256_lowerbound(capi_name code, uint64_t scope, capi_name table, uint128_t* data, uint32_t data_len, uint64_t* primary); /** - * - * Find the table row in a secondary 256-bit index table that matches the upperbound condition for a given secondary key - * The table row that matches the upperbound condition is the first table row in the table with the lowest secondary key that is > the given key (uses lexicographical ordering on the 256-bit keys) - * * @brief Find the table row in a secondary 256-bit index table that matches the upperbound condition for a given secondary key + * @details The table row that matches the upperbound condition is the first table row in the table with the lowest secondary key that is > the given key (uses lexicographical ordering on the 256-bit keys) * @param code - The name of the owner of the table * @param scope - The scope where the table resides * @param table - The table name @@ -667,9 +555,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx256_upperbound(capi_name code, uint64_t scope, capi_name table, uint128_t* data, uint32_t data_len, uint64_t* primary); /** - * - * Get an end iterator representing just-past-the-end of the last table row of a secondary 256-bit index table - * * @brief Get an end iterator representing just-past-the-end of the last table row of a secondary 256-bit index table * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -680,9 +565,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx256_end(capi_name code, uint64_t scope, capi_name table); /** - * - * Store an association of a double-precision floating-point secondary key to a primary key in a secondary double-precision floating-point index table - * * @brief Store an association of a double-precision floating-point secondary key to a primary key in a secondary double-precision floating-point index table * @param scope - The scope where the table resides (implied to be within the code of the current receiver) * @param table - The table name @@ -696,9 +578,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx_double_store(uint64_t scope, capi_name table, capi_name payer, uint64_t id, const double* secondary); /** - * - * Update an association for a double-precision floating-point secondary key to a primary key in a secondary double-precision floating-point index table - * * @brief Update an association for a double-precision floating-point secondary key to a primary key in a secondary double-precision floating-point index table * @param iterator - The iterator to the table row containing the secondary key association to update * @param payer - The account that pays for the storage costs (use 0 to continue using current payer) @@ -710,9 +589,6 @@ __attribute__((eosio_wasm_import)) void db_idx_double_update(int32_t iterator, capi_name payer, const double* secondary); /** - * - * Remove a table row from a secondary double-precision floating-point index table - * * @brief Remove a table row from a secondary double-precision floating-point index table * @param iterator - Iterator to the table row to remove * @pre `iterator` points to an existing table row in the table @@ -722,9 +598,6 @@ __attribute__((eosio_wasm_import)) void db_idx_double_remove(int32_t iterator); /** - * - * Find the table row following the referenced table row in a secondary double-precision floating-point index table - * * @brief Find the table row following the referenced table row in a secondary double-precision floating-point index table * @param iterator - The iterator to the referenced table row * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the next table row @@ -736,9 +609,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx_double_next(int32_t iterator, uint64_t* primary); /** - * - * Find the table row preceding the referenced table row in a secondary double-precision floating-point index table - * * @brief Find the table row preceding the referenced table row in a secondary double-precision floating-point index table * @param iterator - The iterator to the referenced table row * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the previous table row @@ -750,9 +620,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx_double_previous(int32_t iterator, uint64_t* primary); /** - * - * Find a table row in a secondary double-precision floating-point index table by primary key - * * @brief Find a table row in a secondary double-precision floating-point index table by primary key * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -766,9 +633,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx_double_find_primary(capi_name code, uint64_t scope, capi_name table, double* secondary, uint64_t primary); /** - * - * Find a table row in a secondary double-precision floating-point index table by secondary key - * * @brief Find a table row in a secondary double-precision floating-point index table by secondary key * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -782,11 +646,8 @@ __attribute__((eosio_wasm_import)) int32_t db_idx_double_find_secondary(capi_name code, uint64_t scope, capi_name table, const double* secondary, uint64_t* primary); /** - * - * Find the table row in a secondary double-precision floating-point index table that matches the lowerbound condition for a given secondary key - * The table row that matches the lowerbound condition is the first table row in the table with the lowest secondary key that is >= the given key - * * @brief Find the table row in a secondary double-precision floating-point index table that matches the lowerbound condition for a given secondary key + * @details The table row that matches the lowerbound condition is the first table row in the table with the lowest secondary key that is >= the given key * @param code - The name of the owner of the table * @param scope - The scope where the table resides * @param table - The table name @@ -800,11 +661,8 @@ __attribute__((eosio_wasm_import)) int32_t db_idx_double_lowerbound(capi_name code, uint64_t scope, capi_name table, double* secondary, uint64_t* primary); /** - * - * Find the table row in a secondary double-precision floating-point index table that matches the upperbound condition for a given secondary key - * The table row that matches the upperbound condition is the first table row in the table with the lowest secondary key that is > the given key - * * @brief Find the table row in a secondary double-precision floating-point index table that matches the upperbound condition for a given secondary key + * @details The table row that matches the upperbound condition is the first table row in the table with the lowest secondary key that is > the given key * @param code - The name of the owner of the table * @param scope - The scope where the table resides * @param table - The table name @@ -818,9 +676,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx_double_upperbound(capi_name code, uint64_t scope, capi_name table, double* secondary, uint64_t* primary); /** - * - * Get an end iterator representing just-past-the-end of the last table row of a secondary double-precision floating-point index table - * * @brief Get an end iterator representing just-past-the-end of the last table row of a secondary double-precision floating-point index table * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -831,9 +686,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx_double_end(capi_name code, uint64_t scope, capi_name table); /** - * - * Store an association of a quadruple-precision floating-point secondary key to a primary key in a secondary quadruple-precision floating-point index table - * * @brief Store an association of a quadruple-precision floating-point secondary key to a primary key in a secondary quadruple-precision floating-point index table * @param scope - The scope where the table resides (implied to be within the code of the current receiver) * @param table - The table name @@ -847,9 +699,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx_long_double_store(uint64_t scope, capi_name table, capi_name payer, uint64_t id, const long double* secondary); /** - * - * Update an association for a quadruple-precision floating-point secondary key to a primary key in a secondary quadruple-precision floating-point index table - * * @brief Update an association for a quadruple-precision floating-point secondary key to a primary key in a secondary quadruple-precision floating-point index table * @param iterator - The iterator to the table row containing the secondary key association to update * @param payer - The account that pays for the storage costs (use 0 to continue using current payer) @@ -861,9 +710,6 @@ __attribute__((eosio_wasm_import)) void db_idx_long_double_update(int32_t iterator, capi_name payer, const long double* secondary); /** - * - * Remove a table row from a secondary quadruple-precision floating-point index table - * * @brief Remove a table row from a secondary quadruple-precision floating-point index table * @param iterator - Iterator to the table row to remove * @pre `iterator` points to an existing table row in the table @@ -873,10 +719,7 @@ __attribute__((eosio_wasm_import)) void db_idx_long_double_remove(int32_t iterator); /** - * - * Find the table row following the referenced table row in a secondary quadruple-precision floating-point index table - * - * @brief Find the table row following the referenced table row in a secondary quadruple-precision floating-point index table + * @brief Find the table row following the referenced table row in a secondary quadruple-precision floating-point index table * @param iterator - The iterator to the referenced table row * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the next table row * @return iterator to the table row following the referenced table row (or the end iterator of the table if the referenced table row is the last one in the table) @@ -887,9 +730,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx_long_double_next(int32_t iterator, uint64_t* primary); /** - * - * Find the table row preceding the referenced table row in a secondary quadruple-precision floating-point index table - * * @brief Find the table row preceding the referenced table row in a secondary quadruple-precision floating-point index table * @param iterator - The iterator to the referenced table row * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the previous table row @@ -901,9 +741,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx_long_double_previous(int32_t iterator, uint64_t* primary); /** - * - * Find a table row in a secondary quadruple-precision floating-point index table by primary key - * * @brief Find a table row in a secondary quadruple-precision floating-point index table by primary key * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -917,9 +754,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx_long_double_find_primary(capi_name code, uint64_t scope, capi_name table, long double* secondary, uint64_t primary); /** - * - * Find a table row in a secondary quadruple-precision floating-point index table by secondary key - * * @brief Find a table row in a secondary quadruple-precision floating-point index table by secondary key * @param code - The name of the owner of the table * @param scope - The scope where the table resides @@ -933,11 +767,8 @@ __attribute__((eosio_wasm_import)) int32_t db_idx_long_double_find_secondary(capi_name code, uint64_t scope, capi_name table, const long double* secondary, uint64_t* primary); /** - * - * Find the table row in a secondary quadruple-precision floating-point index table that matches the lowerbound condition for a given secondary key - * The table row that matches the lowerbound condition is the first table row in the table with the lowest secondary key that is >= the given key - * * @brief Find the table row in a secondary quadruple-precision floating-point index table that matches the lowerbound condition for a given secondary key + * @details The table row that matches the lowerbound condition is the first table row in the table with the lowest secondary key that is >= the given key * @param code - The name of the owner of the table * @param scope - The scope where the table resides * @param table - The table name @@ -951,11 +782,8 @@ __attribute__((eosio_wasm_import)) int32_t db_idx_long_double_lowerbound(capi_name code, uint64_t scope, capi_name table, long double* secondary, uint64_t* primary); /** - * - * Find the table row in a secondary quadruple-precision floating-point index table that matches the upperbound condition for a given secondary key - * The table row that matches the upperbound condition is the first table row in the table with the lowest secondary key that is > the given key - * * @brief Find the table row in a secondary quadruple-precision floating-point index table that matches the upperbound condition for a given secondary key + * @details The table row that matches the upperbound condition is the first table row in the table with the lowest secondary key that is > the given key * @param code - The name of the owner of the table * @param scope - The scope where the table resides * @param table - The table name @@ -969,9 +797,6 @@ __attribute__((eosio_wasm_import)) int32_t db_idx_long_double_upperbound(capi_name code, uint64_t scope, capi_name table, long double* secondary, uint64_t* primary); /** - * - * Get an end iterator representing just-past-the-end of the last table row of a secondary quadruple-precision floating-point index table - * * @brief Get an end iterator representing just-past-the-end of the last table row of a secondary quadruple-precision floating-point index table * @param code - The name of the owner of the table * @param scope - The scope where the table resides diff --git a/libraries/eosiolib/capi/eosio/privileged.h b/libraries/eosiolib/capi/eosio/privileged.h index 523bf436a3..8628c00769 100644 --- a/libraries/eosiolib/capi/eosio/privileged.h +++ b/libraries/eosiolib/capi/eosio/privileged.h @@ -105,13 +105,23 @@ void set_blockchain_parameters_packed( char* data, uint32_t datalen ); __attribute__((eosio_wasm_import)) uint32_t get_blockchain_parameters_packed( char* data, uint32_t datalen ); +/** + * Set the KV parameters + * + * @param data - pointer to KV parameters packed as bytes + * @param datalen - size of the packed KV parameters + * @pre `data` is a valid pointer to a range of memory at least `datalen` bytes long that contains packed KV params data + */ +__attribute__((eosio_wasm_import)) +void set_kv_parameters_packed( const char* data, uint32_t datalen ); + /** * Pre-activate protocol feature * * @param feature_digest - digest of the protocol feature to pre-activate */ __attribute__((eosio_wasm_import)) -void preactivate_feature( const capi_checksum256* feature_digest ); +void preactivate_feature( const struct capi_checksum256* feature_digest ); #ifdef __cplusplus } diff --git a/libraries/eosiolib/capi/eosio/system.h b/libraries/eosiolib/capi/eosio/system.h index b7b7cb63a1..76c342f9d5 100644 --- a/libraries/eosiolib/capi/eosio/system.h +++ b/libraries/eosiolib/capi/eosio/system.h @@ -74,7 +74,7 @@ void eosio_exit( int32_t code ); * @return time in microseconds from 1970 of the current block */ __attribute__((eosio_wasm_import)) -uint64_t current_time(); +uint64_t current_time( void ); /** * Check if specified protocol feature has been activated @@ -83,7 +83,7 @@ uint64_t current_time(); * @return true if the specified protocol feature has been activated, false otherwise */ __attribute__((eosio_wasm_import)) -bool is_feature_activated( const capi_checksum256* feature_digest ); +bool is_feature_activated( const struct capi_checksum256* feature_digest ); /** * Return name of account that sent current inline action @@ -91,7 +91,7 @@ bool is_feature_activated( const capi_checksum256* feature_digest ); * @return name of account that sent the current inline action (empty name if not called from inline action) */ __attribute__((eosio_wasm_import)) -capi_name get_sender(); +capi_name get_sender( void ); #ifdef __cplusplus } diff --git a/libraries/eosiolib/capi/eosio/transaction.h b/libraries/eosiolib/capi/eosio/transaction.h index d414b23eae..3fce87fb2c 100644 --- a/libraries/eosiolib/capi/eosio/transaction.h +++ b/libraries/eosiolib/capi/eosio/transaction.h @@ -47,7 +47,7 @@ extern "C" { * @param replace_existing - f this is `0` then if the provided sender_id is already in use by an in-flight transaction from this contract, which will be a failing assert. If `1` then transaction will atomically cancel/replace the inflight transaction */ __attribute__((eosio_wasm_import)) -void send_deferred(const uint128_t& sender_id, capi_name payer, const char *serialized_transaction, size_t size, uint32_t replace_existing = 0); +void send_deferred(const uint128_t* sender_id, capi_name payer, const char *serialized_transaction, size_t size, uint32_t replace_existing); /** * Cancels a deferred transaction. @@ -69,7 +69,7 @@ void send_deferred(const uint128_t& sender_id, capi_name payer, const char *seri * @endcode */ __attribute__((eosio_wasm_import)) -int cancel_deferred(const uint128_t& sender_id); +int cancel_deferred(const uint128_t* sender_id); /** * Access a copy of the currently executing transaction. @@ -89,7 +89,7 @@ size_t read_transaction(char *buffer, size_t size); * @return size of the currently executing transaction */ __attribute__((eosio_wasm_import)) -size_t transaction_size(); +size_t transaction_size( void ); /** * Gets the block number used for TAPOS on the currently executing transaction. @@ -102,7 +102,7 @@ size_t transaction_size(); * @endcode */ __attribute__((eosio_wasm_import)) -int tapos_block_num(); +int tapos_block_num( void ); /** * Gets the block prefix used for TAPOS on the currently executing transaction. @@ -115,7 +115,7 @@ int tapos_block_num(); * @endcode */ __attribute__((eosio_wasm_import)) -int tapos_block_prefix(); +int tapos_block_prefix( void ); /** * Gets the expiration of the currently executing transaction. @@ -129,7 +129,7 @@ int tapos_block_prefix(); * @endcode */ __attribute__((eosio_wasm_import)) -uint32_t expiration(); +uint32_t expiration( void ); /** * Retrieves the indicated action from the active transaction. diff --git a/libraries/eosiolib/contracts/eosio/action.hpp b/libraries/eosiolib/contracts/eosio/action.hpp index 85ca78edb4..195c85f842 100644 --- a/libraries/eosiolib/contracts/eosio/action.hpp +++ b/libraries/eosiolib/contracts/eosio/action.hpp @@ -433,9 +433,9 @@ namespace eosio { } /** - * Wrapper for an action object. + * Wrapper for an action object. * - * @brief Used to wrap an a particular action to simplify the process of other contracts sending inline actions to "wrapped" action. + * @brief Used to wrap an a particular action to simplify the process of other contracts sending inline actions to "wrapped" action. * Example: * @code * // defined by contract writer of the actions @@ -576,7 +576,7 @@ INLINE_ACTION_SENDER3( CONTRACT_CLASS, NAME, ::eosio::name(#NAME) ) * Send an inline-action from inside a contract. * * @brief A macro to simplify calling inline actions - * @details The send inline action macro is intended to simplify the process of calling inline actions. When calling new actions from existing actions + * @details The send inline action macro is intended to simplify the process of calling inline actions. When calling new actions from existing actions * EOSIO supports two communication models, inline and deferred. Inline actions are executed as part of the current transaction. This macro * creates an @ref action using the supplied parameters and automatically calls action.send() on this newly created action. * @@ -584,15 +584,15 @@ INLINE_ACTION_SENDER3( CONTRACT_CLASS, NAME, ::eosio::name(#NAME) ) * @code * SEND_INLINE_ACTION( *this, transfer, {st.issuer,N(active)}, {st.issuer, to, quantity, memo} ); * @endcode - * - * The example above is taken from eosio.token. - * This example: - * uses the passed in, dereferenced `this` pointer, to call this.get_self() i.e. the eosio.token contract; - * calls the eosio.token::transfer() action; + * + * The example above is taken from eosio.token. + * This example: + * uses the passed in, dereferenced `this` pointer, to call this.get_self() i.e. the eosio.token contract; + * calls the eosio.token::transfer() action; * uses the active permission of the "issuer" account; - * uses parameters st.issuer, to, quantity and memo. + * uses parameters st.issuer, to, quantity and memo. * This macro creates an action struct used to 'send()' (call) transfer(account_name from, account_name to, asset quantity, string memo) - * + * * @param CONTRACT - The contract to call, which contains the action being sent, maps to the @ref account * @param NAME - The name of the action to be called, maps to a @ref name * @param ... - The authorising permission, maps to an @ref authorization , followed by the parameters of the action, maps to a @ref data. diff --git a/libraries/eosiolib/contracts/eosio/eosio.hpp b/libraries/eosiolib/contracts/eosio/eosio.hpp index 1208ffd5e1..dc8a78790b 100644 --- a/libraries/eosiolib/contracts/eosio/eosio.hpp +++ b/libraries/eosiolib/contracts/eosio/eosio.hpp @@ -5,6 +5,7 @@ #pragma once #include "action.hpp" #include "../../core/eosio/print.hpp" +#include "map.hpp" #include "multi_index.hpp" #include "dispatcher.hpp" #include "contract.hpp" diff --git a/libraries/eosiolib/contracts/eosio/map.hpp b/libraries/eosiolib/contracts/eosio/map.hpp new file mode 100644 index 0000000000..bb4275372a --- /dev/null +++ b/libraries/eosiolib/contracts/eosio/map.hpp @@ -0,0 +1,686 @@ +#pragma once +#include "../../core/eosio/context.hpp" +#include "../../core/eosio/datastream.hpp" +#include "../../core/eosio/name.hpp" +#include "../../core/eosio/varint.hpp" + +#include "../../core/eosio/key_utils.hpp" + +#include +#include +#include +#include + +/** + * @defgroup keyvaluemap Key Value Map + * @ingroup contracts + */ + +namespace eosio::kv { + namespace internal_use_do_not_use { + extern "C" { + __attribute__((eosio_wasm_import)) + int64_t kv_erase(uint64_t contract, const char* key, uint32_t key_size); + + __attribute__((eosio_wasm_import)) + int64_t kv_set(uint64_t contract, const char* key, uint32_t key_size, const char* value, uint32_t value_size, uint64_t payer); + + __attribute__((eosio_wasm_import)) + bool kv_get(uint64_t contract, const char* key, uint32_t key_size, uint32_t& value_size); + + __attribute__((eosio_wasm_import)) + uint32_t kv_get_data(uint32_t offset, char* data, uint32_t data_size); + + __attribute__((eosio_wasm_import)) + uint32_t kv_it_create(uint64_t contract, const char* prefix, uint32_t size); + + __attribute__((eosio_wasm_import)) + void kv_it_destroy(uint32_t itr); + + __attribute__((eosio_wasm_import)) + int32_t kv_it_status(uint32_t itr); + + __attribute__((eosio_wasm_import)) + int32_t kv_it_compare(uint32_t itr_a, uint32_t itr_b); + + __attribute__((eosio_wasm_import)) + int32_t kv_it_key_compare(uint32_t itr, const char* key, uint32_t size); + + __attribute__((eosio_wasm_import)) + int32_t kv_it_move_to_end(uint32_t itr); + + __attribute__((eosio_wasm_import)) + int32_t kv_it_next(uint32_t itr, uint32_t& found_key_size, uint32_t& found_value_size); + + __attribute__((eosio_wasm_import)) + int32_t kv_it_prev(uint32_t itr, uint32_t& found_key_size, uint32_t& found_value_size); + + __attribute__((eosio_wasm_import)) + int32_t kv_it_lower_bound(uint32_t itr, const char* key, uint32_t size, uint32_t& found_key_size, uint32_t& found_value_size); + + __attribute__((eosio_wasm_import)) + int32_t kv_it_key(uint32_t itr, uint32_t offset, char* dest, uint32_t size, uint32_t& actual_size); + + __attribute__((eosio_wasm_import)) + int32_t kv_it_value(uint32_t itr, uint32_t offset, char* dest, uint32_t size, uint32_t& actual_size); + } + } + + // tag used by some of the types to delineate overloads between key_type (std::string) and the map's key type + struct packed_tag {}; + + namespace detail { + static inline void increment_bytes(key_type& kt) { + // this will terminate without have to lengthen by one byte given the way prefixes are formed + // need to re-evaluate if that changes + for (std::size_t i=kt.size()-1; i >= 0; i--) { + uint16_t v = kt[i]+1; + kt[i] = v; + if (v <= 0xFF) + break; + } + } + + struct packed_view { + packed_view() = default; + constexpr inline packed_view(char* p, std::size_t s) + : ptr(p), sz(s) {} + + using iterator = char*; + using const_iterator = const char*; + + constexpr inline char* data() { return ptr; } + constexpr inline const char* data() const { return ptr; } + constexpr inline std::size_t size() const { return sz; } + constexpr inline const_iterator begin() const { return data(); } + constexpr inline const_iterator end() const { return data() + size(); } + char* ptr; + std::size_t sz; + }; + + inline uint32_t itr_create(name contract, std::string_view prefix) { + return internal_use_do_not_use::kv_it_create(contract.value, prefix.data(), prefix.size()); + } + + inline void itr_destroy(uint32_t itr) { internal_use_do_not_use::kv_it_destroy(itr); } + inline int32_t itr_status(uint32_t itr) { return internal_use_do_not_use::kv_it_status(itr); } + inline int32_t itr_compare(uint32_t a, uint32_t b) { return internal_use_do_not_use::kv_it_compare(a, b); } + inline int32_t itr_key_compare(uint32_t itr, std::string_view k) { return internal_use_do_not_use::kv_it_key_compare(itr, k.data(), k.size()); } + inline int32_t itr_move_to_end(uint32_t itr) { return internal_use_do_not_use::kv_it_move_to_end(itr); } + inline int32_t itr_key(uint32_t itr, char* dest, uint32_t size, uint32_t& actual_size) { return internal_use_do_not_use::kv_it_key(itr, 0, dest, size, actual_size); } + inline int32_t itr_value(uint32_t itr, char* dest, uint32_t size, uint32_t& actual_size) { return internal_use_do_not_use::kv_it_value(itr, 0, dest, size, actual_size); } + + inline int32_t itr_next(uint32_t itr, uint32_t& key_size, uint32_t& val_size) { + return internal_use_do_not_use::kv_it_next(itr, key_size, val_size); + } + inline int32_t itr_next(uint32_t itr) { + uint32_t k,v; + return internal_use_do_not_use::kv_it_next(itr, k, v); + } + inline int32_t itr_prev(uint32_t itr, uint32_t& key_size, uint32_t& val_size) { + return internal_use_do_not_use::kv_it_prev(itr, key_size, val_size); + } + inline int32_t itr_prev(uint32_t itr) { + uint32_t k,v; + return internal_use_do_not_use::kv_it_prev(itr, k, v); + } + inline int32_t itr_lower_bound(uint32_t itr, std::string_view k, uint32_t& key_size, uint32_t& val_size) { + return internal_use_do_not_use::kv_it_lower_bound(itr, k.data(), k.size(), key_size, val_size); + } + inline int32_t itr_lower_bound(uint32_t itr, std::string_view k) { + uint32_t _k,_v; + return internal_use_do_not_use::kv_it_lower_bound(itr, k.data(), k.size(), _k, _v); + } + inline int32_t itr_lower_bound(uint32_t itr) { + uint32_t _k,_v; + return internal_use_do_not_use::kv_it_lower_bound(itr, "", 0, _k, _v); + } + + /** + * @ingroup keyvaluemap + * + * @brief This struct represents the data type stored in map. + * @details You will use the set of functions and operations associated + * with this type to access the items stored in the map. + * + * Note that this type in `eosio::kv::map` is defined as `eosio::kv::map::elem_t`. + */ + template + struct elem { + using key_t = typename KV::key_t; + using value_t = typename KV::value_t; + elem() = default; + constexpr inline elem(const key_t& k, value_t v, name p=current_context_contract()) + : key(KV::full_key(k)), value(std::move(v)), payer(p) {} + + inline elem(key_type k, value_t v, name p, packed_tag) + : key(std::move(k)), value(std::move(v)), payer(p) {} + + constexpr inline bool operator==(const elem& e) const { + return std::tie(key, value) == std::tie(e.key, e.value); + } + + key_type& first() { return key; } + const key_type& first() const { return key; } + value_t& second() { return value; } + const value_t& second() const { return value; } + + key_type key; + value_t value; + name payer; + }; + + /** + * @ingroup keyvaluemap + * + * @brief This struct represents the iterator for the `eosio::kv::map` data type. + * @details You will use the set of functions and operations associated with + * this type to iterate through values in the map and reference them. + * + * Note that this iterator type in `eosio::kv::map` is defined as `eosio::kv::map::iterator_t`. + * There is also a reverse iterator available, it is defined as `eosio::kv::map::reverse_iterator_t`. + * The only difference between iterator_t and reverse_iterator_t is the direction the ++ and -- operators go. + */ + template + struct iterator { + using elem_t = elem; + using value_t = typename KV::value_t; + constexpr static inline uint32_t invalidated_iterator = std::numeric_limits::max(); + + enum class status { ok = 0, erased = -1, end = -2 }; + + template + constexpr inline static bool query_status(status stat) { + using namespace internal_use_do_not_use; + return Stat == stat; + } + template + constexpr inline static bool query_status(int32_t stat) { + using namespace internal_use_do_not_use; + return Stat == static_cast(stat); + } + + /** + * @ingroup keyvaluemap + * + * @brief Constructor for the iterator type. + * @param owner This is the owner of the table object. + */ + inline iterator(name owner) + : element(), + handle(itr_create(owner, {KV::prefix().data(), KV::prefix().size()})) {} + + iterator(const iterator&) = delete; + iterator& operator=(const iterator&) = delete; + + inline iterator(iterator&& o) + : element(o.element), + handle(std::exchange(o.handle, invalidated_iterator)), + current_status(o.current_status) {} + + inline iterator(iterator&& o) + : element(o.element), + handle(std::exchange(o.handle, invalidated_iterator)), + current_status(o.current_status) {} + + inline iterator& operator=(iterator&& o) { + element = o.element; + if (handle != invalidated_iterator) + itr_destroy(handle); + handle = std::exchange(o.handle, invalidated_iterator); + current_status = o.current_status; + return *this; + } + + inline iterator& operator=(iterator&& o) { + element = o.element; + if (handle != invalidated_iterator) + itr_destroy(handle); + handle = std::exchange(o.handle, invalidated_iterator); + current_status = o.current_status; + return *this; + } + + ~iterator() { + if (handle != invalidated_iterator) + itr_destroy(handle); + } + + /** + * @ingroup keyvaluemap + * + * @brief Utility function which returns true if iterator is valid or false + * if iterator is not valid. An iterator is not valid if is pointing at + * `end` or one past the last or first element. + */ + inline bool is_valid() const { return query_status(current_status); } + + /** + * @ingroup keyvaluemap + * + * @brief Function to advance the iterator to the beginning of the map's key value pairs. + */ + iterator& seek_to_begin() { + current_status = static_cast(itr_lower_bound(handle)); + return *this; + } + + /** + * @ingroup keyvaluemap + * + * @brief Function to advance the iterator to the last element of the map's key value pairs. + */ + iterator& seek_to_last() { + current_status = static_cast(itr_move_to_end(handle)); + current_status = static_cast(itr_prev(handle)); + return *this; + } + + /** + * @ingroup keyvaluemap + * + * @brief Function to advance the iterator to the element past the last element of the map's key value pairs. + */ + iterator& seek_to_end() { + current_status = static_cast(itr_move_to_end(handle)); + return *this; + } + + /** + * @ingroup keyvaluemap + * + * @brief This will take a `key_type` key and find the value that is equal to or greater than that key. + * @details If no element is greater than or equal to the key the iterator will now hold `end`. + * @param key This is the key which you wish to query with. + */ + iterator& lower_bound(const key_type& k) { + current_status = static_cast(itr_lower_bound(handle, {k.data(), k.size()})); + return *this; + } + + /** + * @ingroup keyvaluemap + * + * @brief This will take a `key_type` key and find the value that exactly matches that key. + * @details If no element is equal to the key the iterator will now hold `end`. + * @param key This is the key which you wish to query with. + */ + iterator& find(const key_type& k) { + lower_bound(k); + if (itr_key_compare(handle, {k.data(), k.size()}) != 0) + seek_to_end(); + return *this; + } + + inline elem_t& operator*() { + materialize(); + return element; + } + inline const elem_t& operator*() const { + materialize(); + return element; + } + inline elem_t* operator->() { + materialize(); + return &element; + } + inline const elem_t* operator->() const { + materialize(); + return &element; + } + + /** + * @ingroup keyvaluemap + * + * @brief Function to increment the iterator to the next element (sorted in lexicographic order). + * @details Note this is the prefix operator, i.e. `++it`, the `it++` operator is explicitly missing because + * of performance issues. If you increment past the last element this iterator will then be invalid and point to `end`. + */ + iterator& operator++() { + if constexpr (Reverse) { + current_status = static_cast(itr_prev(handle)); + check(query_status(current_status), "incrementing past end or an erased iterator"); + } else { + check(query_status(current_status), "incrementing past end or an erased iterator"); + current_status = static_cast(itr_next(handle)); + } + if (query_status(current_status)) + seek_to_end(); + return *this; + } + + /** + * @ingroup keyvaluemap + * + * @brief Function to decrement the iterator to the next element (sorted in lexicographic order). + * @details Note this is the prefix operator, i.e. `--it`, the `it--` operator is explicitly missing because + * of performance issues. If you decrement past the first element this iterator will then be invalid and point to `end`. + */ + iterator& operator--() { + if constexpr (Reverse) { + check(query_status(current_status), "decrementing past end or an erased iterator"); + current_status = static_cast(itr_next(handle)); + } else { + current_status = static_cast(itr_prev(handle)); + check(query_status(current_status), "decrementing past end or an erased iterator"); + } + if (query_status(current_status)) + seek_to_end(); + return *this; + } + + /** + * @ingroup keyvaluemap + * + * @brief Function to test equality. + */ + inline bool operator==(const iterator& o) const { + // ignoring key_size and value_size as they shouldn't play a role in equality + return (std::tie(handle, current_status) == std::tie(o.handle, o.current_status)) || + (query_status(current_status) && query_status(o.current_status)) || + !itr_compare(handle, o.handle); + } + + /** + * @ingroup keyvaluemap + * + * @brief Function to test inequality. + */ + inline bool operator!=(const iterator& o) const { return !((*this) == o); } + + void materialize() const { + using namespace internal_use_do_not_use; + uint32_t sz; + itr_key(handle, nullptr, 0, sz); + element.key.resize(sz); + check(query_status(itr_key(handle, element.key.data(), element.key.size(), sz)), "failure getting key"); + + itr_value(handle, nullptr, 0, sz); + auto val_bytes = KV::get_tmp_buffer(sz); + auto status = itr_value(handle, val_bytes.data(), val_bytes.size(), sz); + check(query_status(status), "failure getting value"); + unpack(element.value, val_bytes.data(), val_bytes.size()); + } + + mutable elem_t element; + uint32_t handle; + status current_status = status::ok; + }; + } // namespace eosio::kv::detail + + /** + * @ingroup keyvaluemap + * + * @brief Defines an EOSIO Key Value Map + * @details EOSIO Key Value API provides a C++ interface to the EOSIO Key Value database. + * The Key Value Map offered by the KV API serves as a storage location which is organized + * as a sorted associative container that contains key-value pairs with unique keys. + * Keys are sorted lexicographically. Search, removal, and insertion operations have + * logarithmic complexity, O(log(n)). 'KV Map' is designed to offer a comparable + * interface to std::map template class. + * + * @tparam K - the type of the data stored as the key of the map + * @tparam V - the type of the data stored as the value of the map + */ + template + class [[eosio::table]] map { + public: + constexpr static inline uint8_t magic = 1; + constexpr static inline name table_name = name{static_cast(TableName)}; + using key_t = K; + using value_t = V; + using self_t = map; + + static const key_type& prefix() { + static key_type prfx = eosio::detail::const_pack(magic, table_name, index_name); + return prfx; + } + + static key_type full_key(const key_t& k) { return prefix() + convert_to_key(k); } + + using elem_t = detail::elem; + using iterator_t = detail::iterator; + using reverse_iterator_t = detail::iterator; + + struct writable_wrapper { + writable_wrapper(key_type k, value_t v, name p, name o=current_context_contract()) + : element(std::move(k), std::move(v), p, packed_tag{}), owner(o) {} + + explicit operator value_t&() { return element.value; } + operator value_t() const { return element.value; } + + writable_wrapper& operator=(const value_t& o) { + map{owner}.set(element.key, o, element.payer, packed_tag{}); + element.value = o; + return *this; + } + + writable_wrapper& operator=(value_t&& o) { + map{owner}.set(element.key, o, element.payer, packed_tag{}); + element.value = std::move(o); + return *this; + } + + elem_t element; + name owner; + }; + + inline map(name owner=current_context_contract()) + : owner(owner) {} + + /** + * Basic constructor of N elements. This will bill the implicit owner of the table by default. + */ + inline map(std::initializer_list l) { + for ( const auto& e : l ) { + set(e.key, e.value, e.payer, packed_tag{}); + } + } + + /** + * Basic constructor of N elements. This will bill the owner explicitly passed in. + */ + inline map(name owner, std::initializer_list l) + : owner(owner) { + for ( const auto& e : l ) { + set(e.key, e.value, e.payer, packed_tag{}); + } + } + + writable_wrapper operator[](const std::pair& key_payer) { + auto v = get(key_payer.first); + if (std::get<0>(v)) + return {std::get<1>(v), *std::get<0>(v), key_payer.second, owner}; + + set(std::get<1>(v), value_t{}, owner, packed_tag{}); + return {std::move(std::get<1>(v)), value_t{}, key_payer.second, owner}; + } + + writable_wrapper operator[](const key_t& k) { + return (*this)[std::pair{k, owner}]; + } + + template + writable_wrapper at(Key&& k, name payer=current_context_contract()) { + auto v = get(std::forward(k)); + check(std::get<0>(v), "key not found"); + return {std::get<1>(v), *std::get<0>(v), payer, owner}; + } + + inline iterator_t begin() const { + iterator_t it = {owner}; + it.seek_to_begin(); + return it; + } + + inline iterator_t end() const { + iterator_t it = {owner}; + it.seek_to_end(); + return it; + } + + inline reverse_iterator_t rbegin() const { + reverse_iterator_t it = {owner}; + it.seek_to_last(); + return it; + } + + inline reverse_iterator_t rend() const { + return end(); + } + + inline bool empty() const { + iterator_t it = {owner}; + it.lower_bound(key_type{""}); + return it == end(); + } + + /** + * @ingroup keyvaluemap + * + * @brief This will take a `key_t` key and find the value that exactly matches that key. + * @details If no element is equal to the key the iterator will now hold `end`. + * @param key This is the key which you wish to query with. + */ + iterator_t find(const key_t& k) const { + auto fk = full_key(k); + iterator_t it = {owner}; + it.find(fk); + return it; + } + + void erase(const key_t& k) { + using namespace internal_use_do_not_use; + auto fk = full_key(k); + kv_erase(owner.value, fk.data(), fk.size()); + } + + iterator_t erase(const iterator_t& it) { + using namespace internal_use_do_not_use; + auto key = *it.key; + ++it; + kv_erase(owner, key.data(), key.size()); + return it; + } + + /** + * @ingroup keyvaluemap + * + * @brief This will take a `key_t` key and find the value that is equal to or greater than that key. + * @details If no element is greater than or equal to the key the iterator will now hold `end`. + * @param key This is the key which you wish to query with. + */ + inline iterator_t lower_bound(const key_t& k) const { + iterator_t it = {owner}; + it.lower_bound(full_key(k)); + return it; + } + + /** + * @ingroup keyvaluemap + * + * @brief This will take a `key_t` key and find the value that is strictly greater than that key. + * @details If no element is strictly greater than the key the iterator will now hold `end`. + * @param key This is the key which you wish to query with. + */ + inline iterator_t upper_bound(const key_t& k) const { + auto fk = full_key(k); + detail::increment_bytes(fk); // add '1' to the last byte, this should get us to the next value up + iterator_t it = {owner}; + it.lower_bound(fk); + return it; + } + + std::pair equal_range(const key_t& k) const { return {lower_bound(k), upper_bound(k)}; } + + std::vector ranged_slice(const key_t& l, const key_t& h) { + std::vector ret; + + for (auto itr = lower_bound(l); itr != lower_bound(h); ++itr) { + ret.emplace_back(std::move(*itr)); + } + + return ret; + } + + inline bool contains(const key_t& k) const { + using namespace internal_use_do_not_use; + uint32_t _vs; + auto fk = full_key(k); + return static_cast(kv_get(owner.value, fk.data(), fk.size(), _vs)); + } + + bool raw_write(const key_t& k, std::string_view bytes, name payer=current_context_contract()) const { + using namespace internal_use_do_not_use; + const auto& fk = full_key(k); + auto wrote = kv_set(owner.value, fk.data(), fk.size(), bytes.data(), bytes.size(), payer.value); + return wrote == bytes.size(); + } + + friend iterator_t; + + static detail::packed_view get_tmp_buffer(std::size_t size_needed=0) { + constexpr std::size_t max_size = 512; + static char static_data[max_size]; + static std::vector dynamic_data = {0}; + if (size_needed > max_size) { + if (dynamic_data.size() < size_needed) + dynamic_data.resize(size_needed); + return {dynamic_data.data(), size_needed}; + } else { + return {&static_data[0], size_needed}; + } + } + + protected: + constexpr static inline name index_name = name{IndexName}; + + writable_wrapper at(const key_type& bytes, name payer=current_context_contract()) { + auto v = get(bytes, packed_tag{}); + check(std::get<0>(v), "key not found"); + return {std::get<1>(v), *std::get<0>(v), payer, owner}; + } + + std::tuple get(key_type k, packed_tag) { + using namespace internal_use_do_not_use; + uint32_t sz; + if (!kv_get(owner.value, k.data(), k.size(), sz)) + return {nullptr, std::move(k)}; + + auto val_bytes = get_tmp_buffer(sz); + check(kv_get_data(0, val_bytes.data(), val_bytes.size()) == val_bytes.size(), "kv get internal failure"); + temp = unpack(val_bytes.data(), val_bytes.size()); + return {&temp, std::move(k)}; + } + + std::tuple get(const key_t& k) { + return get(full_key(k), packed_tag{}); + } + + inline bool set(const key_type& k, const value_t& v, name payer, packed_tag) const { + using namespace internal_use_do_not_use; + const auto& packed_value = pack_value(v); + auto wrote = kv_set(owner.value, k.data(), k.size(), packed_value.data(), packed_value.size(), payer.value); + return wrote == packed_value.size(); + } + + inline bool set(const key_t& k, const value_t& v, name payer) const { + return set(full_key(k), v, payer, packed_tag{}); + } + + template + inline detail::packed_view pack_value(Value&& v) const { + auto pv = get_tmp_buffer(pack_size(v)); + datastream ds(pv.data(), pv.size()); + ds << std::forward(v); + return pv; + } + + private: + name owner = current_context_contract(); + value_t temp; // used for + + CDT_REFLECT(owner, temp); + }; + +} // namespace eosio::kv diff --git a/libraries/eosiolib/contracts/eosio/multi_index.hpp b/libraries/eosiolib/contracts/eosio/multi_index.hpp index 1a288bd9d5..bdc72caf1f 100644 --- a/libraries/eosiolib/contracts/eosio/multi_index.hpp +++ b/libraries/eosiolib/contracts/eosio/multi_index.hpp @@ -1,6 +1,6 @@ /** - * @file - * @copyright defined in eos/LICENSE + * @file + * @copyright defined in eos/LICENSE */ #pragma once @@ -20,6 +20,11 @@ #include #include +/** + * @defgroup multiindex Multi Index Table + * @ingroup contracts + */ + namespace eosio { namespace internal_use_do_not_use { extern "C" { @@ -333,36 +338,36 @@ namespace _multi_index_detail { } /** - * The indexed_by struct is used to instantiate the indices for the Multi-Index table. In EOSIO, up to 16 secondary indices can be specified. + * The indexed_by struct is used to instantiate the indices for the Multi-Index table. In EOSIO, up to 16 secondary indices can be specified. + * + * @ingroup multiindex + * @tparam IndexName - is the name of the index. The name must be provided as an EOSIO base32 encoded 64-bit integer and must conform to the EOSIO naming requirements of a maximum of 13 characters, the first twelve from the lowercase characters a-z, digits 1-5, and ".", and if there is a 13th character, it is restricted to lowercase characters a-p and ".". + * @tparam Extractor - is a function call operator that takes a const reference to the table object type and returns either a secondary key type or a reference to a secondary key type. It is recommended to use the `eosio::const_mem_fun` template, which is a type alias to the `boost::multi_index::const_mem_fun`. See the documentation for the Boost `const_mem_fun` key extractor for more details. * - * @ingroup multiindex - * @tparam IndexName - is the name of the index. The name must be provided as an EOSIO base32 encoded 64-bit integer and must conform to the EOSIO naming requirements of a maximum of 13 characters, the first twelve from the lowercase characters a-z, digits 1-5, and ".", and if there is a 13th character, it is restricted to lowercase characters a-p and ".". - * @tparam Extractor - is a function call operator that takes a const reference to the table object type and returns either a secondary key type or a reference to a secondary key type. It is recommended to use the `eosio::const_mem_fun` template, which is a type alias to the `boost::multi_index::const_mem_fun`. See the documentation for the Boost `const_mem_fun` key extractor for more details. + * Example: * - * Example: - * -* - * @code - * #include - * using namespace eosio; - * class mycontract: eosio::contract { - * struct record { - * uint64_t primary; - * uint128_t secondary; - * uint64_t primary_key() const { return primary; } - * uint128_t get_secondary() const { return secondary; } - * }; - * public: - * mycontract(name receiver, name code, datastream ds):contract(receiver, code, ds){} - * void myaction() { - * auto code = _self; - * auto scope = _self; - * multi_index<"mytable"_n, record, - * indexed_by< "bysecondary"_n, const_mem_fun > > table( code, scope); - * } - * } - * EOSIO_DISPATCH( mycontract, (myaction) ) - * @endcode + * + * @code + * #include + * using namespace eosio; + * class mycontract: eosio::contract { + * struct record { + * uint64_t primary; + * uint128_t secondary; + * uint64_t primary_key() const { return primary; } + * uint128_t get_secondary() const { return secondary; } + * }; + * public: + * mycontract(name receiver, name code, datastream ds):contract(receiver, code, ds){} + * void myaction() { + * auto code = _self; + * auto scope = _self; + * multi_index<"mytable"_n, record, + * indexed_by< "bysecondary"_n, const_mem_fun > > table( code, scope); + * } + * } + * EOSIO_DISPATCH( mycontract, (myaction) ) + * @endcode */ template struct indexed_by { @@ -371,60 +376,59 @@ struct indexed_by { }; /** - * @defgroup multiindex Multi Index Table - * @ingroup contracts + * @ingroup multiindex * - * @brief Defines EOSIO Multi Index Table - * @details EOSIO Multi-Index API provides a C++ interface to the EOSIO database. It is patterned after Boost Multi Index Container. - * EOSIO Multi-Index table requires exactly a uint64_t primary key. For the table to be able to retrieve the primary key, - * the object stored inside the table is required to have a const member function called primary_key() that returns uint64_t. - * EOSIO Multi-Index table also supports up to 16 secondary indices. The type of the secondary indices could be any of: - * - uint64_t - * - uint128_t - * - double - * - long double - * - eosio::checksum256 + * @brief Defines EOSIO Multi Index Table + * @details EOSIO Multi-Index API provides a C++ interface to the EOSIO database. It is patterned after Boost Multi Index Container. + * EOSIO Multi-Index table requires exactly a uint64_t primary key. For the table to be able to retrieve the primary key, + * the object stored inside the table is required to have a const member function called primary_key() that returns uint64_t. + * EOSIO Multi-Index table also supports up to 16 secondary indices. The type of the secondary indices could be any of: + * - uint64_t + * - uint128_t + * - double + * - long double + * - eosio::checksum256 * - * @tparam TableName - name of the table - * @tparam T - type of the data stored inside the table - * @tparam Indices - secondary indices for the table, up to 16 indices is supported here + * @tparam TableName - name of the table + * @tparam T - type of the data stored inside the table + * @tparam Indices - secondary indices for the table, up to 16 indices is supported here * - * Example: - * - * @code - * #include - * using namespace eosio; - * class mycontract: contract { - * struct record { - * uint64_t primary; - * uint64_t secondary_1; - * uint128_t secondary_2; - * checksum256 secondary_3; - * double secondary_4; - * long double secondary_5; - * uint64_t primary_key() const { return primary; } - * uint64_t get_secondary_1() const { return secondary_1; } - * uint128_t get_secondary_2() const { return secondary_2; } - * checksum256 get_secondary_3() const { return secondary_3; } - * double get_secondary_4() const { return secondary_4; } - * long double get_secondary_5() const { return secondary_5; } - * }; - * public: - * mycontract(name receiver, name code, datastream ds):contract(receiver, code, ds){} - * void myaction() { - * auto code = _self; - * auto scope = _self; - * multi_index<"mytable"_n, record, - * indexed_by< "bysecondary1"_n, const_mem_fun >, - * indexed_by< "bysecondary2"_n, const_mem_fun >, - * indexed_by< "bysecondary3"_n, const_mem_fun >, - * indexed_by< "bysecondary4"_n, const_mem_fun >, - * indexed_by< "bysecondary5"_n, const_mem_fun > - * > table( code, scope); - * } - * } - * EOSIO_DISPATCH( mycontract, (myaction) ) - * @endcode + * Example: + * + * @code + * #include + * using namespace eosio; + * class mycontract: contract { + * struct record { + * uint64_t primary; + * uint64_t secondary_1; + * uint128_t secondary_2; + * checksum256 secondary_3; + * double secondary_4; + * long double secondary_5; + * uint64_t primary_key() const { return primary; } + * uint64_t get_secondary_1() const { return secondary_1; } + * uint128_t get_secondary_2() const { return secondary_2; } + * checksum256 get_secondary_3() const { return secondary_3; } + * double get_secondary_4() const { return secondary_4; } + * long double get_secondary_5() const { return secondary_5; } + * }; + * public: + * mycontract(name receiver, name code, datastream ds):contract(receiver, code, ds){} + * void myaction() { + * auto code = _self; + * auto scope = _self; + * multi_index<"mytable"_n, record, + * indexed_by< "bysecondary1"_n, const_mem_fun >, + * indexed_by< "bysecondary2"_n, const_mem_fun >, + * indexed_by< "bysecondary3"_n, const_mem_fun >, + * indexed_by< "bysecondary4"_n, const_mem_fun >, + * indexed_by< "bysecondary5"_n, const_mem_fun > + * > table( code, scope); + * } + * } + * EOSIO_DISPATCH( mycontract, (myaction) ) + * @endcode */ template @@ -634,11 +638,26 @@ class multi_index return lb; } + /** + * Gets the object with the smallest primary key in the case where the secondary key is not unique. + * + * Avoid the common pitfall of copy-assigning the T& reference returned + * to a stack-allocated local variable and then passing that into modify of the multi-index. + * The most common mistake is when the local variable is defined as auto + * typename, instead it should be of type auto& or decltype(auto). + */ const T& get( secondary_key_type&& secondary, const char* error_msg = "unable to find secondary key" )const { return get( secondary, error_msg ); } - // Gets the object with the smallest primary key in the case where the secondary key is not unique. + /** + * Gets the object with the smallest primary key in the case where the secondary key is not unique. + * + * Avoid the common pitfall of copy-assigning the T& reference returned + * to a stack-allocated local variable and then passing that into modify of the multi-index. + * The most common mistake is when the local variable is defined as auto + * typename, instead it should be of type auto& or decltype(auto). + */ const T& get( const secondary_key_type& secondary, const char* error_msg = "unable to find secondary key" )const { auto result = find( secondary ); eosio::check( result != cend(), error_msg ); @@ -680,7 +699,11 @@ class multi_index return {this, &mi}; } - + /** + * Warning: the interator_to can have undefined behavior if the caller + * passes in a reference to a stack-allocated object rather than the + * reference returned by get or by dereferencing a const_iterator. + */ const_iterator iterator_to( const T& obj ) { using namespace _multi_index_detail; @@ -704,6 +727,11 @@ class multi_index _multidx->modify( *itr, payer, std::forward(updater) ); } + template + void modify( const T& obj, eosio::name payer, Lambda&& updater ) { + _multidx->modify( obj, payer, std::forward(updater) ); + } + const_iterator erase( const_iterator itr ) { eosio::check( itr != cend(), "cannot pass end iterator to erase" ); @@ -804,95 +832,95 @@ class multi_index public: /** - * Constructs an instance of a Multi-Index table. - * @ingroup multiindex - * - * @param code - Account that owns table - * @param scope - Scope identifier within the code hierarchy - * - * @pre code and scope member properties are initialized - * @post each secondary index table initialized - * @post Secondary indices are updated to refer to the newly added object. If the secondary index tables do not exist, they are created. - * @post The payer is charged for the storage usage of the new object and, if the table (and secondary index tables) must be created, for the overhead of the table creation. - * - * Notes - * The `eosio::multi_index` template has template parameters ``, where: - * - `TableName` is the name of the table, maximum 12 characters long, characters in the name from the set of lowercase letters, digits 1 to 5, and the "." (period) character and is converted to a eosio::raw - which wraps uint64_t; - * - `T` is the object type (i.e., row definition); - * - `Indices` is a list of up to 16 secondary indices. - * - Each must be a default constructable class or struct - * - Each must have a function call operator that takes a const reference to the table object type and returns either a secondary key type or a reference to a secondary key type - * - It is recommended to use the eosio::const_mem_fun template, which is a type alias to the boost::multi_index::const_mem_fun. See the documentation for the Boost const_mem_fun key extractor for more details. - * - * Example: - * - * @code - * #include - * using namespace eosio; - * using namespace std; - * class addressbook: contract { - * struct address { - * uint64_t account_name; - * string first_name; - * string last_name; - * string street; - * string city; - * string state; - * uint64_t primary_key() const { return account_name; } - * }; - * public: - * addressbook(name self):contract(self) {} - * typedef eosio::multi_index< "address"_n, address > address_index; - * void myaction() { - * address_index addresses(_self, _self.value); // code, scope - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Constructs an instance of a Multi-Index table. + * @ingroup multiindex + * + * @param code - Account that owns table + * @param scope - Scope identifier within the code hierarchy + * + * @pre code and scope member properties are initialized + * @post each secondary index table initialized + * @post Secondary indices are updated to refer to the newly added object. If the secondary index tables do not exist, they are created. + * @post The payer is charged for the storage usage of the new object and, if the table (and secondary index tables) must be created, for the overhead of the table creation. + * + * Notes + * The `eosio::multi_index` template has template parameters ``, where: + * - `TableName` is the name of the table, maximum 12 characters long, characters in the name from the set of lowercase letters, digits 1 to 5, and the "." (period) character and is converted to a eosio::raw - which wraps uint64_t; + * - `T` is the object type (i.e., row definition); + * - `Indices` is a list of up to 16 secondary indices. + * - Each must be a default constructable class or struct + * - Each must have a function call operator that takes a const reference to the table object type and returns either a secondary key type or a reference to a secondary key type + * - It is recommended to use the eosio::const_mem_fun template, which is a type alias to the boost::multi_index::const_mem_fun. See the documentation for the Boost const_mem_fun key extractor for more details. + * + * Example: + * + * @code + * #include + * using namespace eosio; + * using namespace std; + * class addressbook: contract { + * struct address { + * uint64_t account_name; + * string first_name; + * string last_name; + * string street; + * string city; + * string state; + * uint64_t primary_key() const { return account_name; } + * }; + * public: + * addressbook(name self):contract(self) {} + * typedef eosio::multi_index< "address"_n, address > address_index; + * void myaction() { + * address_index addresses(_self, _self.value); // code, scope + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ multi_index( name code, uint64_t scope ) :_code(code),_scope(scope),_next_primary_key(unset_next_primary_key) {} /** - * Returns the `code` member property. - * @ingroup multiindex + * Returns the `code` member property. + * @ingroup multiindex * - * @return Account name of the Code that owns the Primary Table. + * @return Account name of the Code that owns the Primary Table. * - * Example: + * Example: * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} * - * void myaction() { - * address_index addresses("dan"_n, "dan"_n); // code, scope - * eosio::check(addresses.get_code() == "dan"_n, "Codes don't match."); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * void myaction() { + * address_index addresses("dan"_n, "dan"_n.value); // code, scope + * eosio::check(addresses.get_code() == "dan"_n, "Codes don't match."); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ name get_code()const { return _code; } /** - * Returns the `scope` member property. - * @ingroup multiindex + * Returns the `scope` member property. + * @ingroup multiindex * - * @return Scope id of the Scope within the Code of the Current Receiver under which the desired Primary Table instance can be found. + * @return Scope id of the Scope within the Code of the Current Receiver under which the desired Primary Table instance can be found. * - * Example: + * Example: * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} * - * void myaction() { - * address_index addresses("dan"_n, "dan"_n); // code, scope - * eosio::check(addresses.get_code() == "dan"_n, "Scopes don't match"); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * void myaction() { + * address_index addresses("dan"_n, "dan"_n.value); // code, scope + * eosio::check(addresses.get_scope() == "dan"_n.value, "Scopes don't match"); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ uint64_t get_scope()const { return _scope; } @@ -960,283 +988,283 @@ class multi_index typedef std::reverse_iterator const_reverse_iterator; /** - * Returns an iterator pointing to the object_type with the lowest primary key value in the Multi-Index table. - * @ingroup multiindex + * Returns an iterator pointing to the object_type with the lowest primary key value in the Multi-Index table. + * @ingroup multiindex * - * @return An iterator pointing to the object_type with the lowest primary key value in the Multi-Index table. + * @return An iterator pointing to the object_type with the lowest primary key value in the Multi-Index table. * - * Example: + * Example: * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} * - * void myaction() { - * // create reference to address_index - see emplace example below - * // add dan account to table - see emplace example below + * void myaction() { + * // create reference to address_index - see emplace example below + * // add dan account to table - see emplace example below * - * auto itr = addresses.find("dan"_n); - * eosio::check(itr == addresses.cbegin(), "Only address is not at front."); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * auto itr = addresses.find("dan"_n); + * eosio::check(itr == addresses.cbegin(), "Only address is not at front."); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ const_iterator cbegin()const { return lower_bound(std::numeric_limits::lowest()); } /** - * Returns an iterator pointing to the object_type with the lowest primary key value in the Multi-Index table. - * @ingroup multiindex + * Returns an iterator pointing to the object_type with the lowest primary key value in the Multi-Index table. + * @ingroup multiindex * - * @return An iterator pointing to the object_type with the lowest primary key value in the Multi-Index table. + * @return An iterator pointing to the object_type with the lowest primary key value in the Multi-Index table. * - * Example: + * Example: * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} * - * void myaction() { - * // create reference to address_index - see emplace example below - * // add dan account to table - see emplace example below + * void myaction() { + * // create reference to address_index - see emplace example below + * // add dan account to table - see emplace example below * - * auto itr = addresses.find("dan"_n); - * eosio::check(itr == addresses.begin(), "Only address is not at front."); - * } - * } - * EOSIO_ABI( addressbook, (myaction) ) - * @endcode + * auto itr = addresses.find("dan"_n); + * eosio::check(itr == addresses.begin(), "Only address is not at front."); + * } + * } + * EOSIO_ABI( addressbook, (myaction) ) + * @endcode */ const_iterator begin()const { return cbegin(); } /** - * Returns an iterator pointing to the `object_type` with the highest primary key value in the Multi-Index table. - * @ingroup multiindex + * Returns an iterator referring to the `past-the-end` element in the multi index container. The `past-the-end` element is the theoretical element that would follow the last element in the vector. It does not point to any element, and thus shall not be dereferenced. + * @ingroup multiindex * - * @return An iterator pointing to the `object_type` with the highest primary key value in the Multi-Index table. + * @return An iterator referring to the `past-the-end` element in the multi index container. * - * Example: + * Example: * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} * - * void myaction() { - * // create reference to address_index - see emplace example below - * // add dan account to table - see emplace example below + * void myaction() { + * // create reference to address_index - see emplace example below + * // add dan account to table - see emplace example below * - * auto itr = addresses.find("dan"_n); - * eosio::check(itr != addresses.cend(), "Address for account doesn't exist"); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * auto itr = addresses.find("dan"_n); + * eosio::check(itr != addresses.cend(), "Address for account doesn't exist"); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ const_iterator cend()const { return const_iterator( this ); } /** - * Returns an iterator pointing to the `object_type` with the highest primary key value in the Multi-Index table. - * @ingroup multiindex + * Returns an iterator referring to the `past-the-end` element in the multi index container. The `past-the-end` element is the theoretical element that would follow the last element in the vector. It does not point to any element, and thus shall not be dereferenced. + * @ingroup multiindex * - * @return An iterator pointing to the `object_type` with the highest primary key value in the Multi-Index table. + * @return An iterator referring to the `past-the-end` element in the multi index container. * - * Example: + * Example: * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} * - * void myaction() { - * // create reference to address_index - see emplace example below - * // add dan account to table - see emplace example below + * void myaction() { + * // create reference to address_index - see emplace example below + * // add dan account to table - see emplace example below * - * auto itr = addresses.find("dan"_n); - * eosio::check(itr != addresses.end(), "Address for account doesn't exist"); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * auto itr = addresses.find("dan"_n); + * eosio::check(itr != addresses.end(), "Address for account doesn't exist"); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ const_iterator end()const { return cend(); } /** - * Returns a reverse iterator pointing to the `object_type` with the highest primary key value in the Multi-Index table. - * @ingroup multiindex - * - * @return A reverse iterator pointing to the `object_type` with the highest primary key value in the Multi-Index table. - * - * Example: - * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} - * - * void myaction() { - * // create reference to address_index - see emplace example below - * // add dan account to table - see emplace example below - * // add additional account - brendan - * - * addresses.emplace(payer, [&](auto& address) { - * address.account_name = "brendan"_n; - * address.first_name = "Brendan"; - * address.last_name = "Blumer"; - * address.street = "1 EOS Way"; - * address.city = "Hong Kong"; - * address.state = "HK"; - * }); - * auto itr = addresses.crbegin(); - * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect Last Record "); - * itr++; - * eosio::check(itr->account_name == name("brendan"), "Lock arf, Incorrect Second Last Record"); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Returns a reverse iterator pointing to the `object_type` with the highest primary key value in the Multi-Index table. + * @ingroup multiindex + * + * @return A reverse iterator pointing to the `object_type` with the highest primary key value in the Multi-Index table. + * + * Example: + * + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} + * + * void myaction() { + * // create reference to address_index - see emplace example below + * // add dan account to table - see emplace example below + * // add additional account - brendan + * + * addresses.emplace(payer, [&](auto& address) { + * address.account_name = "brendan"_n; + * address.first_name = "Brendan"; + * address.last_name = "Blumer"; + * address.street = "1 EOS Way"; + * address.city = "Hong Kong"; + * address.state = "HK"; + * }); + * auto itr = addresses.crbegin(); + * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect Last Record "); + * itr++; + * eosio::check(itr->account_name == name("brendan"), "Lock arf, Incorrect Second Last Record"); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ const_reverse_iterator crbegin()const { return std::make_reverse_iterator(cend()); } /** - * Returns a reverse iterator pointing to the `object_type` with the highest primary key value in the Multi-Index table. - * @ingroup multiindex - * - * @return A reverse iterator pointing to the `object_type` with the highest primary key value in the Multi-Index table. - * - * Example: - * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} - * - * void myaction() { - * // create reference to address_index - see emplace example below - * // add dan account to table - see emplace example below - * // add additional account - brendan - * - * addresses.emplace(payer, [&](auto& address) { - * address.account_name = "brendan"_n; - * address.first_name = "Brendan"; - * address.last_name = "Blumer"; - * address.street = "1 EOS Way"; - * address.city = "Hong Kong"; - * address.state = "HK"; - * }); - * auto itr = addresses.rbegin(); - * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect Last Record "); - * itr++; - * eosio::check(itr->account_name == name("brendan"), "Lock arf, Incorrect Second Last Record"); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Returns a reverse iterator pointing to the `object_type` with the highest primary key value in the Multi-Index table. + * @ingroup multiindex + * + * @return A reverse iterator pointing to the `object_type` with the highest primary key value in the Multi-Index table. + * + * Example: + * + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} + * + * void myaction() { + * // create reference to address_index - see emplace example below + * // add dan account to table - see emplace example below + * // add additional account - brendan + * + * addresses.emplace(payer, [&](auto& address) { + * address.account_name = "brendan"_n; + * address.first_name = "Brendan"; + * address.last_name = "Blumer"; + * address.street = "1 EOS Way"; + * address.city = "Hong Kong"; + * address.state = "HK"; + * }); + * auto itr = addresses.rbegin(); + * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect Last Record "); + * itr++; + * eosio::check(itr->account_name == name("brendan"), "Lock arf, Incorrect Second Last Record"); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ const_reverse_iterator rbegin()const { return crbegin(); } /** - * Returns an iterator pointing to the `object_type` with the lowest primary key value in the Multi-Index table. - * @ingroup multiindex - * - * @return An iterator pointing to the `object_type` with the lowest primary key value in the Multi-Index table. - * - * Example: - * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} - * - * void myaction() { - * // create reference to address_index - see emplace example below - * // add dan account to table - see emplace example below - * // add additional account - brendan - * - * addresses.emplace(payer, [&](auto& address) { - * address.account_name = "brendan"_n; - * address.first_name = "Brendan"; - * address.last_name = "Blumer"; - * address.street = "1 EOS Way"; - * address.city = "Hong Kong"; - * address.state = "HK"; - * }); - * auto itr = addresses.crend(); - * itr--; - * eosio::check(itr->account_name == name("brendan"), "Lock arf, Incorrect First Record "); - * itr--; - * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect Second Record"); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Returns an iterator pointing to the `object_type` with the lowest primary key value in the Multi-Index table. + * @ingroup multiindex + * + * @return An iterator pointing to the `object_type` with the lowest primary key value in the Multi-Index table. + * + * Example: + * + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} + * + * void myaction() { + * // create reference to address_index - see emplace example below + * // add dan account to table - see emplace example below + * // add additional account - brendan + * + * addresses.emplace(payer, [&](auto& address) { + * address.account_name = "brendan"_n; + * address.first_name = "Brendan"; + * address.last_name = "Blumer"; + * address.street = "1 EOS Way"; + * address.city = "Hong Kong"; + * address.state = "HK"; + * }); + * auto itr = addresses.crend(); + * itr--; + * eosio::check(itr->account_name == name("brendan"), "Lock arf, Incorrect First Record "); + * itr--; + * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect Second Record"); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ const_reverse_iterator crend()const { return std::make_reverse_iterator(cbegin()); } /** - * Returns an iterator pointing to the `object_type` with the lowest primary key value in the Multi-Index table. - * @ingroup multiindex - * - * @return An iterator pointing to the `object_type` with the lowest primary key value in the Multi-Index table. - * - * Example: - * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} - * - * void myaction() { - * // create reference to address_index - see emplace example below - * // add dan account to table - see emplace example below - * // add additional account - brendan - * - * addresses.emplace(payer, [&](auto& address) { - * address.account_name = "brendan"_n; - * address.first_name = "Brendan"; - * address.last_name = "Blumer"; - * address.street = "1 EOS Way"; - * address.city = "Hong Kong"; - * address.state = "HK"; - * }); - * auto itr = addresses.rend(); - * itr--; - * eosio::check(itr->account_name == name("brendan"), "Lock arf, Incorrect First Record "); - * itr--; - * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect Second Record"); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Returns an iterator pointing to the `object_type` with the lowest primary key value in the Multi-Index table. + * @ingroup multiindex + * + * @return An iterator pointing to the `object_type` with the lowest primary key value in the Multi-Index table. + * + * Example: + * + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} + * + * void myaction() { + * // create reference to address_index - see emplace example below + * // add dan account to table - see emplace example below + * // add additional account - brendan + * + * addresses.emplace(payer, [&](auto& address) { + * address.account_name = "brendan"_n; + * address.first_name = "Brendan"; + * address.last_name = "Blumer"; + * address.street = "1 EOS Way"; + * address.city = "Hong Kong"; + * address.state = "HK"; + * }); + * auto itr = addresses.rend(); + * itr--; + * eosio::check(itr->account_name == name("brendan"), "Lock arf, Incorrect First Record "); + * itr--; + * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect Second Record"); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ const_reverse_iterator rend()const { return crend(); } /** - * Searches for the `object_type` with the lowest primary key that is greater than or equal to a given primary key. - * @ingroup multiindex - * - * @param primary - Primary key that establishes the target value for the lower bound search. - * @return An iterator pointing to the `object_type` that has the lowest primary key that is greater than or equal to `primary`. If an object could not be found, it will return the `end` iterator. If the table does not exist** it will return `-1`. - * - * Example: - * - * @code - * // This assumes the code from the get_index() example below. Replace myaction() {...} - * - * void myaction() { - * // create reference to address_index - see emplace example below - * // add dan account to table - see emplace example below - * // add additional account - brendan - * - * addresses.emplace(payer, [&](auto& address) { - * address.account_name = "brendan"_n; - * address.first_name = "Brendan"; - * address.last_name = "Blumer"; - * address.street = "1 EOS Way"; - * address.city = "Hong Kong"; - * address.state = "HK"; - * address.zip = 93445; - * }); - * uint32_t zipnumb = 93445; - * auto zip_index = addresses.get_index(); - * auto itr = zip_index.lower_bound(zipnumb); - * eosio::check(itr->account_name == name("brendan"), "Lock arf, Incorrect First Lower Bound Record "); - * itr++; - * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect Second Lower Bound Record"); - * itr++; - * eosio::check(itr == zip_index.end(), "Lock arf, Incorrect End of Iterator"); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Searches for the `object_type` with the lowest primary key that is greater than or equal to a given primary key. + * @ingroup multiindex + * + * @param primary - Primary key that establishes the target value for the lower bound search. + * @return An iterator pointing to the `object_type` that has the lowest primary key that is greater than or equal to `primary`. If an object could not be found, or if the table does not exist**, it will return the `end` iterator. + * + * Example: + * + * @code + * // This assumes the code from the get_index() example below. Replace myaction() {...} + * + * void myaction() { + * // create reference to address_index - see emplace example below + * // add dan account to table - see emplace example below + * // add additional account - brendan + * + * addresses.emplace(payer, [&](auto& address) { + * address.account_name = "brendan"_n; + * address.first_name = "Brendan"; + * address.last_name = "Blumer"; + * address.street = "1 EOS Way"; + * address.city = "Hong Kong"; + * address.state = "HK"; + * address.zip = 93445; + * }); + * uint32_t zipnumb = 93445; + * auto zip_index = addresses.get_index(); + * auto itr = zip_index.lower_bound(zipnumb); + * eosio::check(itr->account_name == name("brendan"), "Lock arf, Incorrect First Lower Bound Record "); + * itr++; + * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect Second Lower Bound Record"); + * itr++; + * eosio::check(itr == zip_index.end(), "Lock arf, Incorrect End of Iterator"); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ const_iterator lower_bound( uint64_t primary )const { auto itr = internal_use_do_not_use::db_lowerbound_i64( _code.value, _scope, static_cast(TableName), primary ); @@ -1246,41 +1274,41 @@ class multi_index } /** - * Searches for the `object_type` with the lowest primary key that is greater than a given primary key. - * @ingroup multiindex - * - * @param primary - Primary key that establishes the target value for the upper bound search - * @return An iterator pointing to the `object_type` that has the highest primary key that is less than or equal to `primary`. If an object could not be found, it will return the `end` iterator. If the table does not exist** it will return `-1`. - * - * Example: - * - * @code - * // This assumes the code from the get_index() example below. Replace myaction() {...} - * - * void myaction() { - * // create reference to address_index - see emplace example below - * // add dan account to table - see emplace example below - * // add additional account - brendan - * - * addresses.emplace(payer, [&](auto& address) { - * address.account_name = "brendan"_n; - * address.first_name = "Brendan"; - * address.last_name = "Blumer"; - * address.street = "1 EOS Way"; - * address.city = "Hong Kong"; - * address.state = "HK"; - * address.zip = 93445; - * }); - * uint32_t zipnumb = 93445; - * auto zip_index = addresses.get_index(); - * auto itr = zip_index.upper_bound(zipnumb); - * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect First Upper Bound Record "); - * itr++; - * eosio::check(itr == zip_index.end(), "Lock arf, Incorrect End of Iterator"); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Searches for the `object_type` with the lowest primary key that is greater than a given primary key. + * @ingroup multiindex + * + * @param primary - Primary key that establishes the target value for the upper bound search + * @return An iterator pointing to the `object_type` that has the lowest primary key that is greater than a given `primary` key. If an object could not be found, or if the table does not exist**, it will return the `end` iterator. + * + * Example: + * + * @code + * // This assumes the code from the get_index() example below. Replace myaction() {...} + * + * void myaction() { + * // create reference to address_index - see emplace example below + * // add dan account to table - see emplace example below + * // add additional account - brendan + * + * addresses.emplace(payer, [&](auto& address) { + * address.account_name = "brendan"_n; + * address.first_name = "Brendan"; + * address.last_name = "Blumer"; + * address.street = "1 EOS Way"; + * address.city = "Hong Kong"; + * address.state = "HK"; + * address.zip = 93445; + * }); + * uint32_t zipnumb = 93445; + * auto zip_index = addresses.get_index(); + * auto itr = zip_index.upper_bound(zipnumb); + * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect First Upper Bound Record "); + * itr++; + * eosio::check(itr == zip_index.end(), "Lock arf, Incorrect End of Iterator"); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ const_iterator upper_bound( uint64_t primary )const { auto itr = internal_use_do_not_use::db_upperbound_i64( _code.value, _scope, static_cast(TableName), primary ); @@ -1290,35 +1318,35 @@ class multi_index } /** - * Returns an available primary key. - * @ingroup multiindex - * - * @return An available (unused) primary key value. - * - * Notes: - * Intended to be used in tables in which the primary keys of the table are strictly intended to be auto-incrementing, and thus will never be set to custom values by the contract. Violating this expectation could result in the table appearing to be full due to inability to allocate an available primary key. - * Ideally this method would only be used to determine the appropriate primary key to use within new objects added to a table in which the primary keys of the table are strictly intended from the beginning to be autoincrementing and thus will not ever be set to custom arbitrary values by the contract. Violating this agreement could result in the table appearing full when in reality there is plenty of space left. - * - * Example: - * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} - * - * void myaction() { - * address_index addresses(_self, _self.value); // code, scope - * // add to table, first argument is account to bill for storage - * addresses.emplace(payer, [&](auto& address) { - * address.key = addresses.available_primary_key(); - * address.first_name = "Daniel"; - * address.last_name = "Larimer"; - * address.street = "1 EOS Way"; - * address.city = "Blacksburg"; - * address.state = "VA"; - * }); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Returns an available primary key. + * @ingroup multiindex + * + * @return An available (unused) primary key value. + * + * Notes: + * Intended to be used in tables in which the primary keys of the table are strictly intended to be auto-incrementing, and thus will never be set to custom values by the contract. Violating this expectation could result in the table appearing to be full due to inability to allocate an available primary key. + * Ideally this method would only be used to determine the appropriate primary key to use within new objects added to a table in which the primary keys of the table are strictly intended from the beginning to be autoincrementing and thus will not ever be set to custom arbitrary values by the contract. Violating this agreement could result in the table appearing full when in reality there is plenty of space left. + * + * Example: + * + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} + * + * void myaction() { + * address_index addresses(_self, _self.value); // code, scope + * // add to table, first argument is account to bill for storage + * addresses.emplace(payer, [&](auto& address) { + * address.key = addresses.available_primary_key(); + * address.first_name = "Daniel"; + * address.last_name = "Larimer"; + * address.street = "1 EOS Way"; + * address.city = "Blacksburg"; + * address.state = "VA"; + * }); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ uint64_t available_primary_key()const { if( _next_primary_key == unset_next_primary_key ) { @@ -1340,45 +1368,45 @@ class multi_index } /** - * Returns an appropriately typed Secondary Index. - * @ingroup multiindex - * - * @tparam IndexName - the ID of the desired secondary index - * - * @return An index of the appropriate type: Primitive 64-bit unsigned integer key (idx64), Primitive 128-bit unsigned integer key (idx128), 128-bit fixed-size lexicographical key (idx128), 256-bit fixed-size lexicographical key (idx256), Floating point key, Double precision floating point key, Long Double (quadruple) precision floating point key - * - * Example: - * - * @code - * #include - * using namespace eosio; - * using namespace std; - * class addressbook: contract { - * struct address { - * uint64_t account_name; - * string first_name; - * string last_name; - * string street; - * string city; - * string state; - * uint32_t zip = 0; - * uint64_t primary_key() const { return account_name; } - * uint64_t by_zip() const { return zip; } - * }; - * public: - * addressbook(name receiver, name code, datastream ds):contract(receiver, code, ds) {} - * typedef eosio::multi_index< name("address"), address, indexed_by< name("zip"), const_mem_fun > address_index; - * void myaction() { - * // create reference to address_index - see emplace example below - * // add dan account to table - see emplace example below - * uint32_t zipnumb = 93446; - * auto zip_index = addresses.get_index(); - * auto itr = zip_index.find(zipnumb); - * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect Record "); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Returns an appropriately typed Secondary Index. + * @ingroup multiindex + * + * @tparam IndexName - the ID of the desired secondary index + * + * @return An index of the appropriate type: Primitive 64-bit unsigned integer key (idx64), Primitive 128-bit unsigned integer key (idx128), 128-bit fixed-size lexicographical key (idx128), 256-bit fixed-size lexicographical key (idx256), Floating point key, Double precision floating point key, Long Double (quadruple) precision floating point key + * + * Example: + * + * @code + * #include + * using namespace eosio; + * using namespace std; + * class addressbook: contract { + * struct address { + * uint64_t account_name; + * string first_name; + * string last_name; + * string street; + * string city; + * string state; + * uint32_t zip = 0; + * uint64_t primary_key() const { return account_name; } + * uint64_t by_zip() const { return zip; } + * }; + * public: + * addressbook(name receiver, name code, datastream ds):contract(receiver, code, ds) {} + * typedef eosio::multi_index< name("address"), address, indexed_by< name("zip"), const_mem_fun > address_index; + * void myaction() { + * // create reference to address_index - see emplace example below + * // add dan account to table - see emplace example below + * uint32_t zipnumb = 93446; + * auto zip_index = addresses.get_index(); + * auto itr = zip_index.find(zipnumb); + * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect Record "); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ template auto get_index() { @@ -1394,42 +1422,42 @@ class multi_index } /** - * Returns an appropriately typed Secondary Index. - * @ingroup multiindex - * - * @tparam IndexName - the ID of the desired secondary index - * - * @return An index of the appropriate type: Primitive 64-bit unsigned integer key (idx64), Primitive 128-bit unsigned integer key (idx128), 128-bit fixed-size lexicographical key (idx128), 256-bit fixed-size lexicographical key (idx256), Floating point key, Double precision floating point key, Long Double (quadruple) precision floating point key - * - * Example: - * - * @code - * // This assumes the code from the get_index() example. Replace myaction() {...} - * - * void myaction() { - * // create reference to address_index - see emplace example below - * // add dan account to table - see emplace example below - * // add additional account - brendan - * - * addresses.emplace(payer, [&](auto& address) { - * address.account_name = "brendan"_n; - * address.first_name = "Brendan"; - * address.last_name = "Blumer"; - * address.street = "1 EOS Way"; - * address.city = "Hong Kong"; - * address.state = "HK"; - * address.zip = 93445; - * }); - * uint32_t zipnumb = 93445; - * auto zip_index = addresses.get_index(); - * auto itr = zip_index.upper_bound(zipnumb); - * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect First Upper Bound Record "); - * itr++; - * eosio::check(itr == zip_index.end(), "Lock arf, Incorrect End of Iterator"); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Returns an appropriately typed Secondary Index. + * @ingroup multiindex + * + * @tparam IndexName - the ID of the desired secondary index + * + * @return An index of the appropriate type: Primitive 64-bit unsigned integer key (idx64), Primitive 128-bit unsigned integer key (idx128), 128-bit fixed-size lexicographical key (idx128), 256-bit fixed-size lexicographical key (idx256), Floating point key, Double precision floating point key, Long Double (quadruple) precision floating point key + * + * Example: + * + * @code + * // This assumes the code from the get_index() example. Replace myaction() {...} + * + * void myaction() { + * // create reference to address_index - see emplace example below + * // add dan account to table - see emplace example below + * // add additional account - brendan + * + * addresses.emplace(payer, [&](auto& address) { + * address.account_name = "brendan"_n; + * address.first_name = "Brendan"; + * address.last_name = "Blumer"; + * address.street = "1 EOS Way"; + * address.city = "Hong Kong"; + * address.state = "HK"; + * address.zip = 93445; + * }); + * uint32_t zipnumb = 93445; + * auto zip_index = addresses.get_index(); + * auto itr = zip_index.upper_bound(zipnumb); + * eosio::check(itr->account_name == name("dan"), "Lock arf, Incorrect First Upper Bound Record "); + * itr++; + * eosio::check(itr == zip_index.end(), "Lock arf, Incorrect End of Iterator"); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ template auto get_index()const { @@ -1445,39 +1473,43 @@ class multi_index } /** - * Returns an iterator to the given object in a Multi-Index table. - * @ingroup multiindex - * - * @param obj - A reference to the desired object - * - * @return An iterator to the given object - * - * Example: - * - * @code - * // This assumes the code from the get_index() example. Replace myaction() {...} - * - * void myaction() { - * // create reference to address_index - see emplace example below - * // add dan account to table - see emplace example below - * // add additional account - brendan - * - * addresses.emplace(payer, [&](auto& address) { - * address.account_name = "brendan"_n; - * address.first_name = "Brendan"; - * address.last_name = "Blumer"; - * address.street = "1 EOS Way"; - * address.city = "Hong Kong"; - * address.state = "HK"; - * address.zip = 93445; - * }); - * auto user = addresses.get("dan"_n); - * auto itr = address.find("dan"_n); - * eosio::check(iterator_to(user) == itr, "Invalid iterator"); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Returns an iterator to the given object in a Multi-Index table. + * @ingroup multiindex + * + * @param obj - A reference to the desired object + * + * @return An iterator to the given object + * + * Example: + * + * @code + * // This assumes the code from the get_index() example. Replace myaction() {...} + * + * void myaction() { + * // create reference to address_index - see emplace example below + * // add dan account to table - see emplace example below + * // add additional account - brendan + * + * addresses.emplace(payer, [&](auto& address) { + * address.account_name = "brendan"_n; + * address.first_name = "Brendan"; + * address.last_name = "Blumer"; + * address.street = "1 EOS Way"; + * address.city = "Hong Kong"; + * address.state = "HK"; + * address.zip = 93445; + * }); + * auto user = addresses.get("dan"_n); + * auto itr = address.find("dan"_n); + * eosio::check(iterator_to(user) == itr, "Invalid iterator"); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode + * + * Warning: the interator_to can have undefined behavior if the caller + * passes in a reference to a stack-allocated object rather than the + * reference returned by get or by dereferencing a const_iterator. */ const_iterator iterator_to( const T& obj )const { const auto& objitem = static_cast(obj); @@ -1485,41 +1517,41 @@ class multi_index return {this, &objitem}; } /** - * Adds a new object (i.e., row) to the table. - * @ingroup multiindex - * - * @param payer - Account name of the payer for the Storage usage of the new object - * @param constructor - Lambda function that does an in-place initialization of the object to be created in the table - * - * @pre A multi index table has been instantiated - * @post A new object is created in the Multi-Index table, with a unique primary key (as specified in the object). The object is serialized and written to the table. If the table does not exist, it is created. - * @post Secondary indices are updated to refer to the newly added object. If the secondary index tables do not exist, they are created. - * @post The payer is charged for the storage usage of the new object and, if the table (and secondary index tables) must be created, for the overhead of the table creation. - * - * @return A primary key iterator to the newly created object - * - * Exception - The account is not authorized to write to the table. - * - * Example: - * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} - * - * void myaction() { - * address_index addresses(_self, _self.value); // code, scope - * // add to table, first argument is account to bill for storage - * addresses.emplace(_self, [&](auto& address) { - * address.account_name = "dan"_n; - * address.first_name = "Daniel"; - * address.last_name = "Larimer"; - * address.street = "1 EOS Way"; - * address.city = "Blacksburg"; - * address.state = "VA"; - * }); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Adds a new object (i.e., row) to the table. + * @ingroup multiindex + * + * @param payer - Account name of the payer for the Storage usage of the new object + * @param constructor - Lambda function that does an in-place initialization of the object to be created in the table + * + * @pre A multi index table has been instantiated + * @post A new object is created in the Multi-Index table, with a unique primary key (as specified in the object). The object is serialized and written to the table. If the table does not exist, it is created. + * @post Secondary indices are updated to refer to the newly added object. If the secondary index tables do not exist, they are created. + * @post The payer is charged for the storage usage of the new object and, if the table (and secondary index tables) must be created, for the overhead of the table creation. + * + * @return A primary key iterator to the newly created object + * + * Exception - The account is not authorized to write to the table. + * + * Example: + * + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} + * + * void myaction() { + * address_index addresses(_self, _self.value); // code, scope + * // add to table, first argument is account to bill for storage + * addresses.emplace(_self, [&](auto& address) { + * address.account_name = "dan"_n; + * address.first_name = "Daniel"; + * address.last_name = "Larimer"; + * address.street = "1 EOS Way"; + * address.city = "Blacksburg"; + * address.state = "VA"; + * }); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ template const_iterator emplace( name payer, Lambda&& constructor ) { @@ -1567,44 +1599,44 @@ class multi_index } /** - * Modifies an existing object in a table. - * @ingroup multiindex - * - * @param itr - an iterator pointing to the object to be updated - * @param payer - account name of the payer for the Storage usage of the updated row - * @param updater - lambda function that updates the target object - * - * @pre itr points to an existing element - * @pre payer is a valid account that is authorized to execute the action and be billed for storage usage. - * - * @post The modified object is serialized, then replaces the existing object in the table. - * @post Secondary indices are updated; the primary key of the updated object is not changed. - * @post The payer is charged for the storage usage of the updated object. - * @post If payer is the same as the existing payer, payer only pays for the usage difference between existing and updated object (and is refunded if this difference is negative). - * @post If payer is different from the existing payer, the existing payer is refunded for the storage usage of the existing object. - * - * Exceptions: - * If called with an invalid precondition, execution is aborted. - * - * Example: - * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} - * - * void myaction() { - * // create reference to address_index - see emplace example - * // add dan account to table - see emplace example - * - * auto itr = addresses.find("dan"_n); - * eosio::check(itr != addresses.end(), "Address for account not found"); - * addresses.modify( itr, account payer, [&]( auto& address ) { - * address.city = "San Luis Obispo"; - * address.state = "CA"; - * }); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Modifies an existing object in a table. + * @ingroup multiindex + * + * @param itr - an iterator pointing to the object to be updated + * @param payer - account name of the payer for the storage usage of the updated row + * @param updater - lambda function that updates the target object + * + * @pre itr points to an existing element + * @pre payer is a valid account that is authorized to execute the action and be billed for storage usage. + * + * @post The modified object is serialized, then replaces the existing object in the table. + * @post Secondary indices are updated; the primary key of the updated object is not changed. + * @post The payer is charged for the storage usage of the updated object. + * @post If payer is the same as the existing payer, payer only pays for the usage difference between existing and updated object (and is refunded if this difference is negative). + * @post If payer is different from the existing payer, the existing payer is refunded for the storage usage of the existing object. + * + * Exceptions: + * If called with an invalid precondition, execution is aborted. + * + * Example: + * + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} + * + * void myaction() { + * // create reference to address_index - see emplace example + * // add dan account to table - see emplace example + * + * auto itr = addresses.find("dan"_n); + * eosio::check(itr != addresses.end(), "Address for account not found"); + * addresses.modify( itr, account payer, [&]( auto& address ) { + * address.city = "San Luis Obispo"; + * address.state = "CA"; + * }); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ template void modify( const_iterator itr, name payer, Lambda&& updater ) { @@ -1614,45 +1646,45 @@ class multi_index } /** - * Modifies an existing object in a table. - * @ingroup multiindex - * - * @param obj - a reference to the object to be updated - * @param payer - account name of the payer for the Storage usage of the updated row - * @param updater - lambda function that updates the target object - * - * @pre obj is an existing object in the table - * @pre payer is a valid account that is authorized to execute the action and be billed for storage usage. - * - * @post The modified object is serialized, then replaces the existing object in the table. - * @post Secondary indices are updated; the primary key of the updated object is not changed. - * @post The payer is charged for the storage usage of the updated object. - * @post If payer is the same as the existing payer, payer only pays for the usage difference between existing and updated object (and is refunded if this difference is negative). - * @post If payer is different from the existing payer, the existing payer is refunded for the storage usage of the existing object. - * - * Exceptions: - * If called with an invalid precondition, execution is aborted. - * - * Example: - * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} - * - * void myaction() { - * // create reference to address_index - see emplace example - * // add dan account to table - see emplace example - * - * auto itr = addresses.find("dan"_n); - * eosio::check(itr != addresses.end(), "Address for account not found"); - * addresses.modify( *itr, payer, [&]( auto& address ) { - * address.city = "San Luis Obispo"; - * address.state = "CA"; - * }); - * eosio::check(itr->city == "San Luis Obispo", "Lock arf, Address not modified"); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Modifies an existing object in a table. + * @ingroup multiindex + * + * @param obj - a reference to the object to be updated + * @param payer - account name of the payer for the storage usage of the updated row + * @param updater - lambda function that updates the target object + * + * @pre obj is an existing object in the table + * @pre payer is a valid account that is authorized to execute the action and be billed for storage usage. + * + * @post The modified object is serialized, then replaces the existing object in the table. + * @post Secondary indices are updated; the primary key of the updated object is not changed. + * @post The payer is charged for the storage usage of the updated object. + * @post If payer is the same as the existing payer, payer only pays for the usage difference between existing and updated object (and is refunded if this difference is negative). + * @post If payer is different from the existing payer, the existing payer is refunded for the storage usage of the existing object. + * + * Exceptions: + * If called with an invalid precondition, execution is aborted. + * + * Example: + * + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} + * + * void myaction() { + * // create reference to address_index - see emplace example + * // add dan account to table - see emplace example + * + * auto itr = addresses.find("dan"_n); + * eosio::check(itr != addresses.end(), "Address for account not found"); + * addresses.modify( *itr, payer, [&]( auto& address ) { + * address.city = "San Luis Obispo"; + * address.state = "CA"; + * }); + * eosio::check(itr->city == "San Luis Obispo", "Lock arf, Address not modified"); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ template void modify( const T& obj, name payer, Lambda&& updater ) { @@ -1711,29 +1743,36 @@ class multi_index } /** - * Retrieves an existing object from a table using its primary key. - * @ingroup multiindex - * - * @param primary - Primary key value of the object - * @return A constant reference to the object containing the specified primary key. - * - * Exception - No object matches the given key - * - * Example: - * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} - * - * void myaction() { - * // create reference to address_index - see emplace example - * // add dan account to table - see emplace example - * - * auto user = addresses.get("dan"_n); - * eosio::check(user.first_name == "Daniel", "Couldn't get him."); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Retrieves an existing object from a table using its primary key. + * @ingroup multiindex + * + * @param primary - Primary key value of the object. + * @return A constant reference to the object containing the specified primary key. + * + * Exception - No object matches the given key. + * + * Example: + * + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} + * + * void myaction() { + * // create reference to address_index - see emplace example + * // add dan account to table - see emplace example + * + * auto& user = addresses.get("dan"_n); + * eosio::check(user.first_name == "Daniel", "Couldn't get him."); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode + * + * Warning: + * + * Avoid the common pitfall of copy-assigning the T& reference returned + * to a stack-allocated local variable and then passing that into modify of the multi-index. + * The most common mistake is when the local variable is defined as auto + * typename, instead it should be of type auto& or decltype(auto). */ const T& get( uint64_t primary, const char* error_msg = "unable to find key" )const { auto result = find( primary ); @@ -1742,27 +1781,27 @@ class multi_index } /** - * Search for an existing object in a table using its primary key. - * @ingroup multiindex + * Search for an existing object in a table using its primary key. + * @ingroup multiindex * - * @param primary - Primary key value of the object - * @return An iterator to the found object which has a primary key equal to `primary` OR the `end` iterator of the referenced table if an object with primary key `primary` is not found. + * @param primary - Primary key value of the object + * @return An iterator to the found object which has a primary key equal to `primary` OR the `end` iterator of the referenced table if an object with primary key `primary` is not found. * - * Example: + * Example: * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} * - * void myaction() { - * // create reference to address_index - see emplace example - * // add dan account to table - see emplace example + * void myaction() { + * // create reference to address_index - see emplace example + * // add dan account to table - see emplace example * - * auto itr = addresses.find("dan"_n); - * eosio::check(itr != addresses.end(), "Couldn't get him."); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * auto itr = addresses.find("dan"_n); + * eosio::check(itr != addresses.end(), "Couldn't get him."); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ const_iterator find( uint64_t primary )const { auto itr2 = std::find_if(_items_vector.rbegin(), _items_vector.rend(), [&](const item_ptr& ptr) { @@ -1779,12 +1818,12 @@ class multi_index } /** - * Search for an existing object in a table using its primary key. - * @ingroup multiindex + * Search for an existing object in a table using its primary key. + * @ingroup multiindex * - * @param primary - Primary key value of the object - * @param error_msg - error message if an object with primary key `primary` is not found. - * @return An iterator to the found object which has a primary key equal to `primary` OR throws an exception if an object with primary key `primary` is not found. + * @param primary - Primary key value of the object + * @param error_msg - error message if an object with primary key `primary` is not found. + * @return An iterator to the found object which has a primary key equal to `primary` OR throws an exception if an object with primary key `primary` is not found. */ const_iterator require_find( uint64_t primary, const char* error_msg = "unable to find key" )const { @@ -1802,40 +1841,40 @@ class multi_index } /** - * Remove an existing object from a table using its primary key. - * @ingroup multiindex + * Remove an existing object from a table using its primary key. + * @ingroup multiindex * - * @param itr - An iterator pointing to the object to be removed + * @param itr - An iterator pointing to the object to be removed * - * @pre itr points to an existing element - * @post The object is removed from the table and all associated storage is reclaimed. - * @post Secondary indices associated with the table are updated. - * @post The existing payer for storage usage of the object is refunded for the table and secondary indices usage of the removed object, and if the table and indices are removed, for the associated overhead. + * @pre itr points to an existing element + * @post The object is removed from the table and all associated storage is reclaimed. + * @post Secondary indices associated with the table are updated. + * @post The existing payer for storage usage of the object is refunded for the table and secondary indices usage of the removed object, and if the table and indices are removed, for the associated overhead. * - * @return For the signature with `const_iterator`, returns a pointer to the object following the removed object. + * @return For the signature with `const_iterator`, returns a pointer to the object following the removed object. * - * Exceptions: - * The object to be removed is not in the table. - * The action is not authorized to modify the table. - * The given iterator is invalid. + * Exceptions: + * The object to be removed is not in the table. + * The action is not authorized to modify the table. + * The given iterator is invalid. * - * Example: + * Example: * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} * - * void myaction() { - * // create reference to address_index - see emplace example - * // add dan account to table - see emplace example + * void myaction() { + * // create reference to address_index - see emplace example + * // add dan account to table - see emplace example * - * auto itr = addresses.find("dan"_n); - * eosio::check(itr != addresses.end(), "Address for account not found"); - * addresses.erase( itr ); - * eosio::check(itr != addresses.end(), "Everting lock arf, Address not erased properly"); - * } - * } - * EOSIO_ABI( addressbook, (myaction) ) - * @endcode + * auto itr = addresses.find("dan"_n); + * eosio::check(itr != addresses.end(), "Address for account not found"); + * addresses.erase( itr ); + * eosio::check(itr != addresses.end(), "Everting lock arf, Address not erased properly"); + * } + * } + * EOSIO_ABI( addressbook, (myaction) ) + * @endcode */ const_iterator erase( const_iterator itr ) { eosio::check( itr != end(), "cannot pass end iterator to erase" ); @@ -1849,36 +1888,36 @@ class multi_index } /** - * Remove an existing object from a table using its primary key. - * @ingroup multiindex - * - * @param obj - Object to be removed - * - * @pre obj is an existing object in the table - * @post The object is removed from the table and all associated storage is reclaimed. - * @post Secondary indices associated with the table are updated. - * @post The existing payer for storage usage of the object is refunded for the table and secondary indices usage of the removed object, and if the table and indices are removed, for the associated overhead. - * - * Exceptions: - * The object to be removed is not in the table. - * The action is not authorized to modify the table. - * The given iterator is invalid. - * - * Example: - * - * @code - * // This assumes the code from the constructor example. Replace myaction() {...} - * - * void myaction() { - * auto itr = addresses.find("dan"_n); - * eosio::check(itr != addresses.end(), "Record is not found"); - * addresses.erase(*itr); - * itr = addresses.find("dan"_n); - * eosio::check(itr == addresses.end(), "Record is not deleted"); - * } - * } - * EOSIO_DISPATCH( addressbook, (myaction) ) - * @endcode + * Remove an existing object from a table using its primary key. + * @ingroup multiindex + * + * @param obj - Object to be removed + * + * @pre obj is an existing object in the table + * @post The object is removed from the table and all associated storage is reclaimed. + * @post Secondary indices associated with the table are updated. + * @post The existing payer for storage usage of the object is refunded for the table and secondary indices usage of the removed object, and if the table and indices are removed, for the associated overhead. + * + * Exceptions: + * The object to be removed is not in the table. + * The action is not authorized to modify the table. + * The given iterator is invalid. + * + * Example: + * + * @code + * // This assumes the code from the constructor example. Replace myaction() {...} + * + * void myaction() { + * auto itr = addresses.find("dan"_n); + * eosio::check(itr != addresses.end(), "Record is not found"); + * addresses.erase(*itr); + * itr = addresses.find("dan"_n); + * eosio::check(itr == addresses.end(), "Record is not deleted"); + * } + * } + * EOSIO_DISPATCH( addressbook, (myaction) ) + * @endcode */ void erase( const T& obj ) { using namespace _multi_index_detail; @@ -1894,8 +1933,6 @@ class multi_index eosio::check( itr2 != _items_vector.rend(), "attempt to remove object that was not in multi_index" ); - _items_vector.erase(--(itr2.base())); - internal_use_do_not_use::db_remove_i64( objitem.__primary_itr ); hana::for_each( _indices, [&]( auto& idx ) { @@ -1909,6 +1946,8 @@ class multi_index if( i >= 0 ) secondary_index_db_functions::db_idx_remove( i ); }); + + _items_vector.erase(--(itr2.base())); } }; diff --git a/libraries/eosiolib/contracts/eosio/privileged.hpp b/libraries/eosiolib/contracts/eosio/privileged.hpp index 53f194ef09..d40df72339 100644 --- a/libraries/eosiolib/contracts/eosio/privileged.hpp +++ b/libraries/eosiolib/contracts/eosio/privileged.hpp @@ -27,6 +27,9 @@ namespace eosio { __attribute__((eosio_wasm_import)) uint32_t get_blockchain_parameters_packed( char* data, uint32_t datalen ); + __attribute__((eosio_wasm_import)) + void set_kv_parameters_packed( const char* data, uint32_t datalen ); + __attribute((eosio_wasm_import)) int64_t set_proposed_producers( char*, uint32_t ); @@ -51,8 +54,8 @@ namespace eosio { struct blockchain_parameters { /** - * The maxiumum net usage in instructions for a block - * @brief the maxiumum net usage in instructions for a block + * The maximum net usage in instructions for a block + * @brief the maximum net usage in instructions for a block */ uint64_t max_block_net_usage; @@ -92,8 +95,8 @@ namespace eosio { uint32_t context_free_discount_net_usage_den; /** - * The maxiumum billable cpu usage (in microseconds) for a block - * @brief The maxiumum billable cpu usage (in microseconds) for a block + * The maximum billable cpu usage (in microseconds) for a block + * @brief The maximum billable cpu usage (in microseconds) for a block */ uint32_t max_block_cpu_usage; @@ -182,6 +185,54 @@ namespace eosio { */ void get_blockchain_parameters(eosio::blockchain_parameters& params); + /** + * Tunable KV configuration that can be changed via consensus + * @ingroup privileged + */ + struct kv_parameters { + /** + * The maximum key size + * @brief The maximum key size + */ + uint32_t max_key_size; + + /** + * The maximum value size + * @brief The maximum value size + */ + uint32_t max_value_size; + + /** + * The maximum number of iterators + * @brief The maximum number of iterators + */ + uint32_t max_iterators; + + EOSLIB_SERIALIZE( kv_parameters, + (max_key_size) + (max_value_size)(max_iterators) + ) + }; + + /** + * Set the kv parameters + * + * @ingroup privileged + * @param params - New kv parameters to set + */ + inline void set_kv_parameters(const eosio::kv_parameters& params) { + // set_kv_parameters_packed expects version, max_key_size, + // max_value_size, and max_iterators, + // while kv_parameters only contains max_key_size, max_value_size, + // and max_iterators. That's why we place uint32_t in front + // of kv_parameters in buf + char buf[sizeof(uint32_t) + sizeof(eosio::kv_parameters)]; + eosio::datastream ds( buf, sizeof(buf) ); + ds << uint32_t(0); // fill in version + ds << params; + internal_use_do_not_use::set_kv_parameters_packed( buf, ds.tellp() ); + } + /** * Get the resource limits of an account * diff --git a/libraries/eosiolib/contracts/eosio/producer_schedule.hpp b/libraries/eosiolib/contracts/eosio/producer_schedule.hpp index 0c1c800fc4..60f98eb5b1 100644 --- a/libraries/eosiolib/contracts/eosio/producer_schedule.hpp +++ b/libraries/eosiolib/contracts/eosio/producer_schedule.hpp @@ -41,7 +41,7 @@ namespace eosio { /// @endcond - EOSLIB_SERIALIZE( producer_key, (producer_name)(block_signing_key) ) + CDT_REFLECT(producer_name, block_signing_key); }; /** @@ -66,6 +66,8 @@ namespace eosio { * List of producers for this schedule, including its signing key */ std::vector producers; + + CDT_REFLECT(version, producers); }; /** @@ -95,7 +97,7 @@ namespace eosio { */ uint16_t weight; - EOSLIB_SERIALIZE( key_weight, (key)(weight) ) + CDT_REFLECT(key, weight); }; /** @@ -123,7 +125,7 @@ namespace eosio { bool is_valid()const; - EOSLIB_SERIALIZE( block_signing_authority_v0, (threshold)(keys) ) + CDT_REFLECT(threshold, keys); }; /** @@ -158,7 +160,7 @@ namespace eosio { return a.producer_name < b.producer_name; } - EOSLIB_SERIALIZE( producer_authority, (producer_name)(authority) ) + CDT_REFLECT(producer_name, authority); }; /** diff --git a/libraries/eosiolib/contracts/eosio/table.hpp b/libraries/eosiolib/contracts/eosio/table.hpp new file mode 100644 index 0000000000..dc43212ab8 --- /dev/null +++ b/libraries/eosiolib/contracts/eosio/table.hpp @@ -0,0 +1,880 @@ +#pragma once +#include "../../core/eosio/datastream.hpp" +#include "../../core/eosio/name.hpp" +#include "../../core/eosio/varint.hpp" + +#include "../../core/eosio/key_utils.hpp" + +#include +#include +#include + +#warning "eosio::kv::table is designated as `alpha` and should not be used in production code" + +/** + * @defgroup keyvaluetable Key Value Table + * @ingroup contracts + */ + +#define EOSIO_CDT_GET_RETURN_T(value_class, index_name) std::decay_t()))> + +/** + * @ingroup keyvaluetable + * + * @brief Macro to define an index. + * @details This macro allows users to conveniently define an index without having to specify + * the index template type, as those can be large/unwieldy to type out. It can be used for both primary and secondary indexes. + * + * @param index_name - The index name. + * @param member_name - The name of the member pointer used for the index. This also defines the index's C++ variable name. + */ +#define KV_NAMED_INDEX(index_name, member_name) \ + index member_name{eosio::name{index_name}, &value_type::member_name}; + +namespace eosio::kv { + +namespace detail { + constexpr inline size_t max_stack_buffer_size = 512; + + template + static void serialize(const V& value, void* buffer, size_t size) { + datastream ds((char*)buffer, size); + ds << value; + } + + template + static void deserialize(V& value, const void* buffer, size_t size) { + datastream ds((char*)buffer, size); + ds >> value; + } + + template + static size_t get_size(const V& value) { + auto size = pack_size(value); + return size + 1; + } + + template + static size_t get_size(const std::variant& value) { + auto size = pack_size(value); + return size; + } +} + +/* @cond PRIVATE */ +template +inline key_type make_key(T&& t) { + auto bytes = convert_to_key(std::forward(t)); + return key_type(std::move(bytes)); +} + +inline key_type make_prefix(eosio::name table_name, eosio::name index_name, uint8_t status = 1) { + return make_key(std::make_tuple(status, table_name, index_name)); +} + +inline key_type table_key(const key_type& prefix, const key_type& key) { + return prefix + key; +} +/* @endcond */ + +// This is the "best" way to document a function that does not technically exist using Doxygen. +#if EOSIO_CDT_DOXYGEN +/** + * @brief A function for converting types to the appropriate binary representation for the EOSIO Key Value database. + * @details The CDT provides implementations of this function for many of the common primitives and for structs/tuples. + * If sticking with standard types, contract developers should not need to interact with this function. + * If doing something more advanced, contract developers may need to provide their own implementation for a special type. + */ +template +inline key_type make_key(T val) { + return {}; +} +#endif + +/** + * non_unique provides a clear way for developers to mark an index as non-unique + */ +template +using non_unique = std::tuple; + +template +class table; + +namespace internal { + + class table_base; + + class index_base { + + public: + eosio::name index_name; + eosio::name table_name; + eosio::name contract_name; + + key_type to_table_key(const key_type& k) const { return prefix + k; } + + protected: + index_base() = default; + + template + index_base(eosio::name index_name, KF&& kf, T*) : index_name{index_name} { + key_function = [=](const void* t) { + return make_key(std::invoke(kf, static_cast(t))); + }; + } + + template + key_type get_key(const T& inst) const { return key_function(&inst); } + key_type get_key_void(const void* ptr) const { return key_function(ptr); } + + void get(const key_type& key, void* ret_val, void (*deserialize)(void*, const void*, std::size_t)) const; + + table_base* tbl; + key_type prefix; + + private: + template + friend class eosio::kv::table; + friend class table_base; + friend class iterator_base; + + std::function key_function; + + virtual void setup() = 0; + }; + + class table_base { + protected: + friend class index_base; + friend class iterator_base; + eosio::name contract_name; + eosio::name table_name; + + eosio::name primary_index_name; + + index_base* primary_index; + std::vector secondary_indices; + + void put(const void* value, void* old_value, + std::size_t (*get_size)(const void*), + void (*deserialize)(void*, const void*, std::size_t), + void (*serialize)(const void*, void*, std::size_t), + eosio::name payer) { + uint32_t value_size; + + auto primary_key = primary_index->get_key_void(value); + auto tbl_key = make_prefix(table_name, primary_index->index_name) + primary_key; + + auto primary_key_found = internal_use_do_not_use::kv_get(contract_name.value, tbl_key.data(), tbl_key.size(), value_size); + + if (primary_key_found) { + void* buffer = value_size > detail::max_stack_buffer_size ? malloc(value_size) : alloca(value_size); + auto copy_size = internal_use_do_not_use::kv_get_data(0, (char*)buffer, value_size); + + deserialize(old_value, buffer, copy_size); + + if (value_size > detail::max_stack_buffer_size) { + free(buffer); + } + } + + for (const auto& idx : secondary_indices) { + uint32_t value_size; + auto sec_tbl_key = make_prefix(table_name, idx->index_name) + idx->get_key_void(value); + auto sec_found = internal_use_do_not_use::kv_get(contract_name.value, sec_tbl_key.data(), sec_tbl_key.size(), value_size); + + if (!primary_key_found) { + eosio::check(!sec_found, "Attempted to store an existing secondary index."); + internal_use_do_not_use::kv_set(contract_name.value, sec_tbl_key.data(), sec_tbl_key.size(), tbl_key.data(), tbl_key.size(), payer.value); + } else { + if (sec_found) { + void* buffer = value_size > detail::max_stack_buffer_size ? malloc(value_size) : alloca(value_size); + auto copy_size = internal_use_do_not_use::kv_get_data(0, (char*)buffer, value_size); + + auto res = memcmp(buffer, tbl_key.data(), copy_size); + eosio::check(copy_size == tbl_key.size() && res == 0, "Attempted to update an existing secondary index."); + + if (copy_size > detail::max_stack_buffer_size) { + free(buffer); + } + } else { + auto old_sec_key = make_prefix(table_name, idx->index_name) + idx->get_key_void(old_value); + internal_use_do_not_use::kv_erase(contract_name.value, old_sec_key.data(), old_sec_key.size()); + internal_use_do_not_use::kv_set(contract_name.value, sec_tbl_key.data(), sec_tbl_key.size(), tbl_key.data(), tbl_key.size(), payer.value); + } + } + } + + size_t data_size = get_size(value); + void* data_buffer = data_size > detail::max_stack_buffer_size ? malloc(data_size) : alloca(data_size); + + serialize(value, data_buffer, data_size); + + internal_use_do_not_use::kv_set(contract_name.value, tbl_key.data(), tbl_key.size(), (const char*)data_buffer, data_size, payer.value); + + if (data_size > detail::max_stack_buffer_size) { + free(data_buffer); + } + } + + void erase(const void* value) { + uint32_t value_size; + + auto primary_key = primary_index->get_key_void(value); + auto tbl_key = make_prefix(table_name, primary_index->index_name) + primary_key; + auto primary_key_found = internal_use_do_not_use::kv_get(contract_name.value, tbl_key.data(), tbl_key.size(), value_size); + + if (!primary_key_found) { + return; + } + + for (const auto& idx : secondary_indices) { + auto sec_tbl_key = make_prefix(table_name, idx->index_name) + idx->get_key_void(value); + internal_use_do_not_use::kv_erase(contract_name.value, sec_tbl_key.data(), sec_tbl_key.size()); + } + + internal_use_do_not_use::kv_erase(contract_name.value, tbl_key.data(), tbl_key.size()); + } + }; + + inline void index_base::get(const key_type& key, void* ret_val, void (*deserialize)(void*, const void*, std::size_t)) const { + uint32_t value_size; + uint32_t actual_data_size; + + auto success = internal_use_do_not_use::kv_get(contract_name.value, key.data(), key.size(), value_size); + if (!success) { + return; + } + + void* buffer = value_size > detail::max_stack_buffer_size ? malloc(value_size) : alloca(value_size); + auto copy_size = internal_use_do_not_use::kv_get_data(0, (char*)buffer, value_size); + + void* deserialize_buffer = buffer; + size_t deserialize_size = copy_size; + + bool is_primary = index_name == tbl->primary_index_name; + if (!is_primary) { + auto success = internal_use_do_not_use::kv_get(contract_name.value, (char*)buffer, copy_size, actual_data_size); + eosio::check(success, "failure getting primary key"); + + void* pk_buffer = actual_data_size > detail::max_stack_buffer_size ? malloc(actual_data_size) : alloca(actual_data_size); + auto pk_copy_size = internal_use_do_not_use::kv_get_data(0, (char*)pk_buffer, actual_data_size); + + deserialize_buffer = pk_buffer; + deserialize_size = pk_copy_size; + } + + deserialize(ret_val, deserialize_buffer, deserialize_size); + + if (value_size > detail::max_stack_buffer_size) { + free(buffer); + } + + if (!is_primary && actual_data_size > detail::max_stack_buffer_size) { + free(deserialize_buffer); + } + } + + class iterator_base { + public: + enum class status { + iterator_ok = 0, // Iterator is positioned at a key-value pair + iterator_erased = -1, // The key-value pair that the iterator used to be positioned at was erased + iterator_end = -2, // Iterator is out-of-bounds + }; + + iterator_base() = default; + + iterator_base(uint32_t itr, status itr_stat, const index_base* index) : itr{itr}, itr_stat{itr_stat}, index{index} {} + + iterator_base(iterator_base&& other) : + itr(std::exchange(other.itr, 0)), + itr_stat(std::move(other.itr_stat)) + {} + + ~iterator_base() { + if (itr) { + internal_use_do_not_use::kv_it_destroy(itr); + } + } + + iterator_base& operator=(iterator_base&& other) { + if (itr) { + internal_use_do_not_use::kv_it_destroy(itr); + } + itr = std::exchange(other.itr, 0); + itr_stat = std::move(other.itr_stat); + return *this; + } + + bool is_end()const { return itr_stat != status::iterator_end; } + + /** + * Returns the value that the iterator points to. + * @ingroup keyvaluetable + * + * @return The value that the iterator points to. + */ + void value(void* val, void (*deserialize)(void*, const void*, std::size_t)) const { + using namespace detail; + + eosio::check(itr_stat != status::iterator_end, "Cannot read end iterator"); + + uint32_t value_size; + uint32_t actual_value_size; + uint32_t actual_data_size; + uint32_t offset = 0; + + // call once to get the value_size + internal_use_do_not_use::kv_it_value(itr, 0, (char*)nullptr, 0, value_size); + + void* buffer = value_size > detail::max_stack_buffer_size ? malloc(value_size) : alloca(value_size); + auto stat = internal_use_do_not_use::kv_it_value(itr, offset, (char*)buffer, value_size, actual_value_size); + + eosio::check(static_cast(stat) == status::iterator_ok, "Error reading value"); + + void* deserialize_buffer = buffer; + size_t deserialize_size = actual_value_size; + + bool is_primary = index->index_name == index->tbl->primary_index_name; + if (!is_primary) { + auto success = internal_use_do_not_use::kv_get(index->contract_name.value, (char*)buffer, actual_value_size, actual_data_size); + eosio::check(success, "failure getting primary key in `value()`"); + + void* pk_buffer = actual_data_size > detail::max_stack_buffer_size ? malloc(actual_data_size) : alloca(actual_data_size); + internal_use_do_not_use::kv_get_data(0, (char*)pk_buffer, actual_data_size); + + deserialize_buffer = pk_buffer; + deserialize_size = actual_data_size; + } + + deserialize(val, deserialize_buffer, deserialize_size); + + if (value_size > detail::max_stack_buffer_size) { + free(buffer); + } + + if (!is_primary && actual_data_size > detail::max_stack_buffer_size) { + free(deserialize_buffer); + } + } + + key_type key() const { + uint32_t actual_value_size; + uint32_t value_size; + + // call once to get the value size + internal_use_do_not_use::kv_it_key(itr, 0, (char*)nullptr, 0, value_size); + + void* buffer = value_size > detail::max_stack_buffer_size ? malloc(value_size) : alloca(value_size); + auto stat = internal_use_do_not_use::kv_it_key(itr, 0, (char*)buffer, value_size, actual_value_size); + + eosio::check(static_cast(stat) == status::iterator_ok, "Error getting key"); + + return eosio::key_type{(char*)buffer, actual_value_size}; + } + + bool valid() const { return itr_stat == status::iterator_ok; } + + protected: + uint32_t itr; + status itr_stat; + + const index_base* index; + + int compare(const iterator_base& b) const { + bool a_is_end = !itr || itr_stat == status::iterator_end; + bool b_is_end = !b.itr || b.itr_stat == status::iterator_end; + if (a_is_end && b_is_end) { + return 0; + } else if (a_is_end && b.itr) { + return 1; + } else if (itr && b_is_end) { + return -1; + } else { + return internal_use_do_not_use::kv_it_compare(itr, b.itr); + } + } + }; + +} + +/** + * @ingroup keyvaluetable + * + * @brief Defines an EOSIO Key Value Table + * @details EOSIO Key Value API provides a C++ interface to the EOSIO Key Value database. + * Key Value Tables require 1 primary index, of any type that can be serialized to a binary representation. + * Key Value Tables support 0 or more secondary index, of any type that can be serialized to a binary representation. + * Indexes must be a member variable or a member function. + * + * @tparam T - the type of the data stored as the value in the table + */ +template +class table : internal::table_base { +public: + template + class index; + + table(const table&) = delete; // disable copy constructor + table(table&&) = delete; // disable move constructor + table& operator=(const table&) = delete; // disable assignment copy operator + table& operator=(table&&) = delete; // disable assignment move operator + +private: + using index_base = internal::index_base; + + class base_iterator : public internal::iterator_base { + public: + using iterator_base::iterator_base; + /** + * Returns the value that the iterator points to. + * @ingroup keyvaluetable + * + * @return The value that the iterator points to. + */ + T value() const { + T val; + iterator_base::value(&val, &table::deserialize_fun); + return val; + } + }; + + class iterator : public base_iterator { + using base_iterator::itr; + using base_iterator::itr_stat; + using base_iterator::index; + + template + friend class index; + + public: + using status = typename base_iterator::status; + + iterator() = default; + + iterator(uint32_t itr, status itr_stat, const index_base* index) : base_iterator{itr, itr_stat, index} {} + + iterator(iterator&& other) : base_iterator{std::move(other)} {} + + iterator& operator=(iterator&& other) { + if (itr) { + internal_use_do_not_use::kv_it_destroy(itr); + } + itr = std::exchange(other.itr, 0); + itr_stat = std::move(other.itr_stat); + index = std::move(other.index); + return *this; + } + + iterator& operator++() { + eosio::check(itr_stat != status::iterator_end, "cannot increment end iterator"); + itr_stat = static_cast(detail::itr_next(itr)); + return *this; + } + + iterator& operator--() { + if (!itr) { + itr = internal_use_do_not_use::kv_it_create(index->contract_name.value, index->prefix.data(), index->prefix.size()); + } + itr_stat = static_cast(detail::itr_prev(itr)); + eosio::check(itr_stat != status::iterator_end, "decremented past the beginning"); + return *this; + } + + bool operator==(const iterator& b) const { + return base_iterator::compare(b) == 0; + } + + bool operator!=(const iterator& b) const { + return base_iterator::compare(b) != 0; + } + + bool operator<(const iterator& b) const { + return base_iterator::compare(b) < 0; + } + + bool operator<=(const iterator& b) const { + return base_iterator::compare(b) <= 0; + } + + bool operator>(const iterator& b) const { + return base_iterator::compare(b) > 0; + } + + bool operator>=(const iterator& b) const { + return base_iterator::compare(b) >= 0; + } + + explicit operator bool() const { + return this->valid(); + } + }; + + class reverse_iterator : public base_iterator { + using base_iterator::itr; + using base_iterator::itr_stat; + using base_iterator::index; + + public: + using status = typename base_iterator::status; + + reverse_iterator() = default; + + reverse_iterator(uint32_t itr, status itr_stat, const index_base* index) : base_iterator{itr, itr_stat, index} {} + + reverse_iterator(reverse_iterator&& other) : base_iterator{std::move(other)} {} + + reverse_iterator& operator=(reverse_iterator&& other) { + if (itr) { + internal_use_do_not_use::kv_it_destroy(itr); + } + itr = std::exchange(other.itr, 0); + itr_stat = std::move(other.itr_stat); + index = std::move(other.index); + return *this; + } + + reverse_iterator& operator++() { + eosio::check(itr_stat != status::iterator_end, "incremented past the end"); + itr_stat = static_cast(detail::itr_prev(itr)); + return *this; + } + + reverse_iterator& operator--() { + if (!itr) { + itr = internal_use_do_not_use::kv_it_create(index->contract_name.value, index->prefix.data(), index->prefix.size()); + itr_stat = static_cast(detail::itr_lower_bound(itr)); + } + itr_stat = static_cast(detail::itr_next(itr)); + eosio::check(itr_stat != status::iterator_end, "decremented past the beginning"); + return *this; + } + + int compare(const reverse_iterator& b) const { + bool a_is_end = !itr || itr_stat == status::iterator_end; + bool b_is_end = !b.itr || b.itr_stat == status::iterator_end; + if (a_is_end && b_is_end) { + return 0; + } else if (a_is_end && b.itr) { + return 1; + } else if (itr && b_is_end) { + return -1; + } else { + return -(internal_use_do_not_use::kv_it_compare(itr, b.itr)); + } + } + + bool operator==(const reverse_iterator& b) const { + return compare(b) == 0; + } + + bool operator!=(const reverse_iterator& b) const { + return compare(b) != 0; + } + + bool operator<(const reverse_iterator& b) const { + return compare(b) < 0; + } + + bool operator<=(const reverse_iterator& b) const { + return compare(b) <= 0; + } + + bool operator>(const reverse_iterator& b) const { + return compare(b) > 0; + } + + bool operator>=(const reverse_iterator& b) const { + return compare(b) >= 0; + } + }; + +public: + using iterator = table::iterator; + using value_type = T; + + /** + * @ingroup keyvaluetable + * + * @brief Defines an index on an EOSIO Key Value Table + * @details A Key Value Index allows a user of the table to search based on a given field. + * The only restrictions on that field are that it is serializable to a binary representation sortable by the KV intrinsics. + * Convenience functions exist to handle most of the primitive types as well as some more complex types, and are + * used automatically where possible. + * + * @tparam K - The type of the key used in the index. + */ + template + class index : public index_base { + public: + using iterator = table::iterator; + using table::index_base::tbl; + using table::index_base::table_name; + using table::index_base::contract_name; + using table::index_base::index_name; + using table::index_base::prefix; + + template + index(eosio::name name, KF&& kf) : index_base{name, kf, (T*)nullptr} { + static_assert(std::is_same_v()))>>>, + "Make sure the variable/function passed to the constructor returns the same type as the template parameter."); + } + + /** + * Search for an existing object in a table by the index, using the given key. + * @ingroup keyvaluetable + * + * @param key - The key to search for. + * @return An iterator to the found object OR the `end` iterator if the given key was not found. + */ + iterator find(const K& key) const { + auto t_key = prefix + make_key(key); + + uint32_t itr = internal_use_do_not_use::kv_it_create(contract_name.value, prefix.data(), prefix.size()); + int32_t itr_stat = detail::itr_lower_bound(itr, {t_key.data(), t_key.size()}); + + auto cmp = internal_use_do_not_use::kv_it_key_compare(itr, t_key.data(), t_key.size()); + + if (cmp != 0) { + internal_use_do_not_use::kv_it_destroy(itr); + return end(); + } + + return {itr, static_cast(itr_stat), this}; + } + + /** + * Check if a given key exists in the index. + * @ingroup keyvaluetable + * + * @param key - The key to check for. + * @return If the key exists or not. + */ + bool exists(const K& key) const { + uint32_t value_size; + auto t_key = prefix + make_key(key); + + return internal_use_do_not_use::kv_get(contract_name.value, t_key.data(), t_key.size(), value_size); + } + + /** + * Get the value for an existing object in a table by the index, using the given key. + * @ingroup keyvaluetable + * + * @param key - The key to search for. + * @return The value corresponding to the key. + */ + T operator[](const K& key) const { + auto opt = get(key); + eosio::check(opt.has_value(), "Key not found in `[]`"); + return *opt; + } + + /** + * Get the value for an existing object in a table by the index, using the given key. + * @ingroup keyvaluetable + * + * @param key - The key to search for. + * @return A std::optional of the value corresponding to the key. + */ + std::optional get(const K& key) const { + std::optional ret_val; + auto k = prefix + make_key(key); + index_base::get(k, &ret_val, &deserialize_optional_fun); + return ret_val; + } + + /** + * Returns an iterator to the object with the lowest key (by this index) in the table. + * @ingroup keyvaluetable + * + * @return An iterator to the object with the lowest key (by this index) in the table. + */ + iterator begin() const { + uint32_t itr = internal_use_do_not_use::kv_it_create(contract_name.value, prefix.data(), prefix.size()); + int32_t itr_stat = detail::itr_lower_bound(itr); + + return {itr, static_cast(itr_stat), this}; + } + + /** + * Returns an iterator pointing past the end. It does not point to any element, therefore `value` should not be called on it. + * @ingroup keyvaluetable + * + * @return An iterator pointing past the end. + */ + iterator end() const { + return {0, iterator::status::iterator_end, this}; + } + + /** + * Returns a reverse iterator to the object with the highest key (by this index) in the table. + * @ingroup keyvaluetable + * + * @return A reverse iterator to the object with the highest key (by this index) in the table. + */ + reverse_iterator rbegin() const { + uint32_t itr = internal_use_do_not_use::kv_it_create(contract_name.value, prefix.data(), prefix.size()); + int32_t itr_stat = detail::itr_prev(itr); + + return {itr, static_cast(itr_stat), this}; + } + + /** + * Returns a reverse iterator pointing past the beginning. It does not point to any element, therefore `value` should not be called on it. + * @ingroup keyvaluetable + * + * @return A reverse iterator pointing past the beginning. + */ + reverse_iterator rend() const { + return {0, iterator::status::iterator_end, this}; + } + + /** + * Returns an iterator pointing to the element with the lowest key greater than or equal to the given key. + * @ingroup keyvaluetable + * + * @return An iterator pointing to the element with the lowest key greater than or equal to the given key. + */ + iterator lower_bound(const K& key) const { + auto t_key = prefix + make_key(key); + + uint32_t itr = internal_use_do_not_use::kv_it_create(contract_name.value, prefix.data(), prefix.size()); + int32_t itr_stat = detail::itr_lower_bound(itr, {t_key.data(), t_key.size()}); + + return {itr, static_cast(itr_stat), this}; + } + + /** + * Returns an iterator pointing to the first element greater than the given key. + * @ingroup keyvaluetable + * + * @return An iterator pointing to the first element greater than the given key. + */ + iterator upper_bound(const K& key) const { + auto t_key = prefix + make_key(key); + auto it = lower_bound(key); + + int32_t cmp; + + if (it.itr == 0 || it.itr_stat == base_iterator::status::iterator_end) { + cmp = 1; + } else { + cmp = internal_use_do_not_use::kv_it_key_compare(it.itr, t_key.data(), t_key.size()); + } + if (cmp == 0) { + ++it; + } + + return it; + } + + /** + * Returns a vector of objects that fall between the specifed range. The range is inclusive, exclusive. + * @ingroup keyvaluetable + * + * @param begin - The beginning of the range (inclusive). + * @param end - The end of the range (exclusive). + * @return A vector containing all the objects that fall between the range. + */ + std::vector range(const K& b, const K& e) const { + std::vector return_values; + + for(auto itr = lower_bound(b), end_itr = lower_bound(e); itr < end_itr; ++itr) { + return_values.push_back(itr.value()); + } + + return return_values; + } + + void setup() override { + prefix = make_prefix(table_name, index_name); + } + }; + + /** + * @ingroup keyvaluetable + * Puts a value into the table. If the value already exists, it updates the existing entry. + * The key is determined from the defined primary index. + * If the put attempts to store over an existing secondary index, the transaction will be aborted. + * + * @param value - The entry to be stored in the table. + * @param payer - The payer for the entry. + */ + void put(const T& value, eosio::name payer) { + T old_value; + table_base::put(&value, &old_value, &get_size_fun, &deserialize_fun, &serialize_fun, payer); + } + + /* @cond PRIVATE */ + static void deserialize_optional_fun(void* value, const void* buffer, std::size_t buffer_size) { + static_cast*>(value)->emplace(); + return detail::deserialize(**static_cast*>(value), buffer, buffer_size); + } + static void deserialize_fun(void* value, const void* buffer, std::size_t buffer_size) { + return detail::deserialize(*static_cast(value), buffer, buffer_size); + } + static void serialize_fun(const void* value, void* buffer, std::size_t buffer_size) { + return detail::serialize(*static_cast(value), buffer, buffer_size); + } + static std::size_t get_size_fun(const void* value) { + return detail::get_size(*static_cast(value)); + } + /* @endcond */ + + /** + * Removes a value from the table. + * @ingroup keyvaluetable + * + * @param key - The key of the value to be removed. + */ + void erase(const T& value) { + table_base::erase(&value); + } + +protected: + table() = default; + + template + void setup_indices(I& index) { + index_base* idx = &index; + idx->contract_name = contract_name; + idx->table_name = table_name; + idx->tbl = this; + + idx->setup(); + secondary_indices.push_back(idx); + } + + template + void init(eosio::name contract, PrimaryIndex& prim_index, SecondaryIndices&... indices) { + validate_types(prim_index); + (validate_types(indices), ...); + + contract_name = contract; + table_name = eosio::name{TableName}; + + primary_index = &prim_index; + primary_index->contract_name = contract_name; + primary_index->table_name = table_name; + primary_index->tbl = this; + + primary_index->setup(); + + primary_index_name = primary_index->index_name; + + (setup_indices(indices), ...); + } + +private: + + constexpr void validate_types() {} + + template + constexpr void validate_types(Type& t) { + constexpr bool is_index = std::is_base_of_v>; + static_assert(is_index, "Incorrect type passed to init. Must be a reference to an index."); + } +}; +} // namespace eosio::kv diff --git a/libraries/eosiolib/core/eosio/binary_extension.hpp b/libraries/eosiolib/core/eosio/binary_extension.hpp index ac59569e43..44b51af222 100644 --- a/libraries/eosiolib/core/eosio/binary_extension.hpp +++ b/libraries/eosiolib/core/eosio/binary_extension.hpp @@ -40,8 +40,6 @@ namespace eosio { ::new (&_data) T(std::forward(args)...); } - ~binary_extension() { reset(); } - constexpr binary_extension( const binary_extension& other ) :_has_value(other._has_value) { @@ -57,6 +55,33 @@ namespace eosio { } } + /// @cond INTERNAL + ~binary_extension() { reset(); } + + /// @cond INTERNAL + constexpr binary_extension& operator = (const binary_extension& other) { + if (has_value()) + reset(); + + if (other.has_value()) { + ::new (&_data) T(*other); + _has_value = true; + } + return *this; + } + + /// @cond INTERNAL + constexpr binary_extension& operator = (binary_extension&& other) { + if (has_value()) + reset(); + + if (other.has_value()) { + ::new (&_data) T(*other); + _has_value = true; + other._has_value = false; + } + return *this; + } /** test if container is holding a value */ constexpr explicit operator bool()const { return _has_value; } /** test if container is holding a value */ diff --git a/libraries/eosiolib/core/eosio/check.hpp b/libraries/eosiolib/core/eosio/check.hpp index 236b718b5f..2d5d560e57 100644 --- a/libraries/eosiolib/core/eosio/check.hpp +++ b/libraries/eosiolib/core/eosio/check.hpp @@ -4,8 +4,8 @@ */ #pragma once -#include #include +#include namespace eosio { @@ -28,6 +28,22 @@ namespace eosio { * @brief Defines wrappers over eosio_assert */ + + /** + * Assert if the predicate fails and use the supplied message. + * + * @ingroup system + * + * Example: + * @code + * eosio::check(a == b, "a does not equal b"); + * @endcode + */ + inline void check(bool pred, std::string_view msg) { + if (!pred) + internal_use_do_not_use::eosio_assert_message(false, msg.data(), msg.size()); + } + /** * Assert if the predicate fails and use the supplied message. * @@ -56,7 +72,7 @@ namespace eosio { */ inline void check(bool pred, const std::string& msg) { if (!pred) { - internal_use_do_not_use::eosio_assert(false, msg.c_str()); + internal_use_do_not_use::eosio_assert_message(false, msg.data(), msg.size()); } } @@ -72,10 +88,11 @@ namespace eosio { */ inline void check(bool pred, std::string&& msg) { if (!pred) { - internal_use_do_not_use::eosio_assert(false, msg.c_str()); + internal_use_do_not_use::eosio_assert_message(false, msg.data(), msg.size()); } } + /** * Assert if the predicate fails and use a subset of the supplied message. * @@ -106,7 +123,7 @@ namespace eosio { */ inline void check(bool pred, const std::string& msg, size_t n) { if (!pred) { - internal_use_do_not_use::eosio_assert_message(false, msg.c_str(), n); + internal_use_do_not_use::eosio_assert_message(false, msg.data(), n); } } diff --git a/libraries/eosiolib/core/eosio/context.hpp b/libraries/eosiolib/core/eosio/context.hpp new file mode 100644 index 0000000000..956a43c373 --- /dev/null +++ b/libraries/eosiolib/core/eosio/context.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "name.hpp" + +namespace eosio { + namespace internal_use_do_not_use { + extern "C" uint64_t eosio_contract_name; + } + + inline name current_context_contract() { return name{internal_use_do_not_use::eosio_contract_name}; } +} diff --git a/libraries/eosiolib/core/eosio/datastream.hpp b/libraries/eosiolib/core/eosio/datastream.hpp index 5526d2e6bf..b891b03de0 100644 --- a/libraries/eosiolib/core/eosio/datastream.hpp +++ b/libraries/eosiolib/core/eosio/datastream.hpp @@ -65,7 +65,7 @@ class datastream { * @return true */ inline bool read( char* d, size_t s ) { - eosio::check( size_t(_end - _pos) >= (size_t)s, "read" ); + eosio::check( size_t(_end - _pos) >= (size_t)s, "datastream attempted to read past the end" ); memcpy( d, _pos, s ); _pos += s; return true; @@ -79,7 +79,33 @@ class datastream { * @return true */ inline bool write( const char* d, size_t s ) { - eosio::check( _end - _pos >= (int32_t)s, "write" ); + eosio::check( _end - _pos >= (int32_t)s, "datastream attempted to write past the end" ); + memcpy( (void*)_pos, d, s ); + _pos += s; + return true; + } + + /** + * Writes a specified byte into the stream from a buffer + * + * @param d - The byte to be written + * @return true + */ + inline bool write( char d ) { + eosio::check( _end - _pos >= 1, "datastream attempted to write past the end" ); + *_pos++ = d; + return true; + } + + /** + * Writes a specified number of bytes into the stream from a buffer + * + * @param d - The pointer to the source buffer + * @param s - The number of bytes to write + * @return true + */ + inline bool write( const void* d, size_t s ) { + eosio::check( _end - _pos >= (int32_t)s, "datastream attempted to write past the end" ); memcpy( (void*)_pos, d, s ); _pos += s; return true; @@ -88,7 +114,6 @@ class datastream { /** * Writes a byte into the stream * - * @brief Writes a byte into the stream * @param c byte to write * @return true */ @@ -102,7 +127,6 @@ class datastream { /** * Reads a byte from the stream * - * @brief Reads a byte from the stream * @param c - The reference to destination byte * @return true */ @@ -111,7 +135,6 @@ class datastream { /** * Reads a byte from the stream * - * @brief Reads a byte from the stream * @param c - The reference to destination byte * @return true */ @@ -126,7 +149,6 @@ class datastream { /** * Retrieves the current position of the stream * - * @brief Retrieves the current position of the stream * @return T - The current position of the stream */ T pos()const { return _pos; } @@ -135,7 +157,6 @@ class datastream { /** * Sets the position within the current stream * - * @brief Sets the position within the current stream * @param p - The offset relative to the origin * @return true if p is within the range * @return false if p is not within the rawnge @@ -145,7 +166,6 @@ class datastream { /** * Gets the position within the current stream * - * @brief Gets the position within the current stream * @return p - The position within the current stream */ inline size_t tellp()const { return size_t(_pos - _start); } @@ -153,27 +173,20 @@ class datastream { /** * Returns the number of remaining bytes that can be read/skipped * - * @brief Returns the number of remaining bytes that can be read/skipped * @return size_t - The number of remaining bytes */ inline size_t remaining()const { return _end - _pos; } private: /** * The start position of the buffer - * - * @brief The start position of the buffer */ T _start; /** * The current position of the buffer - * - * @brief The current position of the buffer */ T _pos; /** * The end position of the buffer - * - * @brief The end position of the buffer */ T _end; }; @@ -207,6 +220,22 @@ class datastream { */ inline bool write( const char* ,size_t s ) { _size += s; return true; } + /** + * Increment the size by s. This behaves the same as skip( size_t s ) + * + * @param s - The amount of size to increase + * @return true + */ + inline bool write( char ) { _size++; return true; } + + /** + * Increment the size by s. This behaves the same as skip( size_t s ) + * + * @param s - The amount of size to increase + * @return true + */ + inline bool write( const void* ,size_t s ) { _size += s; return true; } + /** * Increment the size by one * @@ -224,7 +253,6 @@ class datastream { /** * Set new size * - * @brief Set new size * @param p - The new size * @return true */ @@ -370,12 +398,12 @@ inline datastream& operator>>(datastream& ds, std::variant& - Reference to the datastream */ -template -DataStream& operator<<( DataStream& ds, const std::pair& t ) { +template +datastream& operator<<( datastream& ds, const std::pair& t ) { ds << std::get<0>(t); ds << std::get<1>(t); return ds; @@ -386,12 +414,12 @@ DataStream& operator<<( DataStream& ds, const std::pair& t ) { * * @param ds - The stream to read * @param t - The destination for deserialized value - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam Args - Type of the objects contained in the tuple - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream */ -template -DataStream& operator>>( DataStream& ds, std::pair& t ) { +template +datastream& operator>>( datastream& ds, std::pair& t ) { T1 t1; T2 t2; ds >> t1; @@ -454,7 +482,6 @@ inline datastream& operator<<(datastream& ds, const bool& d) { /** * Deserialize a bool from a stream * - * @brief Deserialize a bool * @param ds - The stream to read * @param d - The destination for deserialized value * @tparam Stream - Type of datastream buffer @@ -473,11 +500,11 @@ inline datastream& operator>>(datastream& ds, bool& d) { * * @param ds - The stream to write * @param v - The value to serialize - * @tparam DataStream - Type of datastream - * @return DataStream& - Reference to the datastream + * @tparam Stream - Type of datastream buffer + * @return datastream& - Reference to the datastream */ -template -DataStream& operator << ( DataStream& ds, const std::string& v ) { +template +datastream& operator << ( datastream& ds, const std::string& v ) { ds << unsigned_int( v.size() ); if (v.size()) ds.write(v.data(), v.size()); @@ -489,11 +516,11 @@ DataStream& operator << ( DataStream& ds, const std::string& v ) { * * @param ds - The stream to read * @param v - The destination for deserialized value - * @tparam DataStream - Type of datastream - * @return DataStream& - Reference to the datastream + * @tparam Stream - Type of datastream buffer + * @return datastream& - Reference to the datastream */ -template -DataStream& operator >> ( DataStream& ds, std::string& v ) { +template +datastream& operator >> ( datastream& ds, std::string& v ) { std::vector tmp; ds >> tmp; if( tmp.size() ) @@ -508,13 +535,13 @@ DataStream& operator >> ( DataStream& ds, std::string& v ) { * * @param ds - The stream to write * @param v - The value to serialize - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam T - Type of the object contained in the array * @tparam N - Size of the array - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream */ -template -DataStream& operator << ( DataStream& ds, const std::array& v ) { +template +datastream& operator << ( datastream& ds, const std::array& v ) { for( const auto& i : v ) ds << i; return ds; @@ -524,16 +551,15 @@ DataStream& operator << ( DataStream& ds, const std::array& v ) { /** * Deserialize a fixed size std::array * - * @brief Deserialize a fixed size std::array * @param ds - The stream to read * @param v - The destination for deserialized value - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam T - Type of the object contained in the array * @tparam N - Size of the array - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream */ -template -DataStream& operator >> ( DataStream& ds, std::array& v ) { +template +datastream& operator >> ( datastream& ds, std::array& v ) { for( auto& i : v ) ds >> i; return ds; @@ -543,7 +569,6 @@ namespace _datastream_detail { /** * Check if type T is a pointer * - * @brief Check if type T is a pointer * @tparam T - The type to be checked * @return true if T is a pointer * @return false otherwise @@ -558,7 +583,6 @@ namespace _datastream_detail { /** * Check if type T is a primitive type * - * @brief Check if type T is a primitive type * @tparam T - The type to be checked * @return true if T is a primitive type * @return false otherwise @@ -568,20 +592,30 @@ namespace _datastream_detail { return std::is_arithmetic::value || std::is_enum::value; } + + /* + * Check if type T is a specialization of datastream + * + * @tparam T - The type to be checked + */ + template + struct is_datastream { static constexpr bool value = false; }; + template + struct is_datastream> { static constexpr bool value = true; }; } /** - * Pointer should not be serialized, so this function will always throws an error + * Deserialize a pointer * - * @brief Deserialize a a pointer + * @brief Pointer should not be serialized, so this function will always throws an error * @param ds - The stream to read - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam T - Type of the pointer - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream * @post Throw an exception if it is a pointer */ -template()>* = nullptr> -DataStream& operator >> ( DataStream& ds, T ) { +template()>* = nullptr> +datastream& operator >> ( datastream& ds, T ) { static_assert(!_datastream_detail::is_pointer(), "Pointers should not be serialized" ); return ds; } @@ -589,17 +623,16 @@ DataStream& operator >> ( DataStream& ds, T ) { /** * Serialize a fixed size C array of non-primitive and non-pointer type * - * @brief Serialize a fixed size C array of non-primitive and non-pointer type * @param ds - The stream to write * @param v - The value to serialize - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam T - Type of the pointer - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream */ -template() && !_datastream_detail::is_pointer()>* = nullptr> -DataStream& operator << ( DataStream& ds, const T (&v)[N] ) { +datastream& operator << ( datastream& ds, const T (&v)[N] ) { ds << unsigned_int( N ); for( uint32_t i = 0; i < N; ++i ) ds << v[i]; @@ -609,16 +642,15 @@ DataStream& operator << ( DataStream& ds, const T (&v)[N] ) { /** * Serialize a fixed size C array of primitive type * - * @brief Serialize a fixed size C array of primitive type * @param ds - The stream to write * @param v - The value to serialize - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam T - Type of the pointer - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream */ -template()>* = nullptr> -DataStream& operator << ( DataStream& ds, const T (&v)[N] ) { +datastream& operator << ( datastream& ds, const T (&v)[N] ) { ds << unsigned_int( N ); ds.write((char*)&v[0], sizeof(v)); return ds; @@ -627,18 +659,17 @@ DataStream& operator << ( DataStream& ds, const T (&v)[N] ) { /** * Deserialize a fixed size C array of non-primitive and non-pointer type * - * @brief Deserialize a fixed size C array of non-primitive and non-pointer type * @param ds - The stream to read * @param v - The destination for deserialized value * @tparam T - Type of the object contained in the array * @tparam N - Size of the array - * @tparam DataStream - Type of datastream - * @return DataStream& - Reference to the datastream + * @tparam Stream - Type of datastream buffer + * @return datastream& - Reference to the datastream */ -template() && !_datastream_detail::is_pointer()>* = nullptr> -DataStream& operator >> ( DataStream& ds, T (&v)[N] ) { +datastream& operator >> ( datastream& ds, T (&v)[N] ) { unsigned_int s; ds >> s; eosio::check( N == s.value, "T[] size and unpacked size don't match"); @@ -650,17 +681,16 @@ DataStream& operator >> ( DataStream& ds, T (&v)[N] ) { /** * Deserialize a fixed size C array of primitive type * - * @brief Deserialize a fixed size C array of primitive type * @param ds - The stream to read * @param v - The destination for deserialized value * @tparam T - Type of the object contained in the array * @tparam N - Size of the array - * @tparam DataStream - Type of datastream - * @return DataStream& - Reference to the datastream + * @tparam Stream - Type of datastream buffer + * @return datastream& - Reference to the datastream */ -template()>* = nullptr> -DataStream& operator >> ( DataStream& ds, T (&v)[N] ) { +datastream& operator >> ( datastream& ds, T (&v)[N] ) { unsigned_int s; ds >> s; eosio::check( N == s.value, "T[] size and unpacked size don't match"); @@ -671,14 +701,13 @@ DataStream& operator >> ( DataStream& ds, T (&v)[N] ) { /** * Serialize a vector of char * - * @brief Serialize a vector of char * @param ds - The stream to write * @param v - The value to serialize - * @tparam DataStream - Type of datastream - * @return DataStream& - Reference to the datastream + * @tparam Stream - Type of datastream buffer + * @return datastream& - Reference to the datastream */ -template -DataStream& operator << ( DataStream& ds, const std::vector& v ) { +template +datastream& operator << ( datastream& ds, const std::vector& v ) { ds << unsigned_int( v.size() ); ds.write( v.data(), v.size() ); return ds; @@ -687,15 +716,14 @@ DataStream& operator << ( DataStream& ds, const std::vector& v ) { /** * Serialize a vector * - * @brief Serialize a vector * @param ds - The stream to write * @param v - The value to serialize - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam T - Type of the object contained in the vector - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream */ -template -DataStream& operator << ( DataStream& ds, const std::vector& v ) { +template +datastream& operator << ( datastream& ds, const std::vector& v ) { ds << unsigned_int( v.size() ); for( const auto& i : v ) ds << i; @@ -705,14 +733,13 @@ DataStream& operator << ( DataStream& ds, const std::vector& v ) { /** * Deserialize a vector of char * - * @brief Deserialize a vector of char * @param ds - The stream to read * @param v - The destination for deserialized value - * @tparam DataStream - Type of datastream - * @return DataStream& - Reference to the datastream + * @tparam Stream - Type of datastream buffer + * @return datastream& - Reference to the datastream */ -template -DataStream& operator >> ( DataStream& ds, std::vector& v ) { +template +datastream& operator >> ( datastream& ds, std::vector& v ) { unsigned_int s; ds >> s; v.resize( s.value ); @@ -723,15 +750,14 @@ DataStream& operator >> ( DataStream& ds, std::vector& v ) { /** * Deserialize a vector * - * @brief Deserialize a vector * @param ds - The stream to read * @param v - The destination for deserialized value - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam T - Type of the object contained in the vector - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream */ -template -DataStream& operator >> ( DataStream& ds, std::vector& v ) { +template +datastream& operator >> ( datastream& ds, std::vector& v ) { unsigned_int s; ds >> s; v.resize(s.value); @@ -743,15 +769,14 @@ DataStream& operator >> ( DataStream& ds, std::vector& v ) { /** * Serialize a set * - * @brief Serialize a set * @param ds - The stream to write * @param s - The value to serialize - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam T - Type of the object contained in the set - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream */ -template -DataStream& operator << ( DataStream& ds, const std::set& s ) { +template +datastream& operator << ( datastream& ds, const std::set& s ) { ds << unsigned_int( s.size() ); for( const auto& i : s ) { ds << i; @@ -763,15 +788,14 @@ DataStream& operator << ( DataStream& ds, const std::set& s ) { /** * Deserialize a set * - * @brief Deserialize a set * @param ds - The stream to read * @param s - The destination for deserialized value - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam T - Type of the object contained in the set - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream */ -template -DataStream& operator >> ( DataStream& ds, std::set& s ) { +template +datastream& operator >> ( datastream& ds, std::set& s ) { s.clear(); unsigned_int sz; ds >> sz; @@ -786,16 +810,15 @@ DataStream& operator >> ( DataStream& ds, std::set& s ) { /** * Serialize a map * - * @brief Serialize a map * @param ds - The stream to write * @param m - The value to serialize - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam K - Type of the key contained in the map * @tparam V - Type of the value contained in the map - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream */ -template -DataStream& operator << ( DataStream& ds, const std::map& m ) { +template +datastream& operator << ( datastream& ds, const std::map& m ) { ds << unsigned_int( m.size() ); for( const auto& i : m ) { ds << i.first << i.second; @@ -806,16 +829,15 @@ DataStream& operator << ( DataStream& ds, const std::map& m ) { /** * Deserialize a map * - * @brief Deserialize a map * @param ds - The stream to read * @param m - The destination for deserialized value - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam K - Type of the key contained in the map * @tparam V - Type of the value contained in the map - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream */ -template -DataStream& operator >> ( DataStream& ds, std::map& m ) { +template +datastream& operator >> ( datastream& ds, std::map& m ) { m.clear(); unsigned_int s; ds >> s; @@ -830,15 +852,14 @@ DataStream& operator >> ( DataStream& ds, std::map& m ) { /** * Serialize a tuple * - * @brief Serialize a tuple * @param ds - The stream to write * @param t - The value to serialize - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam Args - Type of the objects contained in the tuple - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream */ -template -DataStream& operator<<( DataStream& ds, const std::tuple& t ) { +template +datastream& operator<<( datastream& ds, const std::tuple& t ) { boost::fusion::for_each( t, [&]( const auto& i ) { ds << i; }); @@ -848,15 +869,14 @@ DataStream& operator<<( DataStream& ds, const std::tuple& t ) { /** * Deserialize a tuple * - * @brief Deserialize a tuple * @param ds - The stream to read * @param t - The destination for deserialized value - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam Args - Type of the objects contained in the tuple - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream */ -template -DataStream& operator>>( DataStream& ds, std::tuple& t ) { +template +datastream& operator>>( datastream& ds, std::tuple& t ) { boost::fusion::for_each( t, [&]( auto& i ) { ds >> i; }); @@ -866,14 +886,13 @@ DataStream& operator>>( DataStream& ds, std::tuple& t ) { /** * Serialize a class * - * @brief Serialize a class * @param ds - The stream to write * @param v - The value to serialize * @tparam DataStream - Type of datastream * @tparam T - Type of class * @return DataStream& - Reference to the datastream */ -template::value>* = nullptr> +template::value && _datastream_detail::is_datastream::value>* = nullptr> DataStream& operator<<( DataStream& ds, const T& v ) { boost::pfr::for_each_field(v, [&](const auto& field) { ds << field; @@ -884,14 +903,13 @@ DataStream& operator<<( DataStream& ds, const T& v ) { /** * Deserialize a class * - * @brief Deserialize a class * @param ds - The stream to read * @param v - The destination for deserialized value * @tparam DataStream - Type of datastream * @tparam T - Type of class * @return DataStream& - Reference to the datastream */ -template::value>* = nullptr> +template::value && _datastream_detail::is_datastream::value>* = nullptr> DataStream& operator>>( DataStream& ds, T& v ) { boost::pfr::for_each_field(v, [&](auto& field) { ds >> field; @@ -902,15 +920,14 @@ DataStream& operator>>( DataStream& ds, T& v ) { /** * Serialize a primitive type * - * @brief Serialize a primitive type * @param ds - The stream to write * @param v - The value to serialize - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam T - Type of the primitive type - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream */ -template()>* = nullptr> -DataStream& operator<<( DataStream& ds, const T& v ) { +template()>* = nullptr> +datastream& operator<<( datastream& ds, const T& v ) { ds.write( (const char*)&v, sizeof(T) ); return ds; } @@ -918,15 +935,14 @@ DataStream& operator<<( DataStream& ds, const T& v ) { /** * Deserialize a primitive type * - * @brief Deserialize a primitive type * @param ds - The stream to read * @param v - The destination for deserialized value - * @tparam DataStream - Type of datastream + * @tparam Stream - Type of datastream buffer * @tparam T - Type of the primitive type - * @return DataStream& - Reference to the datastream + * @return datastream& - Reference to the datastream */ -template()>* = nullptr> -DataStream& operator>>( DataStream& ds, T& v ) { +template()>* = nullptr> +datastream& operator>>( datastream& ds, T& v ) { ds.read( (char*)&v, sizeof(T) ); return ds; } @@ -935,7 +951,6 @@ DataStream& operator>>( DataStream& ds, T& v ) { * Unpack data inside a fixed size buffer as T * * @ingroup datastream - * @brief Unpack data inside a fixed size buffer as T * @tparam T - Type of the unpacked data * @param buffer - Pointer to the buffer * @param len - Length of the buffer @@ -949,11 +964,26 @@ T unpack( const char* buffer, size_t len ) { return result; } +/** + * Unpack data inside a fixed size buffer as T + * + * @ingroup datastream + * @tparam T - Type of the unpacked data + * @param res - Variable to fill with the unpacking + * @param buffer - Pointer to the buffer + * @param len - Length of the buffer + * @return T - The unpacked data + */ +template +void unpack( T& res, const char* buffer, size_t len ) { + datastream ds(buffer,len); + ds >> res; +} + /** * Unpack data inside a variable size buffer as T * * @ingroup datastream - * @brief Unpack data inside a variable size buffer as T * @tparam T - Type of the unpacked data * @param bytes - Buffer * @return T - The unpacked data @@ -967,7 +997,6 @@ T unpack( const std::vector& bytes ) { * Get the size of the packed data * * @ingroup datastream - * @brief Get the size of the packed data * @tparam T - Type of the data to be packed * @param value - Data to be packed * @return size_t - Size of the packed data @@ -983,7 +1012,6 @@ size_t pack_size( const T& value ) { * Get packed data * * @ingroup datastream - * @brief Get packed data * @tparam T - Type of the data to be packed * @param value - Data to be packed * @return bytes - The packed data diff --git a/libraries/eosiolib/core/eosio/key_utils.hpp b/libraries/eosiolib/core/eosio/key_utils.hpp new file mode 100644 index 0000000000..c6836cf2b8 --- /dev/null +++ b/libraries/eosiolib/core/eosio/key_utils.hpp @@ -0,0 +1,343 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "datastream.hpp" +#include "reflect.hpp" + +namespace eosio { + + namespace detail { + template + constexpr bool has_bitwise_serialization() { + if constexpr (std::is_arithmetic_v) { + return true; + } else if constexpr (std::is_enum_v) { + static_assert(!std::is_convertible_v>, "Serializing unscoped enum"); + return true; + } else { + return false; + } + } + + template