From 01e65f481cf10226b8c0aaa8f163157810b1b53a Mon Sep 17 00:00:00 2001 From: PeosDev Date: Mon, 4 Mar 2019 18:24:29 +0200 Subject: [PATCH 01/99] Configurable stack size --- tools/include/compiler_options.hpp.in | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/include/compiler_options.hpp.in b/tools/include/compiler_options.hpp.in index 9f2650b214..975697b8d4 100644 --- a/tools/include/compiler_options.hpp.in +++ b/tools/include/compiler_options.hpp.in @@ -63,6 +63,11 @@ static cl::opt fno_stack_first_opt( "fno-stack-first", cl::desc("Don't set the stack first in memory"), cl::cat(LD_CAT)); +static cl::opt stack_size_opt( + "stack-size", + cl::desc("Specifies the maximum stack size for the contract. Defaults to ${EOSIO_STACK_SIZE} bytes."), + cl::init(${EOSIO_STACK_SIZE}), + cl::cat(LD_CAT)); static cl::opt fno_post_pass_opt( "fno-post-pass", cl::desc("Don't run post processing pass"), @@ -413,7 +418,6 @@ static void GetLdDefaults(std::vector& ldopts) { if (!fnative_opt) { ldopts.emplace_back("--gc-sections"); ldopts.emplace_back("--strip-all"); - ldopts.emplace_back("-zstack-size="+std::string("${EOSIO_STACK_SIZE}")); ldopts.emplace_back("--merge-data-segments"); if (fquery_opt || fquery_server_opt || fquery_client_opt) { ldopts.emplace_back("--export-table"); @@ -715,10 +719,12 @@ static Options CreateOptions(bool add_defaults=true) { else { ldopts.emplace_back("--lto-O3"); } + ldopts.emplace_back("-zstack-size=" + std::to_string(stack_size_opt)); #else if (fno_stack_first_opt) { ldopts.emplace_back("-fno-stack-first"); } + ldopts.emplace_back("-stack-size=" + std::to_string(stack_size_opt)); if (fno_lto_opt) { ldopts.emplace_back("-fno-lto-opt"); } From 39ccc55baba74f5d7dc2acf10820b8d7b51842ca Mon Sep 17 00:00:00 2001 From: PeosDev Date: Tue, 12 Mar 2019 15:27:07 +0200 Subject: [PATCH 02/99] Update .md files concerning the stack-size parameter --- docs/tools/eosio-cpp.md | 1 + docs/tools/eosio-ld.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/tools/eosio-cpp.md b/docs/tools/eosio-cpp.md index 6ebab10a56..2cde5b53df 100644 --- a/docs/tools/eosio-cpp.md +++ b/docs/tools/eosio-cpp.md @@ -54,6 +54,7 @@ compiler options: -fno-lto - Disable LTO -fno-post-pass - Don't run post processing pass -fno-stack-first - Don't set the stack first in memory + -stack-size - Specifies the maximum stack size for the contract -fstack-protector - Enable stack protectors for functions potentially vulnerable to stack smashing -fstack-protector-all - Force the usage of stack protectors for all functions -fstack-protector-strong - Use a strong heuristic to apply stack protectors to functions diff --git a/docs/tools/eosio-ld.md b/docs/tools/eosio-ld.md index 41c29e2532..87f368e834 100644 --- a/docs/tools/eosio-ld.md +++ b/docs/tools/eosio-ld.md @@ -20,6 +20,7 @@ ld options: -fno-lto - Disable LTO -fno-post-pass - Don't run post processing pass -fno-stack-first - Don't set the stack first in memory + -stack-size - Specifies the maximum stack size for the contract -fuse-main - Use main as entry -l= - Root name of library to link -lto-opt= - LTO Optimization level (O0-O3) From 618c23a7e2b83c188bfa06723c607577836ef1b6 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Sat, 23 Mar 2019 15:55:00 +0900 Subject: [PATCH 03/99] Fix typo and error in action_wrapper documentation --- libraries/eosiolib/contracts/eosio/action.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/eosiolib/contracts/eosio/action.hpp b/libraries/eosiolib/contracts/eosio/action.hpp index 17b66a4025..0a7348fc0e 100644 --- a/libraries/eosiolib/contracts/eosio/action.hpp +++ b/libraries/eosiolib/contracts/eosio/action.hpp @@ -425,7 +425,7 @@ namespace eosio { * Example: * @code * // defined by contract writer of the actions - * using transfer act = action_wrapper<"transfer"_n, &token::transfer>;( *this, transfer, {st.issuer,N(active)}, {st.issuer, to, quantity, memo} ); + * using transfer_act = action_wrapper<"transfer"_n, &token::transfer>; * // usage by different contract writer * transfer_act{"eosio.token"_n, {st.issuer, "active"_n}}.send(st.issuer, to, quantity, memo); * // or From a70cf5f1b8c9aea3f0c41f791f58a90f0ca865f7 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Sat, 23 Mar 2019 16:17:45 +0900 Subject: [PATCH 04/99] Allow to instantiate action_wrapper without permission_level --- libraries/eosiolib/contracts/eosio/action.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/eosiolib/contracts/eosio/action.hpp b/libraries/eosiolib/contracts/eosio/action.hpp index 17b66a4025..dd5733dc7e 100644 --- a/libraries/eosiolib/contracts/eosio/action.hpp +++ b/libraries/eosiolib/contracts/eosio/action.hpp @@ -451,6 +451,10 @@ namespace eosio { constexpr action_wrapper(Code&& code, const eosio::permission_level& perm) : code_name(std::forward(code)), permissions({1, perm}) {} + template + constexpr action_wrapper(Code&& code) + : code_name(std::forward(code)) {} + static constexpr eosio::name action_name = eosio::name(Name); eosio::name code_name; std::vector permissions; From d472125c27bcd9fd673043c679af3d49e7f33e2b Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Mon, 25 Mar 2019 00:20:31 +0900 Subject: [PATCH 05/99] Include source path for header directory by default --- tools/cc/eosio-cc.cpp.in | 5 +++++ tools/cc/eosio-cpp.cpp.in | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/cc/eosio-cc.cpp.in b/tools/cc/eosio-cc.cpp.in index 1d3860cf13..9674a4093e 100644 --- a/tools/cc/eosio-cc.cpp.in +++ b/tools/cc/eosio-cc.cpp.in @@ -45,6 +45,11 @@ int main(int argc, const char **argv) { std::string tmp_file = std::string(res.c_str())+"/"+input+"-tmp.c"; std::string output; + auto src = SmallString<64>(input); + llvm::sys::path::remove_filename(src); + std::string source_path = src.str().empty() ? "." : src.str(); + new_opts.insert(new_opts.begin(), "-I" + source_path); + output = tmp_file+".o"; new_opts.insert(new_opts.begin(), input); diff --git a/tools/cc/eosio-cpp.cpp.in b/tools/cc/eosio-cpp.cpp.in index c2f69dbe9f..d8a1cdd288 100644 --- a/tools/cc/eosio-cpp.cpp.in +++ b/tools/cc/eosio-cpp.cpp.in @@ -195,9 +195,14 @@ int main(int argc, const char **argv) { llvm::sys::path::system_temp_directory(true, res); std::string tmp_file = std::string(res.c_str())+"/"+llvm::sys::path::filename(input).str(); std::string output; - + generate(opts.comp_options, input, opts.abigen_contract, opts.abigen_resources, opts.abigen); + auto src = SmallString<64>(input); + llvm::sys::path::remove_filename(src); + std::string source_path = src.str().empty() ? "." : src.str(); + new_opts.insert(new_opts.begin(), "-I" + source_path); + if (llvm::sys::fs::exists(tmp_file)) { input = tmp_file; } From 3e10892d15f8b2f548a8529f0ffc554325f4aafb Mon Sep 17 00:00:00 2001 From: smlu Date: Thu, 28 Mar 2019 14:08:49 +0100 Subject: [PATCH 06/99] Fix duplicated symbol error --- libraries/eosiolib/contracts/eosio/transaction.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/eosiolib/contracts/eosio/transaction.hpp b/libraries/eosiolib/contracts/eosio/transaction.hpp index 8f4875ac3c..b1306ddfca 100644 --- a/libraries/eosiolib/contracts/eosio/transaction.hpp +++ b/libraries/eosiolib/contracts/eosio/transaction.hpp @@ -172,7 +172,7 @@ namespace eosio { * @param size - The size of the packed transaction, required for persistence. * @param replace - If true, will replace an existing transaction. */ - void send_deferred(const uint128_t& sender_id, name payer, const char* serialized_transaction, size_t size, bool replace = false) { + inline void send_deferred(const uint128_t& sender_id, name payer, const char* serialized_transaction, size_t size, bool replace = false) { internal_use_do_not_use::send_deferred(sender_id, payer.value, serialized_transaction, size, replace); } /** From af48d9480647be06624289c534522d6119c103d7 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Fri, 12 Apr 2019 17:04:05 -0400 Subject: [PATCH 07/99] fix for on_notify wild card --- eosio_llvm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eosio_llvm b/eosio_llvm index a41b8e7653..3c026f8b78 160000 --- a/eosio_llvm +++ b/eosio_llvm @@ -1 +1 @@ -Subproject commit a41b8e7653258a4f1a5911ef28c95672efce051e +Subproject commit 3c026f8b7813ad693749d3551353a554c5ca33e7 From 24d47c941be385d47b22798a2b10e46478ed455f Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Fri, 12 Apr 2019 17:32:17 -0400 Subject: [PATCH 08/99] various small fixes --- libraries/eosiolib/contracts/eosio/action.hpp | 4 +++- libraries/eosiolib/contracts/eosio/permission.hpp | 4 ++-- libraries/eosiolib/contracts/eosio/transaction.hpp | 2 +- tools/include/compiler_options.hpp.in | 3 ++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/libraries/eosiolib/contracts/eosio/action.hpp b/libraries/eosiolib/contracts/eosio/action.hpp index 17b66a4025..283c3039af 100644 --- a/libraries/eosiolib/contracts/eosio/action.hpp +++ b/libraries/eosiolib/contracts/eosio/action.hpp @@ -412,7 +412,9 @@ namespace eosio { template constexpr bool type_check() { static_assert(sizeof...(Ts) == std::tuple_size>::value); - return check_types::value; + if constexpr (sizeof...(Ts) != 0) + return check_types::value; + return true; } /// @endcond diff --git a/libraries/eosiolib/contracts/eosio/permission.hpp b/libraries/eosiolib/contracts/eosio/permission.hpp index 5b2aac1bcc..8660b19d91 100644 --- a/libraries/eosiolib/contracts/eosio/permission.hpp +++ b/libraries/eosiolib/contracts/eosio/permission.hpp @@ -49,7 +49,7 @@ namespace eosio { check_transaction_authorization( const char* trx_data, uint32_t trx_size, const char* pubkeys_data, uint32_t pubkeys_size, const char* perms_data, uint32_t perms_size ) { - internal_use_do_not_use::check_transaction_authorization( trx_data, trx_size, pubkeys_data, pubkeys_size, perms_data, perms_size ); + return internal_use_do_not_use::check_transaction_authorization( trx_data, trx_size, pubkeys_data, pubkeys_size, perms_data, perms_size ); } /** @@ -74,7 +74,7 @@ namespace eosio { microseconds delay ) { int64_t delay_us = delay.count(); check(delay_us >= 0, "negative delay is not allowed"); - internal_use_do_not_use::check_permission_authorization( account.value, permission.value, pubkeys_data, pubkeys_size, perms_data, perms_size, static_cast(delay_us) ); + return internal_use_do_not_use::check_permission_authorization( account.value, permission.value, pubkeys_data, pubkeys_size, perms_data, perms_size, static_cast(delay_us) ); } diff --git a/libraries/eosiolib/contracts/eosio/transaction.hpp b/libraries/eosiolib/contracts/eosio/transaction.hpp index 8f4875ac3c..b1306ddfca 100644 --- a/libraries/eosiolib/contracts/eosio/transaction.hpp +++ b/libraries/eosiolib/contracts/eosio/transaction.hpp @@ -172,7 +172,7 @@ namespace eosio { * @param size - The size of the packed transaction, required for persistence. * @param replace - If true, will replace an existing transaction. */ - void send_deferred(const uint128_t& sender_id, name payer, const char* serialized_transaction, size_t size, bool replace = false) { + inline void send_deferred(const uint128_t& sender_id, name payer, const char* serialized_transaction, size_t size, bool replace = false) { internal_use_do_not_use::send_deferred(sender_id, payer.value, serialized_transaction, size, replace); } /** diff --git a/tools/include/compiler_options.hpp.in b/tools/include/compiler_options.hpp.in index c26558f8a6..d65c88c9f8 100644 --- a/tools/include/compiler_options.hpp.in +++ b/tools/include/compiler_options.hpp.in @@ -544,6 +544,7 @@ static Options CreateOptions(bool add_defaults=true) { #endif #endif #ifndef ONLY_LD + copts.emplace_back("-I./"); if (!sysroot_opt.empty()) { copts.emplace_back("--sysroot="+sysroot_opt); copts.emplace_back("-I"+sysroot_opt+"/include/libcxx"); @@ -555,7 +556,7 @@ static Options CreateOptions(bool add_defaults=true) { copts.emplace_back("-I"+sysroot_opt+"/include/eosiolib/native"); } #ifndef CPP_COMP - copts.emplace_back("-I"+sysroot_opt+"/include/eosiolib/capi"); + copts.emplace_back("-I"+sysroot_opt+"/include/eosiolib/capi"); #endif copts.emplace_back("-I"+sysroot_opt+"/include/eosiolib/core"); copts.emplace_back("-I"+sysroot_opt+"/include/eosiolib/contracts"); From 77bc8a1e07207f5f6f49b176aae50b450baeb1b2 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Tue, 16 Apr 2019 11:24:27 -0400 Subject: [PATCH 09/99] Update TestsExternalProject.txt --- modules/TestsExternalProject.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/TestsExternalProject.txt b/modules/TestsExternalProject.txt index 4a793d0ce0..b82ed22b60 100644 --- a/modules/TestsExternalProject.txt +++ b/modules/TestsExternalProject.txt @@ -34,7 +34,7 @@ if (EOSIO_RUN_INTEGRATION_TESTS) EosioIntegrationTests SOURCE_DIR "${CMAKE_SOURCE_DIR}/tests/integration" BINARY_DIR "${CMAKE_BINARY_DIR}/tests/integration" - CMAKE_ARGS -DCMAKE_BUILD_TYPE=${TEST_BUILD_TYPE} -DCMAKE_FRAMEWORK_PATH=${TEST_FRAMEWORK_PATH} -DCMAKE_MODULE_PATH=${TEST_MODULE_PATH} -DEOSIO_ROOT=${EOSIO_ROOT} -DLLVM_DIR=${LLVM_DIR} + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${TEST_BUILD_TYPE} -DCMAKE_FRAMEWORK_PATH=${TEST_FRAMEWORK_PATH} -DCMAKE_MODULE_PATH=${TEST_MODULE_PATH} -DEOSIO_ROOT=${EOSIO_ROOT} -DLLVM_DIR=${LLVM_DIR} -DBOOST_ROOT=${BOOST_ROOT} UPDATE_COMMAND "" PATCH_COMMAND "" TEST_COMMAND "" From 478f96fb42f79d6c68f6f043d57da21e2c502fbb Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Tue, 16 Apr 2019 12:05:45 -0400 Subject: [PATCH 10/99] Added Buildkite dependencies for integration tests --- dependencies | 2 ++ tests/CMakeLists.txt | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 dependencies diff --git a/dependencies b/dependencies new file mode 100644 index 0000000000..2c15f5b2f3 --- /dev/null +++ b/dependencies @@ -0,0 +1,2 @@ +# dependencies to pull for cdt integration tests, by branch, tag, or commit hash +eosio=release/1.7.x \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6108af4e2a..70a8655c14 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,17 +1,32 @@ +@@ -1,18 +1,33 @@ add_test( asset_tests ${CMAKE_BINARY_DIR}/tests/unit/asset_tests ) +set_property(TEST asset_tests PROPERTY LABELS unit_tests) add_test( binary_extension_tests ${CMAKE_BINARY_DIR}/tests/unit/binary_extension_tests ) +set_property(TEST binary_extension_tests PROPERTY LABELS unit_tests) add_test( crypto_tests ${CMAKE_BINARY_DIR}/tests/unit/crypto_tests ) +set_property(TEST crypto_tests PROPERTY LABELS unit_tests) add_test( datastream_tests ${CMAKE_BINARY_DIR}/tests/unit/datastream_tests ) +set_property(TEST datastream_tests PROPERTY LABELS unit_tests) add_test( fixed_bytes_tests ${CMAKE_BINARY_DIR}/tests/unit/fixed_bytes_tests ) +set_property(TEST fixed_bytes_tests PROPERTY LABELS unit_tests) add_test( name_tests ${CMAKE_BINARY_DIR}/tests/unit/name_tests ) +set_property(TEST name_tests PROPERTY LABELS unit_tests) add_test( rope_tests ${CMAKE_BINARY_DIR}/tests/unit/rope_tests ) +set_property(TEST rope_tests PROPERTY LABELS unit_tests) add_test( print_tests ${CMAKE_BINARY_DIR}/tests/unit/print_tests ) +set_property(TEST print_tests PROPERTY LABELS unit_tests) add_test( serialize_tests ${CMAKE_BINARY_DIR}/tests/unit/serialize_tests ) +set_property(TEST serialize_tests PROPERTY LABELS unit_tests) add_test( symbol_tests ${CMAKE_BINARY_DIR}/tests/unit/symbol_tests ) +set_property(TEST symbol_tests PROPERTY LABELS unit_tests) add_test( system_tests ${CMAKE_BINARY_DIR}/tests/unit/system_tests ) +set_property(TEST system_tests PROPERTY LABELS unit_tests) add_test( time_tests ${CMAKE_BINARY_DIR}/tests/unit/time_tests ) +set_property(TEST time_tests PROPERTY LABELS unit_tests) add_test( varint_tests ${CMAKE_BINARY_DIR}/tests/unit/varint_tests ) +set_property(TEST varint_tests PROPERTY LABELS unit_tests) if (eosio_FOUND AND EOSIO_RUN_INTEGRATION_TESTS) add_test(integration_tests ${CMAKE_BINARY_DIR}/tests/integration/integration_tests) -endif() + set_property(TEST integration_tests PROPERTY LABELS integration_tests) +endif() \ No newline at end of file From 66cd2afcac9700fc8ad3a09b6887198fbbb01969 Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Tue, 16 Apr 2019 20:38:27 -0400 Subject: [PATCH 11/99] dependencies file points to zach-1.7-centos-lib pending merge into release/1.7.x --- dependencies | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies b/dependencies index 2c15f5b2f3..7f9178bce8 100644 --- a/dependencies +++ b/dependencies @@ -1,2 +1,2 @@ # dependencies to pull for cdt integration tests, by branch, tag, or commit hash -eosio=release/1.7.x \ No newline at end of file +eosio=zach-1.7-centos-lib # change back to "release/1.7.x" when eos pull request 7140 is merged: https://github.com/EOSIO/eos/pull/7140 \ No newline at end of file From 7a348e236bf632795194409f512083df4e7fad19 Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Tue, 16 Apr 2019 20:42:56 -0400 Subject: [PATCH 12/99] Copy-pasta error --- tests/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 70a8655c14..18c1759423 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,3 @@ -@@ -1,18 +1,33 @@ add_test( asset_tests ${CMAKE_BINARY_DIR}/tests/unit/asset_tests ) set_property(TEST asset_tests PROPERTY LABELS unit_tests) add_test( binary_extension_tests ${CMAKE_BINARY_DIR}/tests/unit/binary_extension_tests ) From 35a17ef930e475ed92ba31b40344b9995114e6d4 Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Wed, 17 Apr 2019 19:57:18 -0400 Subject: [PATCH 13/99] EOSIO PR7140 has been merged, reverting dependencies file to release/1.7.x --- dependencies | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies b/dependencies index 7f9178bce8..2c15f5b2f3 100644 --- a/dependencies +++ b/dependencies @@ -1,2 +1,2 @@ # dependencies to pull for cdt integration tests, by branch, tag, or commit hash -eosio=zach-1.7-centos-lib # change back to "release/1.7.x" when eos pull request 7140 is merged: https://github.com/EOSIO/eos/pull/7140 \ No newline at end of file +eosio=release/1.7.x \ No newline at end of file From 59ce33c0b775fed0dad974b784260cdca9966be4 Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Tue, 30 Apr 2019 14:35:05 -0400 Subject: [PATCH 14/99] Created universal pipeline configuration file --- dependencies | 2 -- pipeline.jsonc | 10 ++++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) delete mode 100644 dependencies create mode 100644 pipeline.jsonc diff --git a/dependencies b/dependencies deleted file mode 100644 index 2c15f5b2f3..0000000000 --- a/dependencies +++ /dev/null @@ -1,2 +0,0 @@ -# dependencies to pull for cdt integration tests, by branch, tag, or commit hash -eosio=release/1.7.x \ No newline at end of file diff --git a/pipeline.jsonc b/pipeline.jsonc new file mode 100644 index 0000000000..508acc722c --- /dev/null +++ b/pipeline.jsonc @@ -0,0 +1,10 @@ +{ + "eosio-dot-cdt": + { + "pipeline-branch": "master", + "dependencies": // dependencies to pull for cdt integration tests, by branch, tag, or commit hash + { + "eosio": "release/1.7.x" + } + } +} \ No newline at end of file From f394c58b6b40d55e9f981e6a5ac899be4b8f58aa Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Wed, 8 May 2019 14:35:43 -0400 Subject: [PATCH 15/99] Run CDT pipeline on my test branch --- pipeline.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipeline.jsonc b/pipeline.jsonc index 508acc722c..c6c367a339 100644 --- a/pipeline.jsonc +++ b/pipeline.jsonc @@ -1,7 +1,7 @@ { "eosio-dot-cdt": { - "pipeline-branch": "master", + "pipeline-branch": "zach-cdt-fixes", "dependencies": // dependencies to pull for cdt integration tests, by branch, tag, or commit hash { "eosio": "release/1.7.x" From c02876849c1100e7f8a636af061eae7f955394ad Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Fri, 10 May 2019 22:17:48 -0400 Subject: [PATCH 16/99] Pipeline has been deployed, point Buildkite back to master --- pipeline.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipeline.jsonc b/pipeline.jsonc index c6c367a339..508acc722c 100644 --- a/pipeline.jsonc +++ b/pipeline.jsonc @@ -1,7 +1,7 @@ { "eosio-dot-cdt": { - "pipeline-branch": "zach-cdt-fixes", + "pipeline-branch": "master", "dependencies": // dependencies to pull for cdt integration tests, by branch, tag, or commit hash { "eosio": "release/1.7.x" From cf73c5a8140033a49e0d233c76728781626830f3 Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Thu, 13 Jun 2019 14:25:50 -0400 Subject: [PATCH 17/99] Add support for sccache and ccache to CMake --- libraries/CMakeLists.txt | 14 ++++++++++++++ tests/CMakeLists.txt | 14 ++++++++++++++ tools/CMakeLists.txt | 17 ++++++++++++----- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index b49105f81f..d53ed094f5 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -1,5 +1,19 @@ project(eosio_libraries) +find_program(SCCACHE_FOUND sccache) +if (SCCACHE_FOUND) + message(STATUS "Using sccache") + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE sccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK sccache) +else() + find_program(CCACHE_FOUND ccache) + if (CCACHE_FOUND) + message(STATUS "Using ccache") + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) + endif() +endif() + list(APPEND CMAKE_MODULE_PATH ${EOSIO_CDT_BIN}) include(EosioCDTMacros) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 18c1759423..2a08096078 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,17 @@ +find_program(SCCACHE_FOUND sccache) +if (SCCACHE_FOUND) + message(STATUS "Using sccache") + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE sccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK sccache) +else() + find_program(CCACHE_FOUND ccache) + if (CCACHE_FOUND) + message(STATUS "Using ccache") + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) + endif() +endif() + add_test( asset_tests ${CMAKE_BINARY_DIR}/tests/unit/asset_tests ) set_property(TEST asset_tests PROPERTY LABELS unit_tests) add_test( binary_extension_tests ${CMAKE_BINARY_DIR}/tests/unit/binary_extension_tests ) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 2e02a3a990..df4516e494 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -3,11 +3,18 @@ find_package(LLVM REQUIRED CONFIG) message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") -find_program(CCACHE_FOUND ccache) -if (CCACHE_FOUND) - message(STATUS "Using ccache") - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) +find_program(SCCACHE_FOUND sccache) +if (SCCACHE_FOUND) + message(STATUS "Using sccache") + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE sccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK sccache) +else() + find_program(CCACHE_FOUND ccache) + if (CCACHE_FOUND) + message(STATUS "Using ccache") + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) + endif() endif() include_directories(${LLVM_INCLUDE_DIRS}) From 260904a857b4475583d5e76ba8759c46704322b7 Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Mon, 17 Jun 2019 15:28:32 -0400 Subject: [PATCH 18/99] Update eosio_llvm to support sccache --- eosio_llvm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eosio_llvm b/eosio_llvm index a41b8e7653..8bd2fa5bb1 160000 --- a/eosio_llvm +++ b/eosio_llvm @@ -1 +1 @@ -Subproject commit a41b8e7653258a4f1a5911ef28c95672efce051e +Subproject commit 8bd2fa5bb12b59551540a5676e02b15d1ff5b580 From 7d913bc341fa651ef7e2e8fab1920b4080d0f135 Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Wed, 19 Jun 2019 15:47:40 -0400 Subject: [PATCH 19/99] Remove support for sccache from /libraries because eosio-cc is not a supported compiler --- libraries/CMakeLists.txt | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index d53ed094f5..a84135a67f 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -1,17 +1,10 @@ project(eosio_libraries) -find_program(SCCACHE_FOUND sccache) -if (SCCACHE_FOUND) - message(STATUS "Using sccache") - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE sccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK sccache) -else() - find_program(CCACHE_FOUND ccache) - if (CCACHE_FOUND) - message(STATUS "Using ccache") - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) - endif() +find_program(CCACHE_FOUND ccache) +if (CCACHE_FOUND) + message(STATUS "Using ccache") + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) endif() list(APPEND CMAKE_MODULE_PATH ${EOSIO_CDT_BIN}) From b99cc694e044add9ac0ad132d434e1b20d833ae6 Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Wed, 19 Jun 2019 18:14:14 -0400 Subject: [PATCH 20/99] Removed "RULE_LAUNCH_LINK ccache" as ccache does not accelerate linking --- CMakeLists.txt | 1 - libraries/CMakeLists.txt | 1 - tests/CMakeLists.txt | 1 - tools/CMakeLists.txt | 1 - 4 files changed, 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5346831d17..be1bd50e3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,6 @@ else() if (CCACHE_FOUND) message(STATUS "Using ccache") set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) endif() endif() diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index a84135a67f..f4e6b33b13 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -4,7 +4,6 @@ find_program(CCACHE_FOUND ccache) if (CCACHE_FOUND) message(STATUS "Using ccache") set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) endif() list(APPEND CMAKE_MODULE_PATH ${EOSIO_CDT_BIN}) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2a08096078..6f443ad908 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,7 +8,6 @@ else() if (CCACHE_FOUND) message(STATUS "Using ccache") set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) endif() endif() diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index df4516e494..52fb033186 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -13,7 +13,6 @@ else() if (CCACHE_FOUND) message(STATUS "Using ccache") set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) endif() endif() From d70cfd0ac707ad3cb4f82996f5c9ca632c6e98ea Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Wed, 19 Jun 2019 18:58:22 -0400 Subject: [PATCH 21/99] Removed "RULE_LAUNCH_LINK sccache" as sccache does not support "ar" or linking in general --- CMakeLists.txt | 1 - tests/CMakeLists.txt | 1 - tools/CMakeLists.txt | 1 - 3 files changed, 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index be1bd50e3b..fa67f18725 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,6 @@ find_program(SCCACHE_FOUND sccache) if (SCCACHE_FOUND) message(STATUS "Using sccache") set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE sccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK sccache) else() find_program(CCACHE_FOUND ccache) if (CCACHE_FOUND) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6f443ad908..ab8a503bee 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,7 +2,6 @@ find_program(SCCACHE_FOUND sccache) if (SCCACHE_FOUND) message(STATUS "Using sccache") set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE sccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK sccache) else() find_program(CCACHE_FOUND ccache) if (CCACHE_FOUND) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 52fb033186..a563e73dd5 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -7,7 +7,6 @@ find_program(SCCACHE_FOUND sccache) if (SCCACHE_FOUND) message(STATUS "Using sccache") set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE sccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK sccache) else() find_program(CCACHE_FOUND ccache) if (CCACHE_FOUND) From cd67425ebdf06c5315eb39dd9f1079822aaa7166 Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Wed, 19 Jun 2019 19:00:23 -0400 Subject: [PATCH 22/99] Update eosio_llvm to remove linker caching --- eosio_llvm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eosio_llvm b/eosio_llvm index 8bd2fa5bb1..ce49c87984 160000 --- a/eosio_llvm +++ b/eosio_llvm @@ -1 +1 @@ -Subproject commit 8bd2fa5bb12b59551540a5676e02b15d1ff5b580 +Subproject commit ce49c87984047fe2ecdcfa1c6ce5c53d9ea6e7c1 From 301691c94a42df5b2be1ef412c19ad90ad9a8ce1 Mon Sep 17 00:00:00 2001 From: learnforpractice Date: Mon, 24 Jun 2019 20:35:31 +0800 Subject: [PATCH 23/99] Fix index_by example --- libraries/eosiolib/contracts/eosio/multi_index.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/eosiolib/contracts/eosio/multi_index.hpp b/libraries/eosiolib/contracts/eosio/multi_index.hpp index e20603cb38..5d3dac4e40 100644 --- a/libraries/eosiolib/contracts/eosio/multi_index.hpp +++ b/libraries/eosiolib/contracts/eosio/multi_index.hpp @@ -350,7 +350,7 @@ namespace _multi_index_detail { * uint64_t primary; * uint128_t secondary; * uint64_t primary_key() const { return primary; } - * uint64_t get_secondary() const { return secondary; } + * uint128_t get_secondary() const { return secondary; } * }; * public: * mycontract(name receiver, name code, datastream ds):contract(receiver, code, ds){} From f22e34d7ccca2c0bb5db054f23491bcccfb1dce7 Mon Sep 17 00:00:00 2001 From: swatanabe-b1 <52169356+swatanabe-b1@users.noreply.github.com> Date: Tue, 25 Jun 2019 12:05:05 -0400 Subject: [PATCH 24/99] Fix doc typo Same as #537 --- docs/guides/generator-attributes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/generator-attributes.md b/docs/guides/generator-attributes.md index ee690aaa46..7b0bdca566 100644 --- a/docs/guides/generator-attributes.md +++ b/docs/guides/generator-attributes.md @@ -55,12 +55,12 @@ This will mark this `class` as being an `EOSIO` contract, this allows for namesp #### [[eosio::on_notify("\::\")]] ``` [[eosio::on_notify("eosio.token::transfer")]] -void on_token_transfer(name from, name to, assert quantity, std::string memo) { +void on_token_transfer(name from, name to, asset quantity, std::string memo) { do something on transfer from eosio.token... } [[eosio::on_notify("*::transfer")]] -void on_any_transfer(name from, name to, assert quantity, std::string memo) { +void on_any_transfer(name from, name to, asset quantity, std::string memo) { do something on transfer from any account... } ``` From f42df0749de4fe226047f51e1afacdfffce305c6 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Thu, 27 Jun 2019 15:43:59 -0400 Subject: [PATCH 25/99] fixes to installation --- CMakeLists.txt | 11 +++++++---- install.sh | 2 +- modules/InstallCDT.cmake | 15 +++++++++------ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5346831d17..ac95a45e0f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ else() set(VERSION_FULL "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") endif() + if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) message(WARNING "CMAKE_INSTALL_PREFIX is set to default path of ${CMAKE_INSTALL_PREFIX}, resetting to ${CMAKE_INSTALL_PREFIX}/eosio.cdt") set(CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/eosio.cdt") @@ -34,6 +35,8 @@ elseif ("${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local") message(WARNING "CMAKE_INSTALL_PREFIX is explicitly set to /usr/local. This is not recommended.") endif() +set(CDT_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/eosio.cdt) + include(GNUInstallDirs) include(modules/ClangExternalProject.txt) @@ -47,13 +50,13 @@ configure_file(${CMAKE_SOURCE_DIR}/modules/eosio.cdt-config.cmake ${CMAKE_BINARY configure_file(${CMAKE_SOURCE_DIR}/modules/EosioCDTMacros.cmake.in ${CMAKE_BINARY_DIR}/lib/cmake/eosio.cdt/EosioCDTMacros.cmake @ONLY) configure_file(${CMAKE_SOURCE_DIR}/modules/EosioWasmToolchain.cmake.in ${CMAKE_BINARY_DIR}/lib/cmake/eosio.cdt/EosioWasmToolchain.cmake @ONLY) -set(CDT_ROOT_DIR ${CMAKE_INSTALL_PREFIX}) +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) install(FILES ${CMAKE_BINARY_DIR}/modules/eosio.cdt-config.cmake DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/eosio.cdt) 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/EosioWasmToolchain.cmake DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/eosio.cdt) -install(FILES ${CMAKE_BINARY_DIR}/modules/EosioCDTMacros.cmake DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/eosio.cdt) +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) @@ -65,10 +68,10 @@ include(modules/LibrariesExternalProject.txt) include(modules/InstallCDT.cmake) configure_file(${CMAKE_SOURCE_DIR}/imports/eosio.imports.in ${CMAKE_BINARY_DIR}/eosio.imports COPYONLY) -install(FILES ${CMAKE_BINARY_DIR}/eosio.imports DESTINATION ${CMAKE_INSTALL_PREFIX}) +install(FILES ${CMAKE_BINARY_DIR}/eosio.imports DESTINATION ${CDT_INSTALL_PREFIX}) configure_file(${CMAKE_SOURCE_DIR}/scripts/ricardeos/ricardeos.py ${CMAKE_BINARY_DIR}/scripts/ricardeos.py COPYONLY) -install(FILES ${CMAKE_BINARY_DIR}/scripts/ricardeos.py DESTINATION ${CMAKE_INSTALL_PREFIX}/scripts) +install(FILES ${CMAKE_BINARY_DIR}/scripts/ricardeos.py DESTINATION ${CDT_INSTALL_PREFIX}/scripts) # section for package construction set(VENDOR "block.one") diff --git a/install.sh b/install.sh index dccf6a5a8d..caca639aa0 100755 --- a/install.sh +++ b/install.sh @@ -54,7 +54,7 @@ } install_symlinks() { - printf "\\n\\tInstalling EOSIO.CDT Binary Symlinks\\n\\n" + printf "\\n\\tInstalling EOSIO.CDT Binary Symlinks\\n\\n" create_symlink "llvm-ranlib eosio-ranlib" create_symlink "llvm-ar eosio-ar" create_symlink "llvm-objdump eosio-objdump" diff --git a/modules/InstallCDT.cmake b/modules/InstallCDT.cmake index 1f14b8410f..9f34853cdd 100644 --- a/modules/InstallCDT.cmake +++ b/modules/InstallCDT.cmake @@ -3,7 +3,7 @@ macro( eosio_clang_install file ) set(BINARY_DIR ${CMAKE_BINARY_DIR}/eosio_llvm/bin) add_custom_command( TARGET EosioClang POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${BINARY_DIR}/${file} ${CMAKE_BINARY_DIR}/bin/ ) install(FILES ${BINARY_DIR}/${file} - DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} + DESTINATION ${CDT_INSTALL_PREFIX}/bin PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) endmacro( eosio_clang_install ) @@ -12,15 +12,17 @@ macro( eosio_clang_install_and_symlink file symlink ) add_custom_command( TARGET EosioClang POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${BINARY_DIR}/${file} ${CMAKE_BINARY_DIR}/bin/ ) add_custom_command( TARGET EosioClang POST_BUILD COMMAND cd ${CMAKE_BINARY_DIR}/bin && ln -sf ${file} ${symlink} ) install(FILES ${BINARY_DIR}/${file} - DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} + DESTINATION ${CDT_INSTALL_PREFIX}/bin PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + install(CODE "execute_process( COMMAND ${CMAKE_COMMAND} -E create_symlink ${CDT_INSTALL_PREFIX}/bin/${file} ${CMAKE_INSTALL_PREFIX}/bin/${symlink})") + install(CODE "message(\"-- Created Symlink : ${file} ${symlink}\")") endmacro( eosio_clang_install_and_symlink ) macro( eosio_tool_install file ) set(BINARY_DIR ${CMAKE_BINARY_DIR}/tools/bin) add_custom_command( TARGET EosioTools POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${BINARY_DIR}/${file} ${CMAKE_BINARY_DIR}/bin/ ) install(FILES ${BINARY_DIR}/${file} - DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} + DESTINATION ${CDT_INSTALL_PREFIX}/bin PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) endmacro( eosio_tool_install ) @@ -29,15 +31,16 @@ macro( eosio_tool_install_and_symlink file symlink ) add_custom_command( TARGET EosioTools POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${BINARY_DIR}/${file} ${CMAKE_BINARY_DIR}/bin/ ) add_custom_command( TARGET EosioTools POST_BUILD COMMAND cd ${CMAKE_BINARY_DIR}/bin && ln -sf ${file} ${symlink} ) install(FILES ${BINARY_DIR}/${file} - DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} + DESTINATION ${CDT_INSTALL_PREFIX}/bin PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + install(CODE "execute_process( COMMAND ${CMAKE_COMMAND} -E create_symlink ${CDT_INSTALL_PREFIX}/bin/${file} ${CMAKE_INSTALL_PREFIX}/bin/${symlink})") endmacro( eosio_tool_install_and_symlink ) macro( eosio_libraries_install) execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/lib) execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/include) - install(DIRECTORY ${CMAKE_BINARY_DIR}/lib/ DESTINATION ${CMAKE_INSTALL_PREFIX}/lib) - install(DIRECTORY ${CMAKE_BINARY_DIR}/include/ DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}) + install(DIRECTORY ${CMAKE_BINARY_DIR}/lib/ DESTINATION ${CDT_INSTALL_PREFIX}/lib) + install(DIRECTORY ${CMAKE_BINARY_DIR}/include/ DESTINATION ${CDT_INSTALL_PREFIX}/include) endmacro( eosio_libraries_install ) eosio_clang_install_and_symlink(llvm-ranlib eosio-ranlib) From 13af15de461cb8af002ba9853e86ecf0df17724c Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Thu, 27 Jun 2019 15:56:39 -0400 Subject: [PATCH 26/99] fixed symlinks --- modules/InstallCDT.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/InstallCDT.cmake b/modules/InstallCDT.cmake index 9f34853cdd..2b1fe4c4f4 100644 --- a/modules/InstallCDT.cmake +++ b/modules/InstallCDT.cmake @@ -14,8 +14,8 @@ macro( eosio_clang_install_and_symlink file symlink ) install(FILES ${BINARY_DIR}/${file} DESTINATION ${CDT_INSTALL_PREFIX}/bin PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + install(CODE "execute_process( COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_INSTALL_PREFIX}/bin)") install(CODE "execute_process( COMMAND ${CMAKE_COMMAND} -E create_symlink ${CDT_INSTALL_PREFIX}/bin/${file} ${CMAKE_INSTALL_PREFIX}/bin/${symlink})") - install(CODE "message(\"-- Created Symlink : ${file} ${symlink}\")") endmacro( eosio_clang_install_and_symlink ) macro( eosio_tool_install file ) From c8751aad25ce809c359d6c705ddaea002bea791b Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Thu, 27 Jun 2019 16:59:32 -0400 Subject: [PATCH 27/99] fixed symlinks for tools --- CMakeLists.txt | 12 +++--- install.sh | 84 ++++++++++++---------------------------- modules/InstallCDT.cmake | 20 +++++----- 3 files changed, 40 insertions(+), 76 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ac95a45e0f..e26b165a9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,12 +28,12 @@ else() endif() -if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - message(WARNING "CMAKE_INSTALL_PREFIX is set to default path of ${CMAKE_INSTALL_PREFIX}, resetting to ${CMAKE_INSTALL_PREFIX}/eosio.cdt") - set(CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/eosio.cdt") -elseif ("${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local") - message(WARNING "CMAKE_INSTALL_PREFIX is explicitly set to /usr/local. This is not recommended.") -endif() +##if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) +## message(WARNING "CMAKE_INSTALL_PREFIX is set to default path of ${CMAKE_INSTALL_PREFIX}, resetting to ${CMAKE_INSTALL_PREFIX}/eosio.cdt") +## set(CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/eosio.cdt") +##elseif ("${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local") +## message(WARNING "CMAKE_INSTALL_PREFIX is explicitly set to /usr/local. This is not recommended.") +##endif() set(CDT_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/eosio.cdt) diff --git a/install.sh b/install.sh index caca639aa0..f19a1113bf 100755 --- a/install.sh +++ b/install.sh @@ -31,72 +31,36 @@ ########################################################################## - CWD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - if [ "${CWD}" != "${PWD}" ]; then - printf "\\n\\tPlease cd into directory %s to run this script.\\n \\tExiting now.\\n\\n" "${CWD}" - exit 1 - fi + CWD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + if [ "${CWD}" != "${PWD}" ]; then + printf "\\n\\tPlease cd into directory %s to run this script.\\n \\tExiting now.\\n\\n" "${CWD}" + exit 1 + fi - BUILD_DIR="${PWD}/build" - CMAKE_BUILD_TYPE=Release - TIME_BEGIN=$( date -u +%s ) + BUILD_DIR="${PWD}/build" + CMAKE_BUILD_TYPE=Release + TIME_BEGIN=$( date -u +%s ) INSTALL_PREFIX="/usr/local/eosio.cdt" - VERSION=1.2 + VERSION=1.2 - txtbld=$(tput bold) - bldred=${txtbld}$(tput setaf 1) - txtrst=$(tput sgr0) - - create_symlink() { - pushd /usr/local/bin &> /dev/null - ln -sf ../eosio.cdt/bin/$1 $2 - popd &> /dev/null - } - - install_symlinks() { - printf "\\n\\tInstalling EOSIO.CDT Binary Symlinks\\n\\n" - create_symlink "llvm-ranlib eosio-ranlib" - create_symlink "llvm-ar eosio-ar" - create_symlink "llvm-objdump eosio-objdump" - create_symlink "llvm-readelf eosio-readelf" - create_symlink "eosio-cc eosio-cc" - create_symlink "eosio-cpp eosio-cpp" - create_symlink "eosio-ld eosio-ld" - create_symlink "eosio-pp eosio-pp" - create_symlink "eosio-init eosio-init" - create_symlink "eosio-abigen eosio-abigen" - create_symlink "eosio-abidiff eosio-abidiff" - create_symlink "eosio-wasm2wast eosio-wasm2wast" - create_symlink "eosio-wast2wasm eosio-wast2wasm" - } + txtbld=$(tput bold) + bldred=${txtbld}$(tput setaf 1) + txtrst=$(tput sgr0) - create_cmake_symlink() { - mkdir -p /usr/local/lib/cmake/eosio.cdt - pushd /usr/local/lib/cmake/eosio.cdt &> /dev/null - ln -sf ../../../eosio.cdt/lib/cmake/eosio.cdt/$1 $1 - popd &> /dev/null - } - if [ ! -d "${BUILD_DIR}" ]; then + if [ ! -d "${BUILD_DIR}" ]; then printf "\\n\\tError, build.sh has not ran. Please run ./build.sh first!\\n\\n" exit -1 - fi - - if ! pushd "${BUILD_DIR}" - then - printf "Unable to enter build directory %s.\\n Exiting now.\\n" "${BUILD_DIR}" - exit 1; - fi - - if ! make install - then - printf "\\n\\t>>>>>>>>>>>>>>>>>>>> MAKE installing EOSIO has exited with the above error.\\n\\n" - exit -1 - fi + fi + if ! pushd "${BUILD_DIR}"; then + printf "Unable to enter build directory %s.\\n Exiting now.\\n" "${BUILD_DIR}" + exit 1; + fi + if ! make install; then + printf "\\n\\t>>>>>>>>>>>>>>>>>>>> MAKE installing EOSIO has exited with the above error.\\n\\n" + exit -1 + fi popd &> /dev/null - install_symlinks - create_cmake_symlink "eosio.cdt-config.cmake" - printf "\n${bldred}\t ___ ___ ___ ___\n" printf "\t / /\\ / /\\ / /\\ ___ / /\\ \n" printf "\t / /:/_ / /::\\ / /:/_ / /\\ / /::\\ \n" @@ -109,5 +73,5 @@ printf "\t \\ \\::/ \\ \\::/ /__/:/ \\__\\/ \\ \\::/ \n" printf "\t \\__\\/ \\__\\/ \\__\\/ \\__\\/ \n${txtrst}" - printf "\\tFor more information:\\n" - printf "\\tEOSIO website: https://eos.io\\n" + printf "\\tFor more information:\\n" + printf "\\tEOSIO website: https://eos.io\\n" diff --git a/modules/InstallCDT.cmake b/modules/InstallCDT.cmake index 2b1fe4c4f4..4ba5a88502 100644 --- a/modules/InstallCDT.cmake +++ b/modules/InstallCDT.cmake @@ -29,10 +29,10 @@ endmacro( eosio_tool_install ) macro( eosio_tool_install_and_symlink file symlink ) set(BINARY_DIR ${CMAKE_BINARY_DIR}/tools/bin) add_custom_command( TARGET EosioTools POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${BINARY_DIR}/${file} ${CMAKE_BINARY_DIR}/bin/ ) - add_custom_command( TARGET EosioTools POST_BUILD COMMAND cd ${CMAKE_BINARY_DIR}/bin && ln -sf ${file} ${symlink} ) install(FILES ${BINARY_DIR}/${file} DESTINATION ${CDT_INSTALL_PREFIX}/bin PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + install(CODE "execute_process( COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_INSTALL_PREFIX}/bin)") install(CODE "execute_process( COMMAND ${CMAKE_COMMAND} -E create_symlink ${CDT_INSTALL_PREFIX}/bin/${file} ${CMAKE_INSTALL_PREFIX}/bin/${symlink})") endmacro( eosio_tool_install_and_symlink ) @@ -58,15 +58,15 @@ eosio_clang_install(ld.lld) eosio_clang_install(ld64.lld) eosio_clang_install(clang-7) eosio_clang_install(wasm-ld) -eosio_tool_install(eosio-pp) -eosio_tool_install(eosio-wast2wasm) -eosio_tool_install(eosio-wasm2wast) -eosio_tool_install(eosio-cc) -eosio_tool_install(eosio-cpp) -eosio_tool_install(eosio-ld) -eosio_tool_install(eosio-abigen) -eosio_tool_install(eosio-abidiff) -eosio_tool_install(eosio-init) +eosio_tool_install_and_symlink(eosio-pp eosio-pp) +eosio_tool_install_and_symlink(eosio-wast2wasm eosio-wast2wasm) +eosio_tool_install_and_symlink(eosio-wasm2wast eosio-wasm2wast) +eosio_tool_install_and_symlink(eosio-cc eosio-cc) +eosio_tool_install_and_symlink(eosio-cpp eosio-cpp) +eosio_tool_install_and_symlink(eosio-ld eosio-ld) +eosio_tool_install_and_symlink(eosio-abigen eosio-abigen) +eosio_tool_install_and_symlink(eosio-abidiff eosio-abidiff) +eosio_tool_install_and_symlink(eosio-init eosio-init) eosio_clang_install(../lib/LLVMEosioApply${CMAKE_SHARED_LIBRARY_SUFFIX}) eosio_clang_install(../lib/LLVMEosioSoftfloat${CMAKE_SHARED_LIBRARY_SUFFIX}) eosio_clang_install(../lib/eosio_plugin${CMAKE_SHARED_LIBRARY_SUFFIX}) From 07f4245572116e757a0cc34498647b854dfa2942 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Mon, 8 Jul 2019 14:40:53 -0400 Subject: [PATCH 28/99] Update CMakeList.txt --- CMakeLists.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e26b165a9f..25894f0024 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,14 +27,6 @@ else() set(VERSION_FULL "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") endif() - -##if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) -## message(WARNING "CMAKE_INSTALL_PREFIX is set to default path of ${CMAKE_INSTALL_PREFIX}, resetting to ${CMAKE_INSTALL_PREFIX}/eosio.cdt") -## set(CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/eosio.cdt") -##elseif ("${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local") -## message(WARNING "CMAKE_INSTALL_PREFIX is explicitly set to /usr/local. This is not recommended.") -##endif() - set(CDT_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/eosio.cdt) include(GNUInstallDirs) From 873b6731c8f664aa07da14d5852a1e22aab59427 Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Mon, 8 Jul 2019 15:48:02 -0400 Subject: [PATCH 29/99] Revert the eosio_llvm submodules to the versions currently in release/1.6.x --- eosio_llvm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eosio_llvm b/eosio_llvm index ce49c87984..a41b8e7653 160000 --- a/eosio_llvm +++ b/eosio_llvm @@ -1 +1 @@ -Subproject commit ce49c87984047fe2ecdcfa1c6ce5c53d9ea6e7c1 +Subproject commit a41b8e7653258a4f1a5911ef28c95672efce051e From 4a8ef614a81b50d7a972737f78904c35aa6305ae Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Wed, 10 Jul 2019 12:00:29 -0400 Subject: [PATCH 30/99] bump version to 1.6.2 --- CMakeLists.txt | 2 +- README.md | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 022411db22..61f04653c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ endif() set(VERSION_MAJOR 1) set(VERSION_MINOR 6) -set(VERSION_PATCH 1) +set(VERSION_PATCH 2) #set(VERSION_SUFFIX rc2) if (VERSION_SUFFIX) diff --git a/README.md b/README.md index c0cd29d277..8e166c3190 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # EOSIO.CDT (Contract Development Toolkit) -## Version : 1.6.1 +## Version : 1.6.2 EOSIO.CDT is a toolchain for WebAssembly (WASM) and set of tools to facilitate contract writing 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 not available or incomplete. @@ -22,8 +22,8 @@ $ brew remove eosio.cdt ``` #### Debian Package Install ```sh -$ wget https://github.com/eosio/eosio.cdt/releases/download/v1.6.1/eosio.cdt_1.6.1-1_amd64.deb -$ sudo apt install ./eosio.cdt_1.6.1-1_amd64.deb +$ wget https://github.com/eosio/eosio.cdt/releases/download/v1.6.2/eosio.cdt_1.6.2-1_amd64.deb +$ sudo apt install ./eosio.cdt_1.6.2-1_amd64.deb ``` #### Debian Package Uninstall ```sh @@ -32,8 +32,8 @@ $ sudo apt remove eosio.cdt #### Fedora RPM Package Install ```sh -$ wget https://github.com/eosio/eosio.cdt/releases/download/v1.6.1/eosio.cdt-1.6.1-1.fedora-x86_64.rpm -$ sudo yum install ./eosio.cdt-1.6.1-1.fedora-x86_64.rpm +$ wget https://github.com/eosio/eosio.cdt/releases/download/v1.6.2/eosio.cdt-1.6.2-1.fedora-x86_64.rpm +$ sudo yum install ./eosio.cdt-1.6.2-1.fedora-x86_64.rpm ``` #### Fedora RPM Package Uninstall @@ -43,8 +43,8 @@ $ sudo yum remove eosio.cdt #### Centos RPM Package Install ```sh -$ wget https://github.com/eosio/eosio.cdt/releases/download/v1.6.1/eosio.cdt-1.6.1-1.centos-x86_64.rpm -$ sudo yum install ./eosio.cdt-1.6.1-1.centos-x86_64.rpm +$ wget https://github.com/eosio/eosio.cdt/releases/download/v1.6.2/eosio.cdt-1.6.2-1.centos-x86_64.rpm +$ sudo yum install ./eosio.cdt-1.6.2-1.centos-x86_64.rpm ``` #### Centos RPM Package Uninstall From ce5d7b5c60d89513ab0fa7273d29eb2f148cf626 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Wed, 10 Jul 2019 15:18:52 -0400 Subject: [PATCH 31/99] remove default path with build script --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index fe24568d22..00a111daa9 100755 --- a/build.sh +++ b/build.sh @@ -94,7 +94,7 @@ if [ -z "$CMAKE" ]; then CMAKE=$( command -v cmake ) fi -"$CMAKE" -DCMAKE_INSTALL_PREFIX=/usr/local/eosio.cdt ../ +"$CMAKE" ../ if [ $? -ne 0 ]; then exit -1; fi From ce81ac95516dbf844efbe51e1cd8c7b3b5accec8 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Fri, 19 Jul 2019 10:52:18 -0400 Subject: [PATCH 32/99] empty commit, trigger for pipeline From 320eb7be4b44aaab9a05536ede4c7f9bb69f65c3 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Mon, 5 Aug 2019 04:52:44 +0000 Subject: [PATCH 33/99] Fix include path priority issue Default include paths are prepended to those of users, so user- defined header having same file name is ignored. This behavior prevents users from redefining provided methods. --- tools/include/compiler_options.hpp.in | 92 +++++++++++++-------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/tools/include/compiler_options.hpp.in b/tools/include/compiler_options.hpp.in index e860c2fc25..dcafb3b40d 100644 --- a/tools/include/compiler_options.hpp.in +++ b/tools/include/compiler_options.hpp.in @@ -549,52 +549,6 @@ static Options CreateOptions(bool add_defaults=true) { #endif #ifndef ONLY_LD copts.emplace_back("-I./"); - if (!sysroot_opt.empty()) { - copts.emplace_back("--sysroot="+sysroot_opt); - copts.emplace_back("-I"+sysroot_opt+"/include/libcxx"); - copts.emplace_back("-I"+sysroot_opt+"/include/libc"); - - // only allow capi for native builds and for eosio-cc - if (fnative_opt) { - copts.emplace_back("-I"+sysroot_opt+"/include/eosiolib/capi"); - copts.emplace_back("-I"+sysroot_opt+"/include/eosiolib/native"); - } -#ifndef CPP_COMP - copts.emplace_back("-I"+sysroot_opt+"/include/eosiolib/capi"); -#endif - copts.emplace_back("-I"+sysroot_opt+"/include/eosiolib/core"); - copts.emplace_back("-I"+sysroot_opt+"/include/eosiolib/contracts"); - - ldopts.emplace_back("-L"+sysroot_opt+"/lib"); -#ifndef __APPLE__ - ldopts.emplace_back("-L"+sysroot_opt+"/lib64"); -#endif - } - else { - copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/libcxx"); - copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/libc"); - copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include"); - copts.emplace_back("--sysroot="+eosio::cdt::whereami::where()+"/../"); - agopts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/libcxx"); - agopts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/libc"); - agopts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include"); - agopts.emplace_back("--sysroot="+eosio::cdt::whereami::where()+"/../"); - ldopts.emplace_back("-L"+eosio::cdt::whereami::where()+"/../lib"); - - if (fnative_opt) { - copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/eosiolib/capi"); - copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/eosiolib/native"); - } -#ifndef CPP_COMP - copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/eosiolib/capi"); -#endif - copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/eosiolib/core"); - copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/eosiolib/contracts"); - -#ifndef __APPLE__ - ldopts.emplace_back("-L"+eosio::cdt::whereami::where()+"/../lib64"); -#endif - } if (!isystem_opt.empty()) { copts.emplace_back("-isystem="+isystem_opt); @@ -682,6 +636,52 @@ static Options CreateOptions(bool add_defaults=true) { copts.emplace_back("-I"+inc_dir); agopts.emplace_back("-I"+inc_dir); } + if (!sysroot_opt.empty()) { + copts.emplace_back("--sysroot="+sysroot_opt); + copts.emplace_back("-I"+sysroot_opt+"/include/libcxx"); + copts.emplace_back("-I"+sysroot_opt+"/include/libc"); + + // only allow capi for native builds and for eosio-cc + if (fnative_opt) { + copts.emplace_back("-I"+sysroot_opt+"/include/eosiolib/capi"); + copts.emplace_back("-I"+sysroot_opt+"/include/eosiolib/native"); + } +#ifndef CPP_COMP + copts.emplace_back("-I"+sysroot_opt+"/include/eosiolib/capi"); +#endif + copts.emplace_back("-I"+sysroot_opt+"/include/eosiolib/core"); + copts.emplace_back("-I"+sysroot_opt+"/include/eosiolib/contracts"); + + ldopts.emplace_back("-L"+sysroot_opt+"/lib"); +#ifndef __APPLE__ + ldopts.emplace_back("-L"+sysroot_opt+"/lib64"); +#endif + } + else { + copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/libcxx"); + copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/libc"); + copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include"); + copts.emplace_back("--sysroot="+eosio::cdt::whereami::where()+"/../"); + agopts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/libcxx"); + agopts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/libc"); + agopts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include"); + agopts.emplace_back("--sysroot="+eosio::cdt::whereami::where()+"/../"); + ldopts.emplace_back("-L"+eosio::cdt::whereami::where()+"/../lib"); + + if (fnative_opt) { + copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/eosiolib/capi"); + copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/eosiolib/native"); + } +#ifndef CPP_COMP + copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/eosiolib/capi"); +#endif + copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/eosiolib/core"); + copts.emplace_back("-I"+eosio::cdt::whereami::where()+"/../include/eosiolib/contracts"); + +#ifndef __APPLE__ + ldopts.emplace_back("-L"+eosio::cdt::whereami::where()+"/../lib64"); +#endif + } if (O_opt.empty() && !g_opt) { copts.emplace_back("-O3"); } From 148b4e9d45b30f6ea02f84be54a49ded09d12b0a Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Thu, 8 Aug 2019 00:57:15 +0000 Subject: [PATCH 34/99] Fix libcxx include priority --- libraries/libc++/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/libc++/CMakeLists.txt b/libraries/libc++/CMakeLists.txt index a68aacbdd6..b5b74e98cb 100644 --- a/libraries/libc++/CMakeLists.txt +++ b/libraries/libc++/CMakeLists.txt @@ -18,19 +18,19 @@ add_native_library(native_c++ target_include_directories(c++ PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/libcxx/include ${CMAKE_SOURCE_DIR}/libc/musl/include ${CMAKE_SOURCE_DIR}/libc/musl/src/internal ${CMAKE_SOURCE_DIR}/libc/musl/src/crypt - ${CMAKE_SOURCE_DIR}/libc/musl/arch/eos - ${CMAKE_CURRENT_SOURCE_DIR}/libcxx/include) + ${CMAKE_SOURCE_DIR}/libc/musl/arch/eos) target_include_directories(native_c++ PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/libcxx/include ${CMAKE_SOURCE_DIR}/libc/musl/include ${CMAKE_SOURCE_DIR}/libc/musl/src/internal ${CMAKE_SOURCE_DIR}/libc/musl/src/crypt - ${CMAKE_SOURCE_DIR}/libc/musl/arch/eos - ${CMAKE_CURRENT_SOURCE_DIR}/libcxx/include) + ${CMAKE_SOURCE_DIR}/libc/musl/arch/eos) target_link_libraries(c++ c) target_link_libraries(native_c++ native_c) From d196d525875b70f63c353ec828746056f7ee6c2f Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Thu, 8 Aug 2019 02:26:40 +0000 Subject: [PATCH 35/99] Remove duplicated include path --- tools/include/compiler_options.hpp.in | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tools/include/compiler_options.hpp.in b/tools/include/compiler_options.hpp.in index dcafb3b40d..4302718533 100644 --- a/tools/include/compiler_options.hpp.in +++ b/tools/include/compiler_options.hpp.in @@ -548,8 +548,6 @@ static Options CreateOptions(bool add_defaults=true) { #endif #endif #ifndef ONLY_LD - copts.emplace_back("-I./"); - if (!isystem_opt.empty()) { copts.emplace_back("-isystem="+isystem_opt); } @@ -792,12 +790,11 @@ static Options CreateOptions(bool add_defaults=true) { #ifndef ONLY_LD #ifdef CPP_COMP if (! std_opt.empty()) { - copts.emplace_back("--std="+std_opt); - agopts.emplace_back("--std="+std_opt); + copts.emplace_back("-std="+std_opt); + agopts.emplace_back("-std="+std_opt); } else { - copts.emplace_back("--std=c++17"); - agopts.emplace_back("--std=c++17"); - + copts.emplace_back("-std=c++17"); + agopts.emplace_back("-std=c++17"); } if (faligned_allocation_opt) { From 6afaff3a85c294da7ce5a9172b177137b955c30f Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Thu, 8 Aug 2019 02:28:16 +0000 Subject: [PATCH 36/99] Remove duplicated default compiler options --- tools/cc/eosio-cpp.cpp.in | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/tools/cc/eosio-cpp.cpp.in b/tools/cc/eosio-cpp.cpp.in index d8a1cdd288..1de26af1bc 100644 --- a/tools/cc/eosio-cpp.cpp.in +++ b/tools/cc/eosio-cpp.cpp.in @@ -113,27 +113,10 @@ void generate(const std::vector& base_options, std::string input, s options.push_back(input); // don't remove oddity of CommonOptionsParser? options.push_back(input); options.push_back("--"); - for (size_t i=1; i < base_options.size(); i++) { + for (size_t i=0; i < base_options.size(); i++) { options.push_back(base_options[i]); } - options.push_back("--target=wasm32"); - options.push_back("-nostdlib"); - options.push_back("-ffreestanding"); - options.push_back("-fno-builtin"); - options.push_back("-fno-rtti"); - options.push_back("-fno-exceptions"); - options.push_back("-I${Boost_INCLUDE_DIRS}"); - options.push_back("-DBOOST_DISABLE_ASSERTS"); - options.push_back("-DBOOST_EXCEPTION_DISABLE"); options.push_back("-Wno-everything"); - options.push_back("-std=c++17"); - options.push_back(std::string("-I")+eosio::cdt::whereami::where()+"/../include/libcxx"); - options.push_back(std::string("-I")+eosio::cdt::whereami::where()+"/../include/libc"); - options.push_back(std::string("-I")+eosio::cdt::whereami::where()+"/../include"); - options.push_back(std::string("-I")+eosio::cdt::whereami::where()+"/../../../../../libraries/libc++/libcxx/include"); - options.push_back(std::string("-I")+eosio::cdt::whereami::where()+"/../../../../../libraries/libc/musl/include"); - options.push_back(std::string("-I")+eosio::cdt::whereami::where()+"/../../../../../libraries"); - options.push_back(std::string("-I")+eosio::cdt::whereami::where()+"/../../../../../libraries/boost/include"); int size = options.size(); const char** new_argv = new const char*[size]; From a702a462cb4d67e65d9b546d04d8004d24c771e3 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Mon, 12 Aug 2019 04:43:22 +0000 Subject: [PATCH 37/99] Fix compilation issue with GCC-9 compilation fails with GCC 9: redundant move in return statement [-Werror=redundant-move] --- tools/external/wabt/src/lexer-source.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/external/wabt/src/lexer-source.cc b/tools/external/wabt/src/lexer-source.cc index 762aea7e35..351bf6171c 100644 --- a/tools/external/wabt/src/lexer-source.cc +++ b/tools/external/wabt/src/lexer-source.cc @@ -38,7 +38,7 @@ std::unique_ptr LexerSourceFile::Clone() { result.reset(); } - return std::move(result); + return result; } Result LexerSourceFile::Tell(Offset* out_offset) { From 15fa0afe4c11dba2c4dc5206211bd03ecc23c107 Mon Sep 17 00:00:00 2001 From: Scott Arnette Date: Fri, 23 Aug 2019 19:59:41 -0400 Subject: [PATCH 38/99] Added TravisCI and reworked Buildkite. --- .cicd/base-images.yml | 38 +++ .cicd/build.sh | 44 +++ .cicd/docker/amazonlinux-2.dockerfile | 21 ++ .cicd/docker/centos-7.6.dockerfile | 28 ++ .cicd/docker/ubuntu-16.04.dockerfile | 16 + .cicd/docker/ubuntu-18.04.dockerfile | 7 + .cicd/generate-base-images.sh | 16 + .cicd/helpers/docker-hash.sh | 24 ++ .cicd/helpers/general.sh | 6 + .cicd/metrics/test-metrics.js | 431 ++++++++++++++++++++++++++ .cicd/metrics/test-metrics.tar.gz | Bin 0 -> 96551 bytes .cicd/package.sh | 66 ++++ .cicd/pipeline.yml | 205 ++++++++++++ .cicd/submodule-regression-checker.sh | 44 +++ .cicd/tests.sh | 35 +++ .travis.yml | 27 ++ 16 files changed, 1008 insertions(+) create mode 100644 .cicd/base-images.yml create mode 100755 .cicd/build.sh create mode 100644 .cicd/docker/amazonlinux-2.dockerfile create mode 100644 .cicd/docker/centos-7.6.dockerfile create mode 100644 .cicd/docker/ubuntu-16.04.dockerfile create mode 100644 .cicd/docker/ubuntu-18.04.dockerfile create mode 100755 .cicd/generate-base-images.sh create mode 100644 .cicd/helpers/docker-hash.sh create mode 100644 .cicd/helpers/general.sh create mode 100644 .cicd/metrics/test-metrics.js create mode 100644 .cicd/metrics/test-metrics.tar.gz create mode 100755 .cicd/package.sh create mode 100644 .cicd/pipeline.yml create mode 100644 .cicd/submodule-regression-checker.sh create mode 100755 .cicd/tests.sh create mode 100644 .travis.yml diff --git a/.cicd/base-images.yml b/.cicd/base-images.yml new file mode 100644 index 0000000000..198a253fed --- /dev/null +++ b/.cicd/base-images.yml @@ -0,0 +1,38 @@ +env: + BUILD_TIMEOUT: 120 + TEST_TIMEOUT: 60 + TIMEOUT: 120 + +steps: + + - label: ":aws: [Amazon] 2 Ensure Docker Image" + command: + - ".cicd/generate-base-images.sh amazonlinux-2" + agents: + queue: "automation-eos-dockerhub-image-builder-fleet" + timeout: $BUILD_TIMEOUT + skip: $SKIP_AMAZON_LINUX_2 + + - label: ":centos: [CentOS] 7 Ensure Docker Image" + command: + - ".cicd/generate-base-images.sh centos-7" + agents: + queue: "automation-eos-dockerhub-image-builder-fleet" + timeout: $BUILD_TIMEOUT + skip: $SKIP_CENTOS_7 + + - label: ":ubuntu: [Ubuntu] 16.04 Ensure Docker Image" + command: + - ".cicd/generate-base-images.sh ubuntu-16.04" + agents: + queue: "automation-eos-dockerhub-image-builder-fleet" + timeout: $BUILD_TIMEOUT + skip: $SKIP_UBUNTU_16 + + - label: ":ubuntu: [Ubuntu] 18.04 Ensure Docker Image" + command: + - ".cicd/generate-base-images.sh ubuntu-18.04" + agents: + queue: "automation-eos-dockerhub-image-builder-fleet" + timeout: $BUILD_TIMEOUT + skip: $SKIP_UBUNTU_18 diff --git a/.cicd/build.sh b/.cicd/build.sh new file mode 100755 index 0000000000..e17751af19 --- /dev/null +++ b/.cicd/build.sh @@ -0,0 +1,44 @@ +#!/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 + cmake .. + make -j$JOBS + +else # Linux + + ARGS=${ARGS:-"--rm --init -v $(pwd):$MOUNTED_DIR"} + + . $HELPERS_DIR/docker-hash.sh + + # PRE_COMMANDS: Executed pre-cmake + PRE_COMMANDS="cd $MOUNTED_DIR/build" + BUILD_COMMANDS="cmake .. && make -j$JOBS" + + # 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" + fi + + COMMANDS="$PRE_COMMANDS && $BUILD_COMMANDS" + + # 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 \ No newline at end of file diff --git a/.cicd/docker/amazonlinux-2.dockerfile b/.cicd/docker/amazonlinux-2.dockerfile new file mode 100644 index 0000000000..28a2f5a614 --- /dev/null +++ b/.cicd/docker/amazonlinux-2.dockerfile @@ -0,0 +1,21 @@ +FROM amazonlinux:2.0.20190508 +# install dependencies. +RUN yum update -y && \ + yum install -y git gcc.x86_64 gcc-c++.x86_64 autoconf automake libtool make bzip2 \ + bzip2-devel.x86_64 openssl-devel.x86_64 gmp-devel.x86_64 libstdc++.x86_64 \ + python.x86_64 python3-devel.x86_64 libedit-devel.x86_64 doxygen.x86_64 graphviz.x86_64 perl +# build lcov +RUN git clone https://github.com/linux-test-project/lcov.git && \ + 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 && \ + tar -xzf cmake-3.10.2.tar.gz && \ + cd cmake-3.10.2 && \ + ./bootstrap --prefix=/usr/local && \ + make -j$(nproc) && \ + make install && \ + cd .. && \ + rm -f cmake-3.10.2.tar.gz \ No newline at end of file diff --git a/.cicd/docker/centos-7.6.dockerfile b/.cicd/docker/centos-7.6.dockerfile new file mode 100644 index 0000000000..f7baa808a5 --- /dev/null +++ b/.cicd/docker/centos-7.6.dockerfile @@ -0,0 +1,28 @@ +FROM centos:7.6.1810 +# 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 \ + 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 +# 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 && \ + ./bootstrap --prefix=/usr/local && \ + make -j$(nproc) && \ + make install && \ + cd .. && \ + rm -f cmake-3.10.2.tar.gz \ No newline at end of file diff --git a/.cicd/docker/ubuntu-16.04.dockerfile b/.cicd/docker/ubuntu-16.04.dockerfile new file mode 100644 index 0000000000..0bda1a4138 --- /dev/null +++ b/.cicd/docker/ubuntu-16.04.dockerfile @@ -0,0 +1,16 @@ +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 \ + libgmp3-dev autotools-dev build-essential libicu-dev python2.7-dev \ + python3-dev autoconf libtool curl zlib1g-dev doxygen graphviz +# 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 && \ + cd cmake-3.10.2 && \ + ./bootstrap --prefix=/usr/local && \ + make -j$(nproc) && \ + make install && \ + cd .. && \ + rm -f cmake-3.10.2.tar.gz \ No newline at end of file diff --git a/.cicd/docker/ubuntu-18.04.dockerfile b/.cicd/docker/ubuntu-18.04.dockerfile new file mode 100644 index 0000000000..817636b8e2 --- /dev/null +++ b/.cicd/docker/ubuntu-18.04.dockerfile @@ -0,0 +1,7 @@ +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 diff --git a/.cicd/generate-base-images.sh b/.cicd/generate-base-images.sh new file mode 100755 index 0000000000..94abce495c --- /dev/null +++ b/.cicd/generate-base-images.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -eo pipefail +. ./.cicd/helpers/general.sh +. $HELPERS_DIR/docker-hash.sh +# look for Docker image +echo "+++ :mag_right: Looking for $FULL_TAG" +ORG_REPO=$(echo $FULL_TAG | cut -d: -f1) +TAG=$(echo $FULL_TAG | cut -d: -f2) +EXISTS=$(curl -s -H "Authorization: Bearer $(curl -sSL "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${ORG_REPO}:pull" | jq --raw-output .token)" "https://registry.hub.docker.com/v2/${ORG_REPO}/manifests/$TAG") +# build, if neccessary +if [[ $EXISTS =~ '404 page not found' || $EXISTS =~ 'manifest unknown' ]]; then # if we cannot pull the image, we build and push it first + docker build -t $FULL_TAG -f $CICD_DIR/docker/${IMAGE_TAG}.dockerfile . + docker push $FULL_TAG +else + echo "$FULL_TAG already exists." +fi \ No newline at end of file diff --git a/.cicd/helpers/docker-hash.sh b/.cicd/helpers/docker-hash.sh new file mode 100644 index 0000000000..3bbb644c06 --- /dev/null +++ b/.cicd/helpers/docker-hash.sh @@ -0,0 +1,24 @@ +export IMAGE_TAG=${IMAGE_TAG:-$1} + +function determine-hash() { + # determine the sha1 hash of all dockerfiles in the .cicd directory + [[ -z $1 ]] && echo "Please provide the files to be hashed (wildcards supported)" && exit 1 + echo "Obtaining Hash of files from $1..." + # collect all files, hash them, then hash those + HASHES=() + for FILE in $(find $1 -type f); do + HASH=$(sha1sum $FILE | sha1sum | awk '{ print $1 }') + HASHES=($HASH "${HASHES[*]}") + echo "$FILE - $HASH" + done + export DETERMINED_HASH=$(echo ${HASHES[*]} | sha1sum | awk '{ print $1 }') + export HASHED_IMAGE_TAG="${IMAGE_TAG}-${DETERMINED_HASH}" +} + +if [[ ! -z $IMAGE_TAG ]]; then + determine-hash "$CICD_DIR/docker/${IMAGE_TAG}.dockerfile" + export FULL_TAG="eosio/producer:eosio-cdt-$HASHED_IMAGE_TAG" +else + echo "Please set ENV::IMAGE_TAG to match the name of a platform dockerfile..." + exit 1 +fi \ No newline at end of file diff --git a/.cicd/helpers/general.sh b/.cicd/helpers/general.sh new file mode 100644 index 0000000000..42b041177a --- /dev/null +++ b/.cicd/helpers/general.sh @@ -0,0 +1,6 @@ +export ROOT_DIR=$( dirname "${BASH_SOURCE[0]}" )/../.. +export BUILD_DIR=$ROOT_DIR/build +export CICD_DIR=$ROOT_DIR/.cicd +export HELPERS_DIR=$CICD_DIR/helpers +export JOBS=${JOBS:-"$(getconf _NPROCESSORS_ONLN)"} +export MOUNTED_DIR='/workdir' diff --git a/.cicd/metrics/test-metrics.js b/.cicd/metrics/test-metrics.js new file mode 100644 index 0000000000..b995134d9b --- /dev/null +++ b/.cicd/metrics/test-metrics.js @@ -0,0 +1,431 @@ +#!/usr/bin/env node +/* includes */ +const execSync = require('child_process').execSync; // run shell commands +const fetch = require('node-fetch'); // downloading +const fs = require('fs'); // file stream +const XML = require('xml2js'); // parse xml + +/* globals */ +const buildkiteAccessToken = `?access_token=${process.env.BUILDKITE_API_KEY}`; // import buildkite access token from environment +const debug = (process.env.DEBUG === 'true') ? true : false; +let errorCount = 0; // count number of jobs which caused an error +const EXIT_SUCCESS = 0; +const inBuildkite = (process.env.BUILDKITE === 'true') ? true : false; +const outputFile = 'test-metrics.json'; +const pipelineWhitelist = // the pipelines for which we run diagnostics +[ + 'eosio', + 'eosio-base-images', + 'eosio-beta', + 'eosio-build-unpinned', + 'eosio-debug', + 'eosio-lrt', + 'eosio-security' +]; + +/* functions */ +// given a url string, download a text document +async function download(url) +{ + if (debug) console.log(`download(${url.replace(buildkiteAccessToken, '')})`); // DEBUG + const httpResponse = await fetch(url); + const body = await httpResponse.text(); + if (isNullOrEmpty(body)) + { + console.log(`ERROR: URL returned nothing! URL: ${url.replace(buildkiteAccessToken, '')}`); + const error = + { + http: { body, response: httpResponse, url}, + message: 'http body is null or empty', + origin: 'download()', + } + throw error; + } + if (debug) console.log('Download complete.'); // DEBUG + return body; +} + +// given a pipeline and a build number, get a build object +async function getBuild(pipeline, buildNumber) +{ + if (debug) console.log(`getBuild(${pipeline}, ${buildNumber})`); // DEBUG + const httpResponse = await fetch(`https://api.buildkite.com/v2/organizations/EOSIO/pipelines/${pipeline}/builds/${buildNumber}${buildkiteAccessToken}`); + return httpResponse.json(); +} + +// given a buildkite job, return the environmental variables +async function getEnvironment(job) +{ + if (debug) console.log('getEnvironment()'); // DEBUG + const httpResponse = await fetch(`${job.build_url}/jobs/${job.id}/env${buildkiteAccessToken}`); + const environment = await httpResponse.json(); + return environment.env; +} + +// given a string to search, a key as regex or a string, and optionally a start index, return the lowest line number containing the key +function getLineNumber(text, key, startIndex) +{ + if (debug) console.log('getLineNumber()'); // DEBUG + const begin = (isNullOrEmpty(startIndex) || !Number.isInteger(startIndex) || startIndex < 1) ? 0 : startIndex; + let found = false; + let lineNumber = 0; + const regex = (key instanceof RegExp); + text.split('\n').some((line) => + { + if (lineNumber >= begin && ((regex && key.test(line)) || (!regex && line.includes(key)))) + { + found = true; + return true; // c-style break + } + lineNumber += 1; + return false; // for the linter, plz delete when linter is fixed + }); + return (found) ? lineNumber : -1; +} + +// given a buildkite job, return a sanitized log file +async function getLog(job) +{ + if (debug) console.log(`getLog(${job.raw_log_url})`); // DEBUG + const logText = await download(job.raw_log_url + buildkiteAccessToken); + // returns log lowercase, with single spaces and '\n' only, and only ascii-printable characters + return sanitize(logText); // made this a separate function for unit testing purposes +} + +// given a Buildkite environment, return the operating system used +function getOS(environment) +{ + if (debug) console.log(`getOS(${environment.BUILDKITE_LABEL})`); // DEBUG + if (isNullOrEmpty(environment) || isNullOrEmpty(environment.BUILDKITE_LABEL)) + { + console.log('ERROR: getOS() called with empty environment.BUILDKITE_LABEL!'); + console.log(JSON.stringify(environment)); + return null; + } + const label = environment.BUILDKITE_LABEL.toLowerCase(); + if ((/aws(?!.*[23])/.test(label) || /amazon(?!.*[23])/.test(label))) + return 'Amazon Linux 1'; + if (/aws.*2/.test(label) || /amazon.*2/.test(label)) + return 'Amazon Linux 2'; + if (/centos(?!.*[89])/.test(label)) + return 'CentOS 7'; + if (/fedora(?!.*2[89])/.test(label) && /fedora(?!.*3\d)/.test(label)) + return 'Fedora 27'; + if (/high.*sierra/.test(label)) + return 'High Sierra'; + if (/mojave/.test(label)) + return 'Mojave'; + if (/ubuntu.*16.*04/.test(label) || /ubuntu.*16(?!.*10)/.test(label)) + return 'Ubuntu 16.04'; + if (/ubuntu.*18.*04/.test(label) || /ubuntu.*18(?!.*10)/.test(label)) + return 'Ubuntu 18.04'; + if (/docker/.test(label)) + return 'Docker'; + return 'Unknown'; +} + +// given a Buildkite job, return the test-results.xml file as JSON +async function getXML(job) +{ + if (debug) console.log('getXML()'); // DEBUG + const xmlFilename = 'test-results.xml'; + const artifacts = await download(job.artifacts_url + buildkiteAccessToken); + const testResultsArtifact = JSON.parse(artifacts).filter(artifact => artifact.filename === xmlFilename); + if (isNullOrEmpty(testResultsArtifact)) + { + console.log(`WARNING: No ${xmlFilename} found for "${job.name}"! Link: ${job.web_url}`); + return null; + } + const urlBuildkite = testResultsArtifact[0].download_url; + const rawXML = await download(urlBuildkite + buildkiteAccessToken); + const xmlOptions = + { + attrNameProcessors: [function lower(name) { return name.toLowerCase(); }], + explicitArray: false, // do not put single strings in single-element arrays + mergeAttrs: true, // make attributes children of their node + normalizeTags: true, // convert all tag names to lowercase + }; + let xmlError, xmlTestResults; + await XML.parseString(rawXML, xmlOptions, (err, result) => {xmlTestResults = result; xmlError = err;}); + if (isNullOrEmpty(xmlError)) + return xmlTestResults; + console.log(`WARNING: Failed to parse xml for "${job.name}" job! Link: ${job.web_url}`); + console.log(JSON.stringify(xmlError)); + return null; +} + +// test if variable is empty +function isNullOrEmpty(str) +{ + return (str === null || str === undefined || str.length === 0 || /^\s*$/.test(str)); +} + +// return array of test results from a buildkite job log +function parseLog(logText) +{ + if (debug) console.log('parseLog()'); // DEBUG + const lines = logText.split('\n'); + const resultLines = lines.filter(line => /test\s+#\d+/.test(line)); // 'grep' for the test result lines + // parse the strings and make test records + return resultLines.map((line) => + { + const y = line.trim().split(/test\s+#\d+/).pop(); // remove everything before the test declaration + const parts = y.split(/\s+/).slice(1, -1); // split the line and remove the test number and time unit + const testName = parts[0]; + const testTime = parts[(parts.length - 1)]; + const rawResult = parts.slice(1, -1).join(); + let testResult; + if (rawResult.includes('failed')) + testResult = 'Failed'; + else if (rawResult.includes('passed')) + testResult = 'Passed'; + else + testResult = 'Exception'; + return { testName, testResult, testTime }; // create a test record + }); +} + +// return array of test results from an xUnit-formatted JSON object +function parseXunit(xUnit) +{ + if (debug) console.log('parseXunit()'); // DEBUG + if (isNullOrEmpty(xUnit)) + { + console.log('WARNING: xUnit is empty!'); + return null; + } + return xUnit.site.testing.test.map((test) => + { + const testName = test.name; + const testTime = test.results.namedmeasurement.filter(x => /execution\s+time/.test(x.name.toLowerCase()))[0].value; + let testResult; + if (test.status.includes('failed')) + testResult = 'Failed'; + else if (test.status.includes('passed')) + testResult = 'Passed'; + else + testResult = 'Exception'; + return { testName, testResult, testTime }; + }); +} + +// returns text lowercase, with single spaces and '\n' only, and only ascii-printable characters +function sanitize(text) +{ + if (debug) console.log(`sanitize(text) where text.length = ${text.length} bytes`); // DEBUG + const chunkSize = 131072; // process text in 128 kB chunks + if (text.length > chunkSize) + return sanitize(text.slice(0, chunkSize)).concat(sanitize(text.slice(chunkSize))); + return text + .replace(/(?!\n)\r(?!\n)/g, '\n').replace(/\r/g, '') // convert all line endings to '\n' + .replace(/[^\S\n]+/g, ' ') // convert all whitespace to ' ' + .replace(/[^ -~\n]+/g, '') // remove non-printable characters + .toLowerCase(); +} + +// input is array of whole lines containing "test #" and ("failed" or "exception") +function testDiagnostics(test, logText) +{ + if (debug) + { + console.log(`testDiagnostics(test, logText) where logText.length = ${logText.length} bytes and test is`); // DEBUG + console.log(JSON.stringify(test)); + } + // get basic information + const testResultLine = new RegExp(`test\\s+#\\d+.*${test.testName}`, 'g'); // regex defining "test #" line + const startIndex = getLineNumber(logText, testResultLine); + const output = { errorMsg: null, lineNumber: startIndex + 1, stackTrace: null }; // default output + // filter tests + if (test.testResult.toLowerCase() === 'passed') + return output; + output.errorMsg = 'test diangostics are not enabled for this pipeline'; + if (!pipelineWhitelist.includes(test.pipeline)) + return output; + // diagnostics + if (debug) console.log('Running diagnostics...'); // DEBUG + output.errorMsg = 'uncategorized'; + const testLog = logText.split(testResultLine)[1].split(/test\s*#/)[0].split('\n'); // get log output from this test only, as array of lines + let errorLine = testLog[0]; // first line, from "test ## name" to '\n' exclusive + if (/\.+ *\** *not run\s+0+\.0+ sec$/.test(errorLine)) // not run + output.errorMsg = 'test not run'; + else if (/\.+ *\** *time *out\s+\d+\.\d+ sec$/.test(errorLine)) // timeout + output.errorMsg = 'test timeout'; + else if (/exception/.test(errorLine)) // test exception + output.errorMsg = errorLine.split('exception')[1].replace(/[: \d.]/g, '').replace(/sec$/, ''); // isolate the error message after exception + else if (/fc::.*exception/.test(testLog.filter(line => !isNullOrEmpty(line))[1])) // fc exception + { + [, errorLine] = testLog.filter(line => !isNullOrEmpty(line)); // get first line + output.errorMsg = `fc::${errorLine.split('::')[1].replace(/['",]/g, '').split(' ')[0]}`; // isolate fx exception body + } + else if (testLog.join('\n').includes('ctest:')) // ctest exception + { + [errorLine] = testLog.filter(line => line.includes('ctest:')); + output.errorMsg = `ctest:${errorLine.split('ctest:')[1]}`; + } + else if (!isNullOrEmpty(testLog.filter(line => /boost.+exception/.test(line)))) // boost exception + { + [errorLine] = testLog.filter(line => /boost.+exception/.test(line)); + output.errorMsg = `boost: ${errorLine.replace(/[()]/g, '').split(/: (.+)/)[1]}`; // capturing parenthesis, split only at first ' :' + output.stackTrace = testLog.filter(line => /thread-\d+/.test(line))[0].split('thread-')[1].replace(/^\d+/, '').trim().replace(/[[]\d+m$/, ''); // get the bottom of the stack trace + } + else if (/unit[-_. ]+test/.test(test.testName) || /plugin[-_. ]+test/.test(test.testName)) // unit test, application exception + { + if (!isNullOrEmpty(testLog.filter(line => line.includes('exception: ')))) + { + [errorLine] = testLog.filter(line => line.includes('exception: ')); + [, output.errorMsg] = errorLine.replace(/[()]/g, '').split(/: (.+)/); // capturing parenthesis, split only at first ' :' + output.stackTrace = testLog.filter(line => /thread-\d+/.test(line))[0].split('thread-')[1].replace(/^\d+/, '').trim().replace(/[[]\d+m$/, ''); // get the bottom of the stack trace + } + // else uncategorized unit test + } + // else integration test, add cross-referencing code here (or uncategorized) + if (errorLine !== testLog[0]) // get real line number from log file + output.lineNumber = getLineNumber(logText, errorLine, startIndex) + 1; + return output; +} + +// return test metrics given a buildkite job or build +async function testMetrics(buildkiteObject) +{ + if (!isNullOrEmpty(buildkiteObject.type)) // input is a Buildkite job object + { + const job = buildkiteObject; + console.log(`Processing test metrics for "${job.name}"${(inBuildkite) ? '' : ` at ${job.web_url}`}...`); + if (isNullOrEmpty(job.exit_status)) + { + console.log(`${(inBuildkite) ? '+++ :warning: ' : ''}WARNING: "${job.name}" was skipped!`); + return null; + } + // get test results + const logText = await getLog(job); + let testResults; + let xUnit; + try + { + xUnit = await getXML(job); + testResults = parseXunit(xUnit); + } + catch (error) + { + console.log(`XML processing failed for "${job.name}"! Link: ${job.web_url}`); + console.log(JSON.stringify(error)); + testResults = null; + } + finally + { + if (isNullOrEmpty(testResults)) + testResults = parseLog(logText); + } + // get test metrics + const env = await getEnvironment(job); + env.BUILDKITE_REPO = env.BUILDKITE_REPO.replace(new RegExp('^git@github.com:(EOSIO/)?'), '').replace(new RegExp('.git$'), ''); + const metrics = []; + const os = getOS(env); + testResults.forEach((result) => + { + // add test properties + const test = + { + ...result, // add testName, testResult, testTime + agentName: env.BUILDKITE_AGENT_NAME, + agentRole: env.BUILDKITE_AGENT_META_DATA_QUEUE || env.BUILDKITE_AGENT_META_DATA_ROLE, + branch: env.BUILDKITE_BRANCH, + buildNumber: env.BUILDKITE_BUILD_NUMBER, + commit: env.BUILDKITE_COMMIT, + job: env.BUILDKITE_LABEL, + os, + pipeline: env.BUILDKITE_PIPELINE_SLUG, + repo: env.BUILDKITE_REPO, + testTime: parseFloat(result.testTime), + url: job.web_url, + }; + metrics.push({ ...test, ...testDiagnostics(test, logText) }); + }); + return metrics; + } + else if (!isNullOrEmpty(buildkiteObject.number)) // input is a Buildkite build object + { + const build = buildkiteObject; + console.log(`Processing test metrics for ${build.pipeline.slug} build ${build.number}${(inBuildkite) ? '' : ` at ${build.web_url}`}...`); + let metrics = [], promises = []; + // process test metrics + build.jobs.filter(job => job.type === 'script' && /test/.test(job.name.toLowerCase()) && ! /test metrics/.test(job.name.toLowerCase())).forEach((job) => + { + promises.push( + testMetrics(job) + .then((moreMetrics) => { + if (!isNullOrEmpty(moreMetrics)) + metrics = metrics.concat(moreMetrics); + else + console.log(`${(inBuildkite) ? '+++ :warning: ' : ''}WARNING: "${job.name}" metrics are empty!\nmetrics = ${JSON.stringify(moreMetrics)}`); + }).catch((error) => { + console.log(`${(inBuildkite) ? '+++ :no_entry: ' : ''}ERROR: Failed to process test metrics for "${job.name}"! Link: ${job.web_url}`); + console.log(JSON.stringify(error)); + errorCount++; + }) + ); + }); + await Promise.all(promises); + return metrics; + } + else // something else + { + console.log(`${(inBuildkite) ? '+++ :no_entry: ' : ''}ERROR: Buildkite object not recognized or not a test step!`); + console.log(JSON.stringify({buildkiteObject})); + return null; + } +} + +/* main */ +async function main() +{ + if (debug) console.log(`$ ${process.argv.join(' ')}`); + let build, metrics = null; + console.log(`${(inBuildkite) ? '+++ :evergreen_tree: ' : ''}Getting information from enviroment...`); + const buildNumber = process.env.BUILDKITE_BUILD_NUMBER || process.argv[2]; + const pipeline = process.env.BUILDKITE_PIPELINE_SLUG || process.argv[3]; + if (debug) + { + console.log(`BUILDKITE=${process.env.BUILDKITE}`); + console.log(`BUILDKITE_BUILD_NUMBER=${process.env.BUILDKITE_BUILD_NUMBER}`); + console.log(`BUILDKITE_PIPELINE_SLUG=${process.env.BUILDKITE_PIPELINE_SLUG}`); + console.log(' State:') + console.log(`inBuildkite = "${inBuildkite}"`); + console.log(`buildNumber = "${buildNumber}"`); + console.log(`pipeline = "${pipeline}"`); + } + if (isNullOrEmpty(buildNumber) || isNullOrEmpty(pipeline) || isNullOrEmpty(process.env.BUILDKITE_API_KEY)) + { + console.log(`${(inBuildkite) ? '+++ :no_entry: ' : ''}ERROR: Missing required inputs!`); + if (isNullOrEmpty(process.env.BUILDKITE_API_KEY)) console.log('- Buildkite API key, as BUILDKITE_API_KEY environment variable'); + if (isNullOrEmpty(buildNumber)) console.log('- Build Number, as BUILDKITE_BUILD_NUMBER or argument 1'); + if (isNullOrEmpty(pipeline)) console.log('- Pipeline Slug, as BUILDKITE_PIPELINE_SLUG or argument 2'); + errorCount = -1; + } + else + { + console.log(`${(inBuildkite) ? '+++ :bar_chart: ' : ''}Processing test metrics...`); + build = await getBuild(pipeline, buildNumber); + metrics = await testMetrics(build); + console.log('Done processing test metrics.'); + } + console.log(`${(inBuildkite) ? '+++ :pencil: ' : ''}Writing to file...`); + fs.writeFileSync(outputFile, JSON.stringify({ metrics })); + console.log(`Saved metrics to "${outputFile}" in "${process.cwd()}".`); + if (inBuildkite) + { + console.log('+++ :arrow_up: Uploading artifact...'); + execSync(`buildkite-agent artifact upload ${outputFile}`); + } + if (errorCount === 0) + console.log(`${(inBuildkite) ? '+++ :white_check_mark: ' : ''}Done!`); + else + { + console.log(`${(inBuildkite) ? '+++ :warning: ' : ''}Finished with errors.`); + console.log(`Please send automation a link to this job${(isNullOrEmpty(build)) ? '.' : `: ${build.web_url}`}`); + console.log('@kj4ezj or @zreyn on Telegram'); + } + return (inBuildkite) ? process.exit(EXIT_SUCCESS) : process.exit(errorCount); +}; + +main(); \ No newline at end of file diff --git a/.cicd/metrics/test-metrics.tar.gz b/.cicd/metrics/test-metrics.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..2381787ca06196f1b95c636435a77b58ba1cc0aa GIT binary patch literal 96551 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 /dev/null +git submodule update --init &> /dev/null +while read -r a b; do + BASE_MAP[$a]=$b +done < <(git submodule --quiet foreach --recursive 'echo $path `git log -1 --format=%ct`') + +for k in "${!BASE_MAP[@]}"; do + base_ts=${BASE_MAP[$k]} + pr_ts=${PR_MAP[$k]} + echo "submodule $k" + echo " timestamp on $CURRENT_BRANCH: $pr_ts" + echo " timestamp on $BASE_BRANCH: $base_ts" + if (( $pr_ts < $base_ts)); then + echo "$k is older on $CURRENT_BRANCH than $BASE_BRANCH; investigating..." + + if for c in `git log $CURRENT_BRANCH ^$BASE_BRANCH --pretty=format:"%H"`; do git show --pretty="" --name-only $c; done | grep -q "^$k$"; then + echo "ERROR: $k has regressed" + exit 1 + else + echo "$k was not in the diff; no regression detected" + fi + fi +done diff --git a/.cicd/tests.sh b/.cicd/tests.sh new file mode 100755 index 0000000000..e0a68e452f --- /dev/null +++ b/.cicd/tests.sh @@ -0,0 +1,35 @@ +#!/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 unit_tests -V -T Test" +COMMANDS="$PRE_COMMANDS && $TEST" + +if [[ $(uname) == 'Darwin' ]]; then + + # You can't use chained commands in execute + cd $BUILD_DIR + bash -c "$TEST" + +else # Linux + + ARGS=${ARGS:-"--rm --init -v $(pwd):$MOUNTED_DIR"} + + . $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="" + 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 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..8858c04709 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,27 @@ +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" From ed59261da148082e0733e821b65e433d115ff1eb Mon Sep 17 00:00:00 2001 From: Scott Arnette Date: Fri, 23 Aug 2019 20:06:09 -0400 Subject: [PATCH 39/99] Added TravisCI and reworked Buildkite. --- .cicd/base-images.yml | 38 +++ .cicd/build.sh | 44 +++ .cicd/docker/amazonlinux-2.dockerfile | 21 ++ .cicd/docker/centos-7.6.dockerfile | 28 ++ .cicd/docker/ubuntu-16.04.dockerfile | 16 + .cicd/docker/ubuntu-18.04.dockerfile | 7 + .cicd/generate-base-images.sh | 16 + .cicd/helpers/docker-hash.sh | 24 ++ .cicd/helpers/general.sh | 6 + .cicd/metrics/test-metrics.js | 431 ++++++++++++++++++++++++++ .cicd/metrics/test-metrics.tar.gz | Bin 0 -> 96551 bytes .cicd/package.sh | 66 ++++ .cicd/pipeline.yml | 205 ++++++++++++ .cicd/submodule-regression-checker.sh | 44 +++ .cicd/tests.sh | 35 +++ .travis.yml | 27 ++ 16 files changed, 1008 insertions(+) create mode 100644 .cicd/base-images.yml create mode 100755 .cicd/build.sh create mode 100644 .cicd/docker/amazonlinux-2.dockerfile create mode 100644 .cicd/docker/centos-7.6.dockerfile create mode 100644 .cicd/docker/ubuntu-16.04.dockerfile create mode 100644 .cicd/docker/ubuntu-18.04.dockerfile create mode 100755 .cicd/generate-base-images.sh create mode 100644 .cicd/helpers/docker-hash.sh create mode 100644 .cicd/helpers/general.sh create mode 100644 .cicd/metrics/test-metrics.js create mode 100644 .cicd/metrics/test-metrics.tar.gz create mode 100755 .cicd/package.sh create mode 100644 .cicd/pipeline.yml create mode 100644 .cicd/submodule-regression-checker.sh create mode 100755 .cicd/tests.sh create mode 100644 .travis.yml diff --git a/.cicd/base-images.yml b/.cicd/base-images.yml new file mode 100644 index 0000000000..198a253fed --- /dev/null +++ b/.cicd/base-images.yml @@ -0,0 +1,38 @@ +env: + BUILD_TIMEOUT: 120 + TEST_TIMEOUT: 60 + TIMEOUT: 120 + +steps: + + - label: ":aws: [Amazon] 2 Ensure Docker Image" + command: + - ".cicd/generate-base-images.sh amazonlinux-2" + agents: + queue: "automation-eos-dockerhub-image-builder-fleet" + timeout: $BUILD_TIMEOUT + skip: $SKIP_AMAZON_LINUX_2 + + - label: ":centos: [CentOS] 7 Ensure Docker Image" + command: + - ".cicd/generate-base-images.sh centos-7" + agents: + queue: "automation-eos-dockerhub-image-builder-fleet" + timeout: $BUILD_TIMEOUT + skip: $SKIP_CENTOS_7 + + - label: ":ubuntu: [Ubuntu] 16.04 Ensure Docker Image" + command: + - ".cicd/generate-base-images.sh ubuntu-16.04" + agents: + queue: "automation-eos-dockerhub-image-builder-fleet" + timeout: $BUILD_TIMEOUT + skip: $SKIP_UBUNTU_16 + + - label: ":ubuntu: [Ubuntu] 18.04 Ensure Docker Image" + command: + - ".cicd/generate-base-images.sh ubuntu-18.04" + agents: + queue: "automation-eos-dockerhub-image-builder-fleet" + timeout: $BUILD_TIMEOUT + skip: $SKIP_UBUNTU_18 diff --git a/.cicd/build.sh b/.cicd/build.sh new file mode 100755 index 0000000000..e17751af19 --- /dev/null +++ b/.cicd/build.sh @@ -0,0 +1,44 @@ +#!/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 + cmake .. + make -j$JOBS + +else # Linux + + ARGS=${ARGS:-"--rm --init -v $(pwd):$MOUNTED_DIR"} + + . $HELPERS_DIR/docker-hash.sh + + # PRE_COMMANDS: Executed pre-cmake + PRE_COMMANDS="cd $MOUNTED_DIR/build" + BUILD_COMMANDS="cmake .. && make -j$JOBS" + + # 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" + fi + + COMMANDS="$PRE_COMMANDS && $BUILD_COMMANDS" + + # 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 \ No newline at end of file diff --git a/.cicd/docker/amazonlinux-2.dockerfile b/.cicd/docker/amazonlinux-2.dockerfile new file mode 100644 index 0000000000..28a2f5a614 --- /dev/null +++ b/.cicd/docker/amazonlinux-2.dockerfile @@ -0,0 +1,21 @@ +FROM amazonlinux:2.0.20190508 +# install dependencies. +RUN yum update -y && \ + yum install -y git gcc.x86_64 gcc-c++.x86_64 autoconf automake libtool make bzip2 \ + bzip2-devel.x86_64 openssl-devel.x86_64 gmp-devel.x86_64 libstdc++.x86_64 \ + python.x86_64 python3-devel.x86_64 libedit-devel.x86_64 doxygen.x86_64 graphviz.x86_64 perl +# build lcov +RUN git clone https://github.com/linux-test-project/lcov.git && \ + 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 && \ + tar -xzf cmake-3.10.2.tar.gz && \ + cd cmake-3.10.2 && \ + ./bootstrap --prefix=/usr/local && \ + make -j$(nproc) && \ + make install && \ + cd .. && \ + rm -f cmake-3.10.2.tar.gz \ No newline at end of file diff --git a/.cicd/docker/centos-7.6.dockerfile b/.cicd/docker/centos-7.6.dockerfile new file mode 100644 index 0000000000..f7baa808a5 --- /dev/null +++ b/.cicd/docker/centos-7.6.dockerfile @@ -0,0 +1,28 @@ +FROM centos:7.6.1810 +# 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 \ + 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 +# 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 && \ + ./bootstrap --prefix=/usr/local && \ + make -j$(nproc) && \ + make install && \ + cd .. && \ + rm -f cmake-3.10.2.tar.gz \ No newline at end of file diff --git a/.cicd/docker/ubuntu-16.04.dockerfile b/.cicd/docker/ubuntu-16.04.dockerfile new file mode 100644 index 0000000000..0bda1a4138 --- /dev/null +++ b/.cicd/docker/ubuntu-16.04.dockerfile @@ -0,0 +1,16 @@ +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 \ + libgmp3-dev autotools-dev build-essential libicu-dev python2.7-dev \ + python3-dev autoconf libtool curl zlib1g-dev doxygen graphviz +# 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 && \ + cd cmake-3.10.2 && \ + ./bootstrap --prefix=/usr/local && \ + make -j$(nproc) && \ + make install && \ + cd .. && \ + rm -f cmake-3.10.2.tar.gz \ No newline at end of file diff --git a/.cicd/docker/ubuntu-18.04.dockerfile b/.cicd/docker/ubuntu-18.04.dockerfile new file mode 100644 index 0000000000..817636b8e2 --- /dev/null +++ b/.cicd/docker/ubuntu-18.04.dockerfile @@ -0,0 +1,7 @@ +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 diff --git a/.cicd/generate-base-images.sh b/.cicd/generate-base-images.sh new file mode 100755 index 0000000000..94abce495c --- /dev/null +++ b/.cicd/generate-base-images.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -eo pipefail +. ./.cicd/helpers/general.sh +. $HELPERS_DIR/docker-hash.sh +# look for Docker image +echo "+++ :mag_right: Looking for $FULL_TAG" +ORG_REPO=$(echo $FULL_TAG | cut -d: -f1) +TAG=$(echo $FULL_TAG | cut -d: -f2) +EXISTS=$(curl -s -H "Authorization: Bearer $(curl -sSL "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${ORG_REPO}:pull" | jq --raw-output .token)" "https://registry.hub.docker.com/v2/${ORG_REPO}/manifests/$TAG") +# build, if neccessary +if [[ $EXISTS =~ '404 page not found' || $EXISTS =~ 'manifest unknown' ]]; then # if we cannot pull the image, we build and push it first + docker build -t $FULL_TAG -f $CICD_DIR/docker/${IMAGE_TAG}.dockerfile . + docker push $FULL_TAG +else + echo "$FULL_TAG already exists." +fi \ No newline at end of file diff --git a/.cicd/helpers/docker-hash.sh b/.cicd/helpers/docker-hash.sh new file mode 100644 index 0000000000..3bbb644c06 --- /dev/null +++ b/.cicd/helpers/docker-hash.sh @@ -0,0 +1,24 @@ +export IMAGE_TAG=${IMAGE_TAG:-$1} + +function determine-hash() { + # determine the sha1 hash of all dockerfiles in the .cicd directory + [[ -z $1 ]] && echo "Please provide the files to be hashed (wildcards supported)" && exit 1 + echo "Obtaining Hash of files from $1..." + # collect all files, hash them, then hash those + HASHES=() + for FILE in $(find $1 -type f); do + HASH=$(sha1sum $FILE | sha1sum | awk '{ print $1 }') + HASHES=($HASH "${HASHES[*]}") + echo "$FILE - $HASH" + done + export DETERMINED_HASH=$(echo ${HASHES[*]} | sha1sum | awk '{ print $1 }') + export HASHED_IMAGE_TAG="${IMAGE_TAG}-${DETERMINED_HASH}" +} + +if [[ ! -z $IMAGE_TAG ]]; then + determine-hash "$CICD_DIR/docker/${IMAGE_TAG}.dockerfile" + export FULL_TAG="eosio/producer:eosio-cdt-$HASHED_IMAGE_TAG" +else + echo "Please set ENV::IMAGE_TAG to match the name of a platform dockerfile..." + exit 1 +fi \ No newline at end of file diff --git a/.cicd/helpers/general.sh b/.cicd/helpers/general.sh new file mode 100644 index 0000000000..42b041177a --- /dev/null +++ b/.cicd/helpers/general.sh @@ -0,0 +1,6 @@ +export ROOT_DIR=$( dirname "${BASH_SOURCE[0]}" )/../.. +export BUILD_DIR=$ROOT_DIR/build +export CICD_DIR=$ROOT_DIR/.cicd +export HELPERS_DIR=$CICD_DIR/helpers +export JOBS=${JOBS:-"$(getconf _NPROCESSORS_ONLN)"} +export MOUNTED_DIR='/workdir' diff --git a/.cicd/metrics/test-metrics.js b/.cicd/metrics/test-metrics.js new file mode 100644 index 0000000000..b995134d9b --- /dev/null +++ b/.cicd/metrics/test-metrics.js @@ -0,0 +1,431 @@ +#!/usr/bin/env node +/* includes */ +const execSync = require('child_process').execSync; // run shell commands +const fetch = require('node-fetch'); // downloading +const fs = require('fs'); // file stream +const XML = require('xml2js'); // parse xml + +/* globals */ +const buildkiteAccessToken = `?access_token=${process.env.BUILDKITE_API_KEY}`; // import buildkite access token from environment +const debug = (process.env.DEBUG === 'true') ? true : false; +let errorCount = 0; // count number of jobs which caused an error +const EXIT_SUCCESS = 0; +const inBuildkite = (process.env.BUILDKITE === 'true') ? true : false; +const outputFile = 'test-metrics.json'; +const pipelineWhitelist = // the pipelines for which we run diagnostics +[ + 'eosio', + 'eosio-base-images', + 'eosio-beta', + 'eosio-build-unpinned', + 'eosio-debug', + 'eosio-lrt', + 'eosio-security' +]; + +/* functions */ +// given a url string, download a text document +async function download(url) +{ + if (debug) console.log(`download(${url.replace(buildkiteAccessToken, '')})`); // DEBUG + const httpResponse = await fetch(url); + const body = await httpResponse.text(); + if (isNullOrEmpty(body)) + { + console.log(`ERROR: URL returned nothing! URL: ${url.replace(buildkiteAccessToken, '')}`); + const error = + { + http: { body, response: httpResponse, url}, + message: 'http body is null or empty', + origin: 'download()', + } + throw error; + } + if (debug) console.log('Download complete.'); // DEBUG + return body; +} + +// given a pipeline and a build number, get a build object +async function getBuild(pipeline, buildNumber) +{ + if (debug) console.log(`getBuild(${pipeline}, ${buildNumber})`); // DEBUG + const httpResponse = await fetch(`https://api.buildkite.com/v2/organizations/EOSIO/pipelines/${pipeline}/builds/${buildNumber}${buildkiteAccessToken}`); + return httpResponse.json(); +} + +// given a buildkite job, return the environmental variables +async function getEnvironment(job) +{ + if (debug) console.log('getEnvironment()'); // DEBUG + const httpResponse = await fetch(`${job.build_url}/jobs/${job.id}/env${buildkiteAccessToken}`); + const environment = await httpResponse.json(); + return environment.env; +} + +// given a string to search, a key as regex or a string, and optionally a start index, return the lowest line number containing the key +function getLineNumber(text, key, startIndex) +{ + if (debug) console.log('getLineNumber()'); // DEBUG + const begin = (isNullOrEmpty(startIndex) || !Number.isInteger(startIndex) || startIndex < 1) ? 0 : startIndex; + let found = false; + let lineNumber = 0; + const regex = (key instanceof RegExp); + text.split('\n').some((line) => + { + if (lineNumber >= begin && ((regex && key.test(line)) || (!regex && line.includes(key)))) + { + found = true; + return true; // c-style break + } + lineNumber += 1; + return false; // for the linter, plz delete when linter is fixed + }); + return (found) ? lineNumber : -1; +} + +// given a buildkite job, return a sanitized log file +async function getLog(job) +{ + if (debug) console.log(`getLog(${job.raw_log_url})`); // DEBUG + const logText = await download(job.raw_log_url + buildkiteAccessToken); + // returns log lowercase, with single spaces and '\n' only, and only ascii-printable characters + return sanitize(logText); // made this a separate function for unit testing purposes +} + +// given a Buildkite environment, return the operating system used +function getOS(environment) +{ + if (debug) console.log(`getOS(${environment.BUILDKITE_LABEL})`); // DEBUG + if (isNullOrEmpty(environment) || isNullOrEmpty(environment.BUILDKITE_LABEL)) + { + console.log('ERROR: getOS() called with empty environment.BUILDKITE_LABEL!'); + console.log(JSON.stringify(environment)); + return null; + } + const label = environment.BUILDKITE_LABEL.toLowerCase(); + if ((/aws(?!.*[23])/.test(label) || /amazon(?!.*[23])/.test(label))) + return 'Amazon Linux 1'; + if (/aws.*2/.test(label) || /amazon.*2/.test(label)) + return 'Amazon Linux 2'; + if (/centos(?!.*[89])/.test(label)) + return 'CentOS 7'; + if (/fedora(?!.*2[89])/.test(label) && /fedora(?!.*3\d)/.test(label)) + return 'Fedora 27'; + if (/high.*sierra/.test(label)) + return 'High Sierra'; + if (/mojave/.test(label)) + return 'Mojave'; + if (/ubuntu.*16.*04/.test(label) || /ubuntu.*16(?!.*10)/.test(label)) + return 'Ubuntu 16.04'; + if (/ubuntu.*18.*04/.test(label) || /ubuntu.*18(?!.*10)/.test(label)) + return 'Ubuntu 18.04'; + if (/docker/.test(label)) + return 'Docker'; + return 'Unknown'; +} + +// given a Buildkite job, return the test-results.xml file as JSON +async function getXML(job) +{ + if (debug) console.log('getXML()'); // DEBUG + const xmlFilename = 'test-results.xml'; + const artifacts = await download(job.artifacts_url + buildkiteAccessToken); + const testResultsArtifact = JSON.parse(artifacts).filter(artifact => artifact.filename === xmlFilename); + if (isNullOrEmpty(testResultsArtifact)) + { + console.log(`WARNING: No ${xmlFilename} found for "${job.name}"! Link: ${job.web_url}`); + return null; + } + const urlBuildkite = testResultsArtifact[0].download_url; + const rawXML = await download(urlBuildkite + buildkiteAccessToken); + const xmlOptions = + { + attrNameProcessors: [function lower(name) { return name.toLowerCase(); }], + explicitArray: false, // do not put single strings in single-element arrays + mergeAttrs: true, // make attributes children of their node + normalizeTags: true, // convert all tag names to lowercase + }; + let xmlError, xmlTestResults; + await XML.parseString(rawXML, xmlOptions, (err, result) => {xmlTestResults = result; xmlError = err;}); + if (isNullOrEmpty(xmlError)) + return xmlTestResults; + console.log(`WARNING: Failed to parse xml for "${job.name}" job! Link: ${job.web_url}`); + console.log(JSON.stringify(xmlError)); + return null; +} + +// test if variable is empty +function isNullOrEmpty(str) +{ + return (str === null || str === undefined || str.length === 0 || /^\s*$/.test(str)); +} + +// return array of test results from a buildkite job log +function parseLog(logText) +{ + if (debug) console.log('parseLog()'); // DEBUG + const lines = logText.split('\n'); + const resultLines = lines.filter(line => /test\s+#\d+/.test(line)); // 'grep' for the test result lines + // parse the strings and make test records + return resultLines.map((line) => + { + const y = line.trim().split(/test\s+#\d+/).pop(); // remove everything before the test declaration + const parts = y.split(/\s+/).slice(1, -1); // split the line and remove the test number and time unit + const testName = parts[0]; + const testTime = parts[(parts.length - 1)]; + const rawResult = parts.slice(1, -1).join(); + let testResult; + if (rawResult.includes('failed')) + testResult = 'Failed'; + else if (rawResult.includes('passed')) + testResult = 'Passed'; + else + testResult = 'Exception'; + return { testName, testResult, testTime }; // create a test record + }); +} + +// return array of test results from an xUnit-formatted JSON object +function parseXunit(xUnit) +{ + if (debug) console.log('parseXunit()'); // DEBUG + if (isNullOrEmpty(xUnit)) + { + console.log('WARNING: xUnit is empty!'); + return null; + } + return xUnit.site.testing.test.map((test) => + { + const testName = test.name; + const testTime = test.results.namedmeasurement.filter(x => /execution\s+time/.test(x.name.toLowerCase()))[0].value; + let testResult; + if (test.status.includes('failed')) + testResult = 'Failed'; + else if (test.status.includes('passed')) + testResult = 'Passed'; + else + testResult = 'Exception'; + return { testName, testResult, testTime }; + }); +} + +// returns text lowercase, with single spaces and '\n' only, and only ascii-printable characters +function sanitize(text) +{ + if (debug) console.log(`sanitize(text) where text.length = ${text.length} bytes`); // DEBUG + const chunkSize = 131072; // process text in 128 kB chunks + if (text.length > chunkSize) + return sanitize(text.slice(0, chunkSize)).concat(sanitize(text.slice(chunkSize))); + return text + .replace(/(?!\n)\r(?!\n)/g, '\n').replace(/\r/g, '') // convert all line endings to '\n' + .replace(/[^\S\n]+/g, ' ') // convert all whitespace to ' ' + .replace(/[^ -~\n]+/g, '') // remove non-printable characters + .toLowerCase(); +} + +// input is array of whole lines containing "test #" and ("failed" or "exception") +function testDiagnostics(test, logText) +{ + if (debug) + { + console.log(`testDiagnostics(test, logText) where logText.length = ${logText.length} bytes and test is`); // DEBUG + console.log(JSON.stringify(test)); + } + // get basic information + const testResultLine = new RegExp(`test\\s+#\\d+.*${test.testName}`, 'g'); // regex defining "test #" line + const startIndex = getLineNumber(logText, testResultLine); + const output = { errorMsg: null, lineNumber: startIndex + 1, stackTrace: null }; // default output + // filter tests + if (test.testResult.toLowerCase() === 'passed') + return output; + output.errorMsg = 'test diangostics are not enabled for this pipeline'; + if (!pipelineWhitelist.includes(test.pipeline)) + return output; + // diagnostics + if (debug) console.log('Running diagnostics...'); // DEBUG + output.errorMsg = 'uncategorized'; + const testLog = logText.split(testResultLine)[1].split(/test\s*#/)[0].split('\n'); // get log output from this test only, as array of lines + let errorLine = testLog[0]; // first line, from "test ## name" to '\n' exclusive + if (/\.+ *\** *not run\s+0+\.0+ sec$/.test(errorLine)) // not run + output.errorMsg = 'test not run'; + else if (/\.+ *\** *time *out\s+\d+\.\d+ sec$/.test(errorLine)) // timeout + output.errorMsg = 'test timeout'; + else if (/exception/.test(errorLine)) // test exception + output.errorMsg = errorLine.split('exception')[1].replace(/[: \d.]/g, '').replace(/sec$/, ''); // isolate the error message after exception + else if (/fc::.*exception/.test(testLog.filter(line => !isNullOrEmpty(line))[1])) // fc exception + { + [, errorLine] = testLog.filter(line => !isNullOrEmpty(line)); // get first line + output.errorMsg = `fc::${errorLine.split('::')[1].replace(/['",]/g, '').split(' ')[0]}`; // isolate fx exception body + } + else if (testLog.join('\n').includes('ctest:')) // ctest exception + { + [errorLine] = testLog.filter(line => line.includes('ctest:')); + output.errorMsg = `ctest:${errorLine.split('ctest:')[1]}`; + } + else if (!isNullOrEmpty(testLog.filter(line => /boost.+exception/.test(line)))) // boost exception + { + [errorLine] = testLog.filter(line => /boost.+exception/.test(line)); + output.errorMsg = `boost: ${errorLine.replace(/[()]/g, '').split(/: (.+)/)[1]}`; // capturing parenthesis, split only at first ' :' + output.stackTrace = testLog.filter(line => /thread-\d+/.test(line))[0].split('thread-')[1].replace(/^\d+/, '').trim().replace(/[[]\d+m$/, ''); // get the bottom of the stack trace + } + else if (/unit[-_. ]+test/.test(test.testName) || /plugin[-_. ]+test/.test(test.testName)) // unit test, application exception + { + if (!isNullOrEmpty(testLog.filter(line => line.includes('exception: ')))) + { + [errorLine] = testLog.filter(line => line.includes('exception: ')); + [, output.errorMsg] = errorLine.replace(/[()]/g, '').split(/: (.+)/); // capturing parenthesis, split only at first ' :' + output.stackTrace = testLog.filter(line => /thread-\d+/.test(line))[0].split('thread-')[1].replace(/^\d+/, '').trim().replace(/[[]\d+m$/, ''); // get the bottom of the stack trace + } + // else uncategorized unit test + } + // else integration test, add cross-referencing code here (or uncategorized) + if (errorLine !== testLog[0]) // get real line number from log file + output.lineNumber = getLineNumber(logText, errorLine, startIndex) + 1; + return output; +} + +// return test metrics given a buildkite job or build +async function testMetrics(buildkiteObject) +{ + if (!isNullOrEmpty(buildkiteObject.type)) // input is a Buildkite job object + { + const job = buildkiteObject; + console.log(`Processing test metrics for "${job.name}"${(inBuildkite) ? '' : ` at ${job.web_url}`}...`); + if (isNullOrEmpty(job.exit_status)) + { + console.log(`${(inBuildkite) ? '+++ :warning: ' : ''}WARNING: "${job.name}" was skipped!`); + return null; + } + // get test results + const logText = await getLog(job); + let testResults; + let xUnit; + try + { + xUnit = await getXML(job); + testResults = parseXunit(xUnit); + } + catch (error) + { + console.log(`XML processing failed for "${job.name}"! Link: ${job.web_url}`); + console.log(JSON.stringify(error)); + testResults = null; + } + finally + { + if (isNullOrEmpty(testResults)) + testResults = parseLog(logText); + } + // get test metrics + const env = await getEnvironment(job); + env.BUILDKITE_REPO = env.BUILDKITE_REPO.replace(new RegExp('^git@github.com:(EOSIO/)?'), '').replace(new RegExp('.git$'), ''); + const metrics = []; + const os = getOS(env); + testResults.forEach((result) => + { + // add test properties + const test = + { + ...result, // add testName, testResult, testTime + agentName: env.BUILDKITE_AGENT_NAME, + agentRole: env.BUILDKITE_AGENT_META_DATA_QUEUE || env.BUILDKITE_AGENT_META_DATA_ROLE, + branch: env.BUILDKITE_BRANCH, + buildNumber: env.BUILDKITE_BUILD_NUMBER, + commit: env.BUILDKITE_COMMIT, + job: env.BUILDKITE_LABEL, + os, + pipeline: env.BUILDKITE_PIPELINE_SLUG, + repo: env.BUILDKITE_REPO, + testTime: parseFloat(result.testTime), + url: job.web_url, + }; + metrics.push({ ...test, ...testDiagnostics(test, logText) }); + }); + return metrics; + } + else if (!isNullOrEmpty(buildkiteObject.number)) // input is a Buildkite build object + { + const build = buildkiteObject; + console.log(`Processing test metrics for ${build.pipeline.slug} build ${build.number}${(inBuildkite) ? '' : ` at ${build.web_url}`}...`); + let metrics = [], promises = []; + // process test metrics + build.jobs.filter(job => job.type === 'script' && /test/.test(job.name.toLowerCase()) && ! /test metrics/.test(job.name.toLowerCase())).forEach((job) => + { + promises.push( + testMetrics(job) + .then((moreMetrics) => { + if (!isNullOrEmpty(moreMetrics)) + metrics = metrics.concat(moreMetrics); + else + console.log(`${(inBuildkite) ? '+++ :warning: ' : ''}WARNING: "${job.name}" metrics are empty!\nmetrics = ${JSON.stringify(moreMetrics)}`); + }).catch((error) => { + console.log(`${(inBuildkite) ? '+++ :no_entry: ' : ''}ERROR: Failed to process test metrics for "${job.name}"! Link: ${job.web_url}`); + console.log(JSON.stringify(error)); + errorCount++; + }) + ); + }); + await Promise.all(promises); + return metrics; + } + else // something else + { + console.log(`${(inBuildkite) ? '+++ :no_entry: ' : ''}ERROR: Buildkite object not recognized or not a test step!`); + console.log(JSON.stringify({buildkiteObject})); + return null; + } +} + +/* main */ +async function main() +{ + if (debug) console.log(`$ ${process.argv.join(' ')}`); + let build, metrics = null; + console.log(`${(inBuildkite) ? '+++ :evergreen_tree: ' : ''}Getting information from enviroment...`); + const buildNumber = process.env.BUILDKITE_BUILD_NUMBER || process.argv[2]; + const pipeline = process.env.BUILDKITE_PIPELINE_SLUG || process.argv[3]; + if (debug) + { + console.log(`BUILDKITE=${process.env.BUILDKITE}`); + console.log(`BUILDKITE_BUILD_NUMBER=${process.env.BUILDKITE_BUILD_NUMBER}`); + console.log(`BUILDKITE_PIPELINE_SLUG=${process.env.BUILDKITE_PIPELINE_SLUG}`); + console.log(' State:') + console.log(`inBuildkite = "${inBuildkite}"`); + console.log(`buildNumber = "${buildNumber}"`); + console.log(`pipeline = "${pipeline}"`); + } + if (isNullOrEmpty(buildNumber) || isNullOrEmpty(pipeline) || isNullOrEmpty(process.env.BUILDKITE_API_KEY)) + { + console.log(`${(inBuildkite) ? '+++ :no_entry: ' : ''}ERROR: Missing required inputs!`); + if (isNullOrEmpty(process.env.BUILDKITE_API_KEY)) console.log('- Buildkite API key, as BUILDKITE_API_KEY environment variable'); + if (isNullOrEmpty(buildNumber)) console.log('- Build Number, as BUILDKITE_BUILD_NUMBER or argument 1'); + if (isNullOrEmpty(pipeline)) console.log('- Pipeline Slug, as BUILDKITE_PIPELINE_SLUG or argument 2'); + errorCount = -1; + } + else + { + console.log(`${(inBuildkite) ? '+++ :bar_chart: ' : ''}Processing test metrics...`); + build = await getBuild(pipeline, buildNumber); + metrics = await testMetrics(build); + console.log('Done processing test metrics.'); + } + console.log(`${(inBuildkite) ? '+++ :pencil: ' : ''}Writing to file...`); + fs.writeFileSync(outputFile, JSON.stringify({ metrics })); + console.log(`Saved metrics to "${outputFile}" in "${process.cwd()}".`); + if (inBuildkite) + { + console.log('+++ :arrow_up: Uploading artifact...'); + execSync(`buildkite-agent artifact upload ${outputFile}`); + } + if (errorCount === 0) + console.log(`${(inBuildkite) ? '+++ :white_check_mark: ' : ''}Done!`); + else + { + console.log(`${(inBuildkite) ? '+++ :warning: ' : ''}Finished with errors.`); + console.log(`Please send automation a link to this job${(isNullOrEmpty(build)) ? '.' : `: ${build.web_url}`}`); + console.log('@kj4ezj or @zreyn on Telegram'); + } + return (inBuildkite) ? process.exit(EXIT_SUCCESS) : process.exit(errorCount); +}; + +main(); \ No newline at end of file diff --git a/.cicd/metrics/test-metrics.tar.gz b/.cicd/metrics/test-metrics.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..2381787ca06196f1b95c636435a77b58ba1cc0aa GIT binary patch literal 96551 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 /dev/null +git submodule update --init &> /dev/null +while read -r a b; do + BASE_MAP[$a]=$b +done < <(git submodule --quiet foreach --recursive 'echo $path `git log -1 --format=%ct`') + +for k in "${!BASE_MAP[@]}"; do + base_ts=${BASE_MAP[$k]} + pr_ts=${PR_MAP[$k]} + echo "submodule $k" + echo " timestamp on $CURRENT_BRANCH: $pr_ts" + echo " timestamp on $BASE_BRANCH: $base_ts" + if (( $pr_ts < $base_ts)); then + echo "$k is older on $CURRENT_BRANCH than $BASE_BRANCH; investigating..." + + if for c in `git log $CURRENT_BRANCH ^$BASE_BRANCH --pretty=format:"%H"`; do git show --pretty="" --name-only $c; done | grep -q "^$k$"; then + echo "ERROR: $k has regressed" + exit 1 + else + echo "$k was not in the diff; no regression detected" + fi + fi +done diff --git a/.cicd/tests.sh b/.cicd/tests.sh new file mode 100755 index 0000000000..e0a68e452f --- /dev/null +++ b/.cicd/tests.sh @@ -0,0 +1,35 @@ +#!/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 unit_tests -V -T Test" +COMMANDS="$PRE_COMMANDS && $TEST" + +if [[ $(uname) == 'Darwin' ]]; then + + # You can't use chained commands in execute + cd $BUILD_DIR + bash -c "$TEST" + +else # Linux + + ARGS=${ARGS:-"--rm --init -v $(pwd):$MOUNTED_DIR"} + + . $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="" + 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 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..8858c04709 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,27 @@ +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" From b91b5fc896d0ae1cfce6222a622140ca83627361 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Sun, 25 Aug 2019 07:35:51 +0000 Subject: [PATCH 40/99] Fix build fail when struct inherits from std types `add_struct` enforces parsing its base as struct, and it causes segfault when inheriting from the std types. This patch mitigates this issue by allowing add_struct to call add_type for its base, but generated ABI is still not usable, because chain::abi_serializer requires struct base to be struct type. There is a workaround fix making it work by trick, but ultimately it needs consensus upgrade. https://github.com/EOSIO/eosio.cdt/issues/541 --- tools/include/eosio/abigen.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/eosio/abigen.hpp b/tools/include/eosio/abigen.hpp index 3090a92db1..6c14b3cbe0 100644 --- a/tools/include/eosio/abigen.hpp +++ b/tools/include/eosio/abigen.hpp @@ -159,7 +159,7 @@ namespace eosio { namespace cdt { abi_struct ret; if ( decl->getNumBases() == 1 ) { ret.base = get_type(decl->bases_begin()->getType()); - add_struct(decl->bases_begin()->getType().getTypePtr()->getAsCXXRecordDecl()); + add_type(decl->bases_begin()->getType()); } std::string sub_name = ""; for ( auto field : decl->fields() ) { From dffdbbd2aa7e8a1fa884789667cdcf348f5f6d0a Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Sun, 25 Aug 2019 07:58:54 +0000 Subject: [PATCH 41/99] Remove unused variable --- tools/include/eosio/abigen.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/include/eosio/abigen.hpp b/tools/include/eosio/abigen.hpp index 6c14b3cbe0..6032cf5074 100644 --- a/tools/include/eosio/abigen.hpp +++ b/tools/include/eosio/abigen.hpp @@ -161,7 +161,6 @@ namespace eosio { namespace cdt { ret.base = get_type(decl->bases_begin()->getType()); add_type(decl->bases_begin()->getType()); } - std::string sub_name = ""; for ( auto field : decl->fields() ) { if ( field->getName() == "transaction_extensions") { abi_struct ext; @@ -173,7 +172,6 @@ namespace eosio { namespace cdt { } else { ret.fields.push_back({field->getName().str(), get_type(field->getType())}); - sub_name += "_" + get_type(field->getType()); add_type(field->getType()); } } From 174424c7eba726a3227b6c4e165ffbb3f839a266 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Sun, 25 Aug 2019 09:55:43 +0000 Subject: [PATCH 42/99] Fix missing ABI when struct base is typedef'ed abigen removes unused defs from abi json, but when struct base type is aliased type (typedef, using), its type is excluded from json generation unexpectedly. https://github.com/EOSIO/eosio.cdt/issues/601 --- tools/include/eosio/abigen.hpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tools/include/eosio/abigen.hpp b/tools/include/eosio/abigen.hpp index 6032cf5074..b7bea3715f 100644 --- a/tools/include/eosio/abigen.hpp +++ b/tools/include/eosio/abigen.hpp @@ -384,6 +384,14 @@ namespace eosio { namespace cdt { set_of_tables.insert(t); } + std::function get_root_name; + get_root_name = [&] (const std::string& name) { + for (auto td : _abi.typedefs) + if (remove_suffix(name) == td.new_type_name) + return get_root_name(td.type); + return name; + }; + auto validate_struct = [&]( abi_struct as ) { if ( is_builtin_type(_translate_type(as.name)) ) return false; @@ -398,7 +406,7 @@ namespace eosio { namespace cdt { return true; } } - if (s.base == as.name) + if (get_root_name(s.base) == as.name) return true; } for ( auto a : _abi.actions ) { @@ -418,10 +426,13 @@ namespace eosio { namespace cdt { auto validate_types = [&]( abi_typedef td ) { for ( auto as : _abi.structs ) - if (validate_struct(as)) + if (validate_struct(as)) { for ( auto f : as.fields ) if ( remove_suffix(f.type) == td.new_type_name ) return true; + if (as.base == td.new_type_name) + return true; + } for ( auto t : _abi.tables ) if ( t.type == td.new_type_name ) return true; From 22ce195386000c95abac0323fc5c0acacc3f87df Mon Sep 17 00:00:00 2001 From: Scott Arnette Date: Sun, 25 Aug 2019 07:55:06 -0400 Subject: [PATCH 43/99] Enable devtoolset7 on Centos. --- .cicd/build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.cicd/build.sh b/.cicd/build.sh index e17751af19..0268ae9e6a 100755 --- a/.cicd/build.sh +++ b/.cicd/build.sh @@ -21,6 +21,7 @@ else # Linux PRE_COMMANDS="cd $MOUNTED_DIR/build" BUILD_COMMANDS="cmake .. && make -j$JOBS" + [[ $IMAGE_TAG == 'centos-7.6' ]] && PRE_COMMANDS="$PRE_COMMANDS && source /opt/rh/devtoolset-7/enable" # Docker Commands if [[ $BUILDKITE == true ]]; then # Generate Base Images From 50a30b8160edf2ade70492133a417eb50e6b841e Mon Sep 17 00:00:00 2001 From: Scott Arnette Date: Mon, 26 Aug 2019 14:32:55 -0400 Subject: [PATCH 44/99] Removed old pipeline file. --- .cicd/base-images.yml | 38 -------------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 .cicd/base-images.yml diff --git a/.cicd/base-images.yml b/.cicd/base-images.yml deleted file mode 100644 index 198a253fed..0000000000 --- a/.cicd/base-images.yml +++ /dev/null @@ -1,38 +0,0 @@ -env: - BUILD_TIMEOUT: 120 - TEST_TIMEOUT: 60 - TIMEOUT: 120 - -steps: - - - label: ":aws: [Amazon] 2 Ensure Docker Image" - command: - - ".cicd/generate-base-images.sh amazonlinux-2" - agents: - queue: "automation-eos-dockerhub-image-builder-fleet" - timeout: $BUILD_TIMEOUT - skip: $SKIP_AMAZON_LINUX_2 - - - label: ":centos: [CentOS] 7 Ensure Docker Image" - command: - - ".cicd/generate-base-images.sh centos-7" - agents: - queue: "automation-eos-dockerhub-image-builder-fleet" - timeout: $BUILD_TIMEOUT - skip: $SKIP_CENTOS_7 - - - label: ":ubuntu: [Ubuntu] 16.04 Ensure Docker Image" - command: - - ".cicd/generate-base-images.sh ubuntu-16.04" - agents: - queue: "automation-eos-dockerhub-image-builder-fleet" - timeout: $BUILD_TIMEOUT - skip: $SKIP_UBUNTU_16 - - - label: ":ubuntu: [Ubuntu] 18.04 Ensure Docker Image" - command: - - ".cicd/generate-base-images.sh ubuntu-18.04" - agents: - queue: "automation-eos-dockerhub-image-builder-fleet" - timeout: $BUILD_TIMEOUT - skip: $SKIP_UBUNTU_18 From ccf9e03933c28eb90d0d1ee5cef5aaf1f9670f1e Mon Sep 17 00:00:00 2001 From: Zach Butler Date: Mon, 26 Aug 2019 14:33:20 -0400 Subject: [PATCH 45/99] Remove base-images.yml --- .cicd/base-images.yml | 38 -------------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 .cicd/base-images.yml diff --git a/.cicd/base-images.yml b/.cicd/base-images.yml deleted file mode 100644 index 198a253fed..0000000000 --- a/.cicd/base-images.yml +++ /dev/null @@ -1,38 +0,0 @@ -env: - BUILD_TIMEOUT: 120 - TEST_TIMEOUT: 60 - TIMEOUT: 120 - -steps: - - - label: ":aws: [Amazon] 2 Ensure Docker Image" - command: - - ".cicd/generate-base-images.sh amazonlinux-2" - agents: - queue: "automation-eos-dockerhub-image-builder-fleet" - timeout: $BUILD_TIMEOUT - skip: $SKIP_AMAZON_LINUX_2 - - - label: ":centos: [CentOS] 7 Ensure Docker Image" - command: - - ".cicd/generate-base-images.sh centos-7" - agents: - queue: "automation-eos-dockerhub-image-builder-fleet" - timeout: $BUILD_TIMEOUT - skip: $SKIP_CENTOS_7 - - - label: ":ubuntu: [Ubuntu] 16.04 Ensure Docker Image" - command: - - ".cicd/generate-base-images.sh ubuntu-16.04" - agents: - queue: "automation-eos-dockerhub-image-builder-fleet" - timeout: $BUILD_TIMEOUT - skip: $SKIP_UBUNTU_16 - - - label: ":ubuntu: [Ubuntu] 18.04 Ensure Docker Image" - command: - - ".cicd/generate-base-images.sh ubuntu-18.04" - agents: - queue: "automation-eos-dockerhub-image-builder-fleet" - timeout: $BUILD_TIMEOUT - skip: $SKIP_UBUNTU_18 From 19c25a978ce4abb2469f6b9f4b3d5f734ff7dc99 Mon Sep 17 00:00:00 2001 From: arhag Date: Tue, 27 Aug 2019 14:43:52 -0400 Subject: [PATCH 46/99] add IMPORTANT and CONTRIBUTING files and modify README and LICENSE --- CONTRIBUTING.md | 148 ++++++++++++++++++++++++++++++++++++++++++++++++ IMPORTANT | 27 +++++++++ LICENSE | 2 +- README.md | 18 +++++- 4 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 IMPORTANT diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..9a13653c33 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,148 @@ +# Contributing to eosio.cdt + +Interested in contributing? That's awesome! Here are some guidelines to get started quickly and easily: + +- [Reporting An Issue](#reporting-an-issue) + - [Bug Reports](#bug-reports) + - [Feature Requests](#feature-requests) + - [Change Requests](#change-requests) +- [Working on eosio.cdt](#working-on-eosiocdt) + - [Feature Branches](#feature-branches) + - [Submitting Pull Requests](#submitting-pull-requests) + - [Testing and Quality Assurance](#testing-and-quality-assurance) +- [Conduct](#conduct) +- [Contributor License & Acknowledgments](#contributor-license--acknowledgments) +- [References](#references) + +## Reporting An Issue + +If you're about to raise an issue because you think you've found a problem with eosio.cdt, or you'd like to make a request for a new feature in the codebase, or any other reason… please read this first. + +The GitHub issue tracker is the preferred channel for [bug reports](#bug-reports), [feature requests](#feature-requests), and [submitting pull requests](#submitting-pull-requests), but please respect the following restrictions: + +* Please **search for existing issues**. Help us keep duplicate issues to a minimum by checking to see if someone has already reported your problem or requested your idea. + +* Please **be civil**. Keep the discussion on topic and respect the opinions of others. See also our [Contributor Code of Conduct](#conduct). + +### Bug Reports + +A bug is a _demonstrable problem_ that is caused by the code in the repository. Good bug reports are extremely helpful - thank you! + +Guidelines for bug reports: + +1. **Use the GitHub issue search** — check if the issue has already been + reported. + +1. **Check if the issue has been fixed** — look for [closed issues in the + current milestone](https://github.com/EOSIO/eosio.cdt/issues?q=is%3Aissue+is%3Aclosed) or try to reproduce it + using the latest `develop` branch. + +A good bug report shouldn't leave others needing to chase you up for more information. Be sure to include the details of your environment and relevant tests that demonstrate the failure. + +[Report a bug](https://github.com/EOSIO/eosio.cdt/issues/new?title=Bug%3A) + +### Feature Requests + +Feature requests are welcome. Before you submit one be sure to have: + +1. **Use the GitHub search** and check the feature hasn't already been requested. +1. Take a moment to think about whether your idea fits with the scope and aims of the project. +1. Remember, it's up to *you* to make a strong case to convince the project's leaders of the merits of this feature. Please provide as much detail and context as possible, this means explaining the use case and why it is likely to be common. + +### Change Requests + +Change requests cover both architectural and functional changes to how eosio.cdt works. If you have an idea for a new or different dependency, a refactor, or an improvement to a feature, etc - please be sure to: + +1. **Use the GitHub search** and check someone else didn't get there first +1. Take a moment to think about the best way to make a case for, and explain what you're thinking. Are you sure this shouldn't really be + a [bug report](#bug-reports) or a [feature request](#feature-requests)? Is it really one idea or is it many? What's the context? What problem are you solving? Why is what you are suggesting better than what's already there? + +## Working on eosio.cdt + +Code contributions are welcome and encouraged! If you are looking for a good place to start, check out the [good first issue](https://github.com/EOSIO/eosio.cdt/labels/good%20first%20issue) label in GitHub issues. + +Also, please follow these guidelines when submitting code: + +### Feature Branches + +To get it out of the way: + +- **[develop](https://github.com/EOSIO/eosio.cdt/tree/develop)** is the development branch. All work on the next release happens here so you should generally branch off `develop`. Do **NOT** use this branch for a production site. +- **[master](https://github.com/EOSIO/eosio.cdt/tree/master)** contains the latest release of eosio.cdt. This branch may be used in production. Do **NOT** use this branch to work on eosio.cdt's source. + +### Submitting Pull Requests + +Pull requests are awesome. If you're looking to raise a PR for something which doesn't have an open issue, please think carefully about [raising an issue](#reporting-an-issue) which your PR can close, especially if you're fixing a bug. This makes it more likely that there will be enough information available for your PR to be properly tested and merged. + +### Testing and Quality Assurance + +Never underestimate just how useful quality assurance is. If you're looking to get involved with the code base and don't know where to start, checking out and testing a pull request is one of the most useful things you could do. + +Essentially, [check out the latest develop branch](#working-on-eosio.cdt), take it for a spin, and if you find anything odd, please follow the [bug report guidelines](#bug-reports) and let us know! + +## Conduct + +While contributing, please be respectful and constructive, so that participation in our project is a positive experience for everyone. + +Examples of behavior that contributes to creating a positive environment include: +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior include: +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others’ private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Contributor License & Acknowledgments + +Whenever you make a contribution to this project, you license your contribution under the same terms as set out in LICENSE, and you represent and warrant that you have the right to license your contribution under those terms. Whenever you make a contribution to this project, you also certify in the terms of the Developer’s Certificate of Origin set out below: + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +## References + +* Overall CONTRIB adapted from https://github.com/mathjax/MathJax/blob/master/CONTRIBUTING.md +* Conduct section adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html diff --git a/IMPORTANT b/IMPORTANT new file mode 100644 index 0000000000..ed433799c6 --- /dev/null +++ b/IMPORTANT @@ -0,0 +1,27 @@ +# Important Notice + +We (block.one and its affiliates) make available EOSIO and other software, updates, patches and documentation (collectively, Software) on a voluntary basis as a member of the EOSIO community. A condition of you accessing any Software, websites, articles, media, publications, documents or other material (collectively, Material) is your acceptance of the terms of this important notice. + +## Software +We are not responsible for ensuring the overall performance of Software or any related applications. Any test results or performance figures are indicative and will not reflect performance under all conditions. Software may contain components that are open sourced and subject to their own licenses; you are responsible for ensuring your compliance with those licenses. + +We make no representation, warranty, guarantee or undertaking in respect of Software, whether expressed or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall we be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software. + +Wallets and related components are complex software that require the highest levels of security. If incorrectly built or used, they may compromise users’ private keys and digital assets. Wallet applications and related components should undergo thorough security evaluations before being used. Only experienced developers should work with such Software. + +Material is not made available to any person or entity that is the subject of sanctions administered or enforced by any country or government or otherwise designated on any list of prohibited or restricted parties (including but not limited to the lists maintained by the United Nations Security Council, the U.S. Government, the European Union or its Member States, or other applicable government authority) or organized or resident in a country or territory that is the subject of country-wide or territory-wide sanctions. You represent and warrant that neither you nor any party having a direct or indirect beneficial interest in you or on whose behalf you are acting as agent or nominee is such a person or entity and you will comply with all applicable import, re-import, sanctions, anti-boycott, export, and re-export control laws and regulations. If this is not accurate or you do not agree, then you must immediately cease accessing our Material and delete all copies of Software. + +Any person using or offering Software in connection with providing software, goods or services to third parties shall advise such third parties of this important notice, including all limitations, restrictions and exclusions of liability. + +## Trademarks +Block.one, EOSIO, EOS, the heptahedron and associated logos and related marks are our trademarks. Other trademarks referenced in Material are the property of their respective owners. + +## Third parties +Any reference in Material to any third party or third-party product, resource or service is not an endorsement or recommendation by Block.one. We are not responsible for, and disclaim any and all responsibility and liability for, your use of or reliance on any of these resources. Third-party resources may be updated, changed or terminated at any time, so information in Material may be out of date or inaccurate. + +## Forward-looking statements +Please note that in making statements expressing Block.one’s vision, we do not guarantee anything, and all aspects of our vision are subject to change at any time and in all respects at Block.one’s sole discretion, with or without notice. We call these “forward-looking statements”, which includes statements on our website and in other Material, other than statements of historical facts, such as statements regarding EOSIO’s development, expected performance, and future features, or our business strategy, plans, prospects, developments and objectives. These statements are only predictions and reflect Block.one’s current beliefs and expectations with respect to future events; they are based on assumptions and are subject to risk, uncertainties and change at any time. + +We operate in a rapidly changing environment and new risks emerge from time to time. Given these risks and uncertainties, you are cautioned not to rely on these forward-looking statements. Actual results, performance or events may differ materially from what is predicted in the forward-looking statements. Some of the factors that could cause actual results, performance or events to differ materially from the forward-looking statements include, without limitation: technical feasibility and barriers; market trends and volatility; continued availability of capital, financing and personnel; product acceptance; the commercial success of any new products or technologies; competition; government regulation and laws; and general economic, market or business conditions. + +All statements are valid only as of the date of first posting and Block.one is under no obligation to, and expressly disclaims any obligation to, update or alter any statements, whether as a result of new information, subsequent events or otherwise. Nothing in any Material constitutes technological, financial, investment, legal or other advice, either in general or with regard to any particular situation or implementation. Please consult with experts in appropriate areas before implementing or utilizing anything contained in Material. diff --git a/LICENSE b/LICENSE index 1516b96cbd..22d36d65db 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019 Respective Authors all rights reserved. +Copyright (c) 2017-2019 block.one and its contributors. All rights reserved. The MIT License diff --git a/README.md b/README.md index 8e166c3190..94d392157b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ EOSIO.CDT is a toolchain for WebAssembly (WASM) and set of tools to facilitate contract writing 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 not available or incomplete. -## Important! +### Attention EOSIO.CDT Version 1.3.x introduced quite a few breaking changes. To have binary releases we needed to remove the concept of a core symbol from EOSIO.CDT. This meant drastic changes to symbol, asset and other types/functions that were connected to them. Since these changes would be disruptive, we decided to add as many disruptive changes needed for future contract writing, so that disruption should only occur once. Please read the **_Differences between Version 1.2.x and Version 1.3.x_** section of this readme. ### Binary Releases @@ -73,3 +73,19 @@ $ sudo ./install.sh * eosio-ar * eosio-objdump * eosio-readelf + +## Contributing + +[Contributing Guide](./CONTRIBUTING.md) + +[Code of Conduct](./CONTRIBUTING.md#conduct) + +## License + +[MIT](./LICENSE) + +## Important + +See [LICENSE](./LICENSE) for copyright and license terms. + +All repositories and other materials are provided subject to the terms of this [IMPORTANT](./IMPORTANT) notice and you must familiarize yourself with its terms. The notice contains important information, limitations and restrictions relating to our software, publications, trademarks, third-party resources, and forward-looking statements. By accessing any of our repositories and other materials, you accept and agree to the terms of the notice. From faa33f18ce7808f995a41f6bb1a14948b438ad4e Mon Sep 17 00:00:00 2001 From: arhag Date: Tue, 27 Aug 2019 17:29:31 -0400 Subject: [PATCH 47/99] link native_rt library for native builds --- tools/include/compiler_options.hpp.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/compiler_options.hpp.in b/tools/include/compiler_options.hpp.in index e860c2fc25..51da49b076 100644 --- a/tools/include/compiler_options.hpp.in +++ b/tools/include/compiler_options.hpp.in @@ -473,7 +473,7 @@ static void GetLdDefaults(std::vector& ldopts) { ldopts.emplace_back("-arch x86_64 -macosx_version_min 10.13 -framework Foundation -framework System"); #endif ldopts.emplace_back("-static"); - ldopts.emplace_back("-lnative_c++ -lnative_c -lnative_eosio -lnative"); + ldopts.emplace_back("-lnative_c++ -lnative_c -lnative_eosio -lnative -lnative_rt"); } } #endif From 477f27ee6ac1a64461f55f46b5789e864c3496f8 Mon Sep 17 00:00:00 2001 From: arhag Date: Tue, 27 Aug 2019 18:53:04 -0400 Subject: [PATCH 48/99] implement printhex intrinsic for native tester --- libraries/native/crt.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/libraries/native/crt.cpp b/libraries/native/crt.cpp index d9df0d328d..9e99678512 100644 --- a/libraries/native/crt.cpp +++ b/libraries/native/crt.cpp @@ -136,8 +136,31 @@ extern "C" { std::string s = eosio::name(nm).to_string(); prints_l(s.c_str(), s.length()); }); + intrinsics::set_intrinsic([](const void* data, uint32_t len) { + constexpr static uint32_t max_stack_buffer_size = 512; + const char* hex_characters = "0123456789abcdef"; - jmp_ret = setjmp(env); + uint32_t buffer_size = 2*len; + if(buffer_size < len) eosio_assert( false, "length passed into printhex is too large" ); + + void* buffer = (max_stack_buffer_size < buffer_size) ? malloc(buffer_size) : alloca(buffer_size); + + char* b = reinterpret_cast(buffer); + const uint8_t* d = reinterpret_cast(data); + for( uint32_t i = 0; i < len; ++i ) { + *b = hex_characters[d[i] >> 4]; + ++b; + *b = hex_characters[d[i] & 0x0f]; + ++b; + } + + prints_l(reinterpret_cast(buffer), buffer_size); + + if(max_stack_buffer_size < buffer_size) free(buffer); + }); + + + jmp_ret = setjmp(env); if (jmp_ret == 0) { ret_val = main(argc, argv); } else { From a2478e6b3b7136142159e742fa37edbdac516895 Mon Sep 17 00:00:00 2001 From: arhag Date: Tue, 27 Aug 2019 20:53:13 -0400 Subject: [PATCH 49/99] fix bugs in native tester macros --- libraries/native/crt.cpp | 17 +++++----- libraries/native/native/eosio/tester.hpp | 40 +++++++++++++++--------- libraries/native/tester.hpp | 40 +++++++++++++++--------- 3 files changed, 60 insertions(+), 37 deletions(-) diff --git a/libraries/native/crt.cpp b/libraries/native/crt.cpp index 9e99678512..fb1b34d40f 100644 --- a/libraries/native/crt.cpp +++ b/libraries/native/crt.cpp @@ -13,7 +13,7 @@ eosio::cdt::output_stream std_err; extern "C" { int main(int, char**); char* _mmap(); - + static jmp_buf env; static jmp_buf test_env; static volatile int jmp_ret; @@ -25,7 +25,8 @@ extern "C" { void ___putc(char c); bool ___disable_output; bool ___has_failed; - + bool ___earlier_unit_test_has_failed; + void* __get_heap_base() { return ___heap_base_ptr; } @@ -79,6 +80,8 @@ extern "C" { ___pages = 1; ___disable_output = false; ___has_failed = false; + ___earlier_unit_test_has_failed = false; + // preset the print functions intrinsics::set_intrinsic([](const char* cs, uint32_t l) { _prints_l(cs, l, eosio::cdt::output_stream_kind::std_out); @@ -106,9 +109,9 @@ extern "C" { memcpy(buff, ret.c_str(), ret.size()); v -= (int)v; buff[ret.size()] = '.'; - size_t size = ret.size(); + size_t size = ret.size(); for (size_t i=size+1; i < size+10; i++) { - v *= 10; + v *= 10; buff[i] = ((int)v)+'0'; v -= (int)v; } @@ -120,9 +123,9 @@ extern "C" { memcpy(buff, ret.c_str(), ret.size()); v -= (long)v; buff[ret.size()] = '.'; - size_t size = ret.size(); + size_t size = ret.size(); for (size_t i=size+1; i < size+10; i++) { - v *= 10; + v *= 10; buff[i] = ((int)v)+'0'; v -= (int)v; } @@ -168,7 +171,7 @@ extern "C" { } return ret_val; } - + extern "C" void* memset(void*, int, size_t); extern "C" void __bzero(void* to, size_t cnt) { char* cp{static_cast(to)}; diff --git a/libraries/native/native/eosio/tester.hpp b/libraries/native/native/eosio/tester.hpp index 4f6f8defb4..1a232e56b5 100644 --- a/libraries/native/native/eosio/tester.hpp +++ b/libraries/native/native/eosio/tester.hpp @@ -7,6 +7,7 @@ extern "C" bool ___disable_output; extern "C" bool ___has_failed; +extern "C" bool ___earlier_unit_test_has_failed; inline void silence_output(bool t) { ___disable_output = t; @@ -32,7 +33,7 @@ inline bool expect_assert(bool check, const std::string& li, Pred&& pred, F&& fu eosio::print("error : expect_assert, no assert {"+li+"}\n"); silence_output(disable_out); return false; - } + } __reset_env(); bool passed = pred(std_err.get()); std_err.clear(); @@ -43,13 +44,13 @@ inline bool expect_assert(bool check, const std::string& li, Pred&& pred, F&& fu eosio::print("error : expect_assert, wrong assert {"+li+"}\n"); silence_output(disable_out); - return passed; + return passed; } template inline bool expect_assert(bool check, const std::string& li, const char (&expected)[N], F&& func, Args... args) { - return expect_assert(check, li, - [&](const std::string& s) { + return expect_assert(check, li, + [&](const std::string& s) { return std_err.index == N-1 && memcmp(expected, s.c_str(), N-1) == 0; }, func, args...); } @@ -72,51 +73,60 @@ inline bool expect_print(bool check, const std::string& li, Pred&& pred, F&& fun template inline bool expect_print(bool check, const std::string& li, const char (&expected)[N], F&& func, Args... args) { - return expect_print(check, li, - [&](const std::string& s) { + return expect_print(check, li, + [&](const std::string& s) { return std_out.index == N-1 && memcmp(expected, s.c_str(), N-1) == 0; }, func, args...); } #define CHECK_ASSERT(...) \ - ___has_failed &= expect_assert(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); + ___has_failed |= expect_assert(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define REQUIRE_ASSERT(...) \ expect_assert(false, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define CHECK_PRINT(...) \ - ___has_failed &= expect_print(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); + ___has_failed |= expect_print(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define REQUIRE_PRINT(...) \ expect_print(false, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define CHECK_EQUAL(X, Y) \ - if (X != Y) { \ + if (!(X == Y)) { \ ___has_failed = true; \ eosio::print(std::string("CHECK_EQUAL failed (")+#X+" != "+#Y+") {"+__FILE__+":"+std::to_string(__LINE__)+"}\n"); \ } #define REQUIRE_EQUAL(X, Y) \ eosio::check(X == Y, std::string(std::string("REQUIRE_EQUAL failed (")+#X+" != "+#Y+") {"+__FILE__+":"+std::to_string(__LINE__)+"}").c_str()); - + #define EOSIO_TEST(X) \ int X ## _ret = setjmp(*___env_ptr); \ if ( X ## _ret == 0 ) \ X(); \ else { \ + bool ___original_disable_output = ___disable_output; \ silence_output(false); \ - eosio::print("\033[1;37m", #X, " \033[0;37munit test \033[1;31mfailed\033[0m\n"); \ + eosio::print("\033[1;37m", #X, " \033[0;37munit test \033[1;31mfailed\033[0m (aborted)\n"); \ ___has_failed = true; \ - silence_output(___disable_output); \ + silence_output(___original_disable_output); \ } #define EOSIO_TEST_BEGIN(X) \ void X() { \ - static constexpr const char* __test_name = #X; + static constexpr const char* __test_name = #X; \ + ___earlier_unit_test_has_failed = ___has_failed; \ + ___has_failed = false; #define EOSIO_TEST_END \ + bool ___original_disable_output = ___disable_output; \ silence_output(false); \ - eosio::print("\033[1;37m",__test_name," \033[0;37munit test \033[1;32mpassed\033[0m\n"); \ - silence_output(___disable_output); \ + if (___has_failed) \ + eosio::print("\033[1;37m", __test_name, " \033[0;37munit test \033[1;31mfailed\033[0m\n"); \ + else \ + eosio::print("\033[1;37m", __test_name, " \033[0;37munit test \033[1;32mpassed\033[0m\n"); \ + silence_output(___original_disable_output); \ + ___has_failed |= ___earlier_unit_test_has_failed; \ + ___earlier_unit_test_has_failed = ___has_failed; \ } diff --git a/libraries/native/tester.hpp b/libraries/native/tester.hpp index 981e450a9e..17a3840201 100644 --- a/libraries/native/tester.hpp +++ b/libraries/native/tester.hpp @@ -9,6 +9,7 @@ extern "C" bool ___disable_output; extern "C" bool ___has_failed; +extern "C" bool ___earlier_unit_test_has_failed; inline void silence_output(bool t) { ___disable_output = t; @@ -34,7 +35,7 @@ inline bool expect_assert(bool check, const std::string& li, Pred&& pred, F&& fu eosio::print("error : expect_assert, no assert {"+li+"}\n"); silence_output(disable_out); return false; - } + } __reset_env(); bool passed = pred(std_err.get()); std_err.clear(); @@ -45,13 +46,13 @@ inline bool expect_assert(bool check, const std::string& li, Pred&& pred, F&& fu eosio::print("error : expect_assert, wrong assert {"+li+"}\n"); silence_output(disable_out); - return passed; + return passed; } template inline bool expect_assert(bool check, const std::string& li, const char (&expected)[N], F&& func, Args... args) { - return expect_assert(check, li, - [&](const std::string& s) { + return expect_assert(check, li, + [&](const std::string& s) { return std_err.index == N-1 && memcmp(expected, s.c_str(), N-1) == 0; }, func, args...); } @@ -74,51 +75,60 @@ inline bool expect_print(bool check, const std::string& li, Pred&& pred, F&& fun template inline bool expect_print(bool check, const std::string& li, const char (&expected)[N], F&& func, Args... args) { - return expect_print(check, li, - [&](const std::string& s) { + return expect_print(check, li, + [&](const std::string& s) { return std_out.index == N-1 && memcmp(expected, s.c_str(), N-1) == 0; }, func, args...); } #define CHECK_ASSERT(...) \ - ___has_failed &= expect_assert(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); + ___has_failed |= expect_assert(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define REQUIRE_ASSERT(...) \ expect_assert(false, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define CHECK_PRINT(...) \ - ___has_failed &= expect_print(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); + ___has_failed |= expect_print(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define REQUIRE_PRINT(...) \ expect_print(false, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define CHECK_EQUAL(X, Y) \ - if (X != Y) { \ + if (!(X == Y)) { \ ___has_failed = true; \ eosio::print(std::string("CHECK_EQUAL failed (")+#X+" != "+#Y+") {"+__FILE__+":"+std::to_string(__LINE__)+"}\n"); \ } #define REQUIRE_EQUAL(X, Y) \ eosio_assert(X == Y, std::string(std::string("REQUIRE_EQUAL failed (")+#X+" != "+#Y+") {"+__FILE__+":"+std::to_string(__LINE__)+"}").c_str()); - + #define EOSIO_TEST(X) \ int X ## _ret = setjmp(*___env_ptr); \ if ( X ## _ret == 0 ) \ X(); \ else { \ + bool ___original_disable_output = ___disable_output; \ silence_output(false); \ - eosio::print("\033[1;37m", #X, " \033[0;37munit test \033[1;31mfailed\033[0m\n"); \ + eosio::print("\033[1;37m", #X, " \033[0;37munit test \033[1;31mfailed\033[0m (aborted)\n"); \ ___has_failed = true; \ - silence_output(___disable_output); \ + silence_output(___original_disable_output); \ } #define EOSIO_TEST_BEGIN(X) \ void X() { \ - static constexpr const char* __test_name = #X; + static constexpr const char* __test_name = #X; \ + ___earlier_unit_test_has_failed = ___has_failed; \ + ___has_failed = false; #define EOSIO_TEST_END \ + bool ___original_disable_output = ___disable_output; \ silence_output(false); \ - eosio::print("\033[1;37m",__test_name," \033[0;37munit test \033[1;32mpassed\033[0m\n"); \ - silence_output(___disable_output); \ + if (___has_failed) \ + eosio::print("\033[1;37m", __test_name, " \033[0;37munit test \033[1;31mfailed\033[0m\n"); \ + else \ + eosio::print("\033[1;37m", __test_name, " \033[0;37munit test \033[1;32mpassed\033[0m\n"); \ + silence_output(___original_disable_output); \ + ___has_failed |= ___earlier_unit_test_has_failed; \ + ___earlier_unit_test_has_failed = ___has_failed; \ } From 02fcbd38242508b60a93cd43d3516d5e8970d40b Mon Sep 17 00:00:00 2001 From: arhag Date: Wed, 28 Aug 2019 12:00:25 -0400 Subject: [PATCH 50/99] rename IMPORTANT to IMPORTANT.md; add link to LICENSE file in CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- IMPORTANT => IMPORTANT.md | 0 README.md | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename IMPORTANT => IMPORTANT.md (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9a13653c33..8e791ac0fd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -100,7 +100,7 @@ Examples of unacceptable behavior include: ## Contributor License & Acknowledgments -Whenever you make a contribution to this project, you license your contribution under the same terms as set out in LICENSE, and you represent and warrant that you have the right to license your contribution under those terms. Whenever you make a contribution to this project, you also certify in the terms of the Developer’s Certificate of Origin set out below: +Whenever you make a contribution to this project, you license your contribution under the same terms as set out in [LICENSE](./LICENSE), and you represent and warrant that you have the right to license your contribution under those terms. Whenever you make a contribution to this project, you also certify in the terms of the Developer’s Certificate of Origin set out below: ``` Developer Certificate of Origin diff --git a/IMPORTANT b/IMPORTANT.md similarity index 100% rename from IMPORTANT rename to IMPORTANT.md diff --git a/README.md b/README.md index 94d392157b..d9d3697486 100644 --- a/README.md +++ b/README.md @@ -88,4 +88,4 @@ $ sudo ./install.sh See [LICENSE](./LICENSE) for copyright and license terms. -All repositories and other materials are provided subject to the terms of this [IMPORTANT](./IMPORTANT) notice and you must familiarize yourself with its terms. The notice contains important information, limitations and restrictions relating to our software, publications, trademarks, third-party resources, and forward-looking statements. By accessing any of our repositories and other materials, you accept and agree to the terms of the notice. +All repositories and other materials are provided subject to the terms of this [IMPORTANT](./IMPORTANT.md) notice and you must familiarize yourself with its terms. The notice contains important information, limitations and restrictions relating to our software, publications, trademarks, third-party resources, and forward-looking statements. By accessing any of our repositories and other materials, you accept and agree to the terms of the notice. From 220086a8f0af3b085c03e634babae9f5948d46bb Mon Sep 17 00:00:00 2001 From: arhag Date: Wed, 28 Aug 2019 15:19:31 -0400 Subject: [PATCH 51/99] support verbose output in unit tests --- tests/unit/asset_tests.cpp | 38 ++++++++++++------------ tests/unit/binary_extension_tests.cpp | 12 ++++---- tests/unit/crypto_tests.cpp | 14 ++++----- tests/unit/datastream_tests.cpp | 42 ++++++++++----------------- tests/unit/fixed_bytes_tests.cpp | 10 ++++--- tests/unit/name_tests.cpp | 34 ++++++++++++---------- tests/unit/print_tests.cpp | 8 +++-- tests/unit/rope_tests.cpp | 25 +++++++++------- tests/unit/serialize_tests.cpp | 14 +++++---- tests/unit/symbol_tests.cpp | 24 ++++++--------- tests/unit/system_tests.cpp | 10 ++++--- tests/unit/time_tests.cpp | 36 +++++++++-------------- tests/unit/varint_tests.cpp | 18 +++++------- 13 files changed, 135 insertions(+), 150 deletions(-) diff --git a/tests/unit/asset_tests.cpp b/tests/unit/asset_tests.cpp index 065001d656..841e76720d 100644 --- a/tests/unit/asset_tests.cpp +++ b/tests/unit/asset_tests.cpp @@ -22,8 +22,6 @@ static constexpr int64_t asset_max{ asset_mask}; // 4611686018427387903 // Definitions in `eosio.cdt/libraries/eosio/asset.hpp` EOSIO_TEST_BEGIN(asset_type_test) - silence_output(true); - static constexpr symbol s0{"A", 0}; static constexpr symbol s1{"Z", 0}; static constexpr symbol s2{"AAAAAAA", 0}; @@ -34,7 +32,7 @@ EOSIO_TEST_BEGIN(asset_type_test) //// constexpr asset() CHECK_EQUAL( asset{}.amount, 0ULL ) CHECK_EQUAL( asset{}.symbol.raw(), 0ULL ) - + //// constexpr asset(int64_t, symbol) CHECK_EQUAL( (asset{0LL, s0}.amount), 0LL ) CHECK_EQUAL( (asset{asset_min, s0}.amount), asset_min ) @@ -44,12 +42,12 @@ EOSIO_TEST_BEGIN(asset_type_test) CHECK_EQUAL( (asset{0LL, s1}.symbol.raw()), 23040ULL ) // "Z", precision: 0 CHECK_EQUAL( (asset{0LL, s2}.symbol.raw()), 4702111234474983680ULL ) // "AAAAAAA", precision: 0 CHECK_EQUAL( (asset{0LL, s3}.symbol.raw()), 6510615555426900480ULL ) // "ZZZZZZZ", precision: 0 - + // Note: there is an invariant established for `asset` that is not enforced for `symbol` // For example: // `symbol{};` // valid code // `asset{{}, symbol{}};` // throws "invalid symbol name" - + CHECK_ASSERT( "invalid symbol name", ([]() {asset{0LL, symbol{0LL}};}) ) CHECK_ASSERT( "invalid symbol name", ([]() {asset{0LL, symbol{1LL}};}) ) CHECK_ASSERT( "invalid symbol name", ([]() {asset{0LL, symbol{16639ULL}};}) ) @@ -95,7 +93,7 @@ EOSIO_TEST_BEGIN(asset_type_test) CHECK_EQUAL( (asset_set_amount.set_amount(1LL), asset_set_amount.amount), 1LL ) CHECK_EQUAL( (asset_set_amount.set_amount(asset_min), asset_set_amount.amount), asset_min ) CHECK_EQUAL( (asset_set_amount.set_amount(asset_max), asset_set_amount.amount), asset_max ) - + CHECK_ASSERT( "magnitude of asset amount must be less than 2^62", ( [&]() { asset_set_amount.set_amount(asset_min - 1); @@ -114,7 +112,7 @@ EOSIO_TEST_BEGIN(asset_type_test) // Printing an `asset` is limited to a precision of 63 // This will trigger an error: // `asset{int64_t{1LL}, symbol{"SYMBOLL", 64}}.print();` // output: "Floating point exception: ..." - + CHECK_EQUAL( (asset{ 0LL, sym_no_prec}.to_string()), "0 SYMBOLL" ) CHECK_EQUAL( (asset{-0LL, sym_no_prec}.to_string()), "0 SYMBOLL" ) CHECK_EQUAL( (asset{ 0LL, sym_prec}.to_string()), @@ -128,7 +126,7 @@ EOSIO_TEST_BEGIN(asset_type_test) "0.000000000000000000000000000000000000000000000000000000000000001 SYMBOLL" ) CHECK_EQUAL( (asset{-1LL, sym_prec}.to_string()), "0.000000000000000000000000000000000000000000000000000000000000001 SYMBOLL" ) - + CHECK_EQUAL( (asset{asset_min, sym_no_prec}.to_string()), "-4611686018427387903 SYMBOLL" ) CHECK_EQUAL( (asset{asset_max, sym_no_prec}.to_string()), "4611686018427387903 SYMBOLL" ) CHECK_EQUAL( (asset{asset_min, sym_prec}.to_string()), @@ -222,7 +220,7 @@ EOSIO_TEST_BEGIN(asset_type_test) asset{asset_max, sym_no_prec} += asset{1LL, sym_no_prec}; }) ) - + // ------------------------------------------------------------------------------------------ // inline friend asset& operator-(const asset&, const asset&)/asset& operator-=(const asset&) CHECK_EQUAL( (asset{0LL, sym_no_prec} -= asset{0LL, sym_no_prec} ), (asset{0LL, sym_no_prec}) ) @@ -342,14 +340,10 @@ EOSIO_TEST_BEGIN(asset_type_test) // friend bool operator>=( const asset&, const asset&) CHECK_EQUAL( ( asset{1LL, sym_no_prec} >= asset{0LL, sym_no_prec} ), true ) CHECK_EQUAL( ( asset{1LL, sym_no_prec} >= asset{1LL, sym_no_prec} ), true ) - - silence_output(false); EOSIO_TEST_END // Definitions in `eosio.cdt/libraries/eosio/asset.hpp` EOSIO_TEST_BEGIN(extended_asset_type_test) - silence_output(true); - static constexpr symbol sym_no_prec{"SYMBOLL",0}; static constexpr symbol sym_prec{"SYMBOLL",63}; @@ -382,12 +376,12 @@ EOSIO_TEST_BEGIN(extended_asset_type_test) CHECK_PRINT( "0 A@5", [](){extended_asset{asset{int64_t{0}, symbol{"A", 0}}, name{"5"}}.print();} ) CHECK_PRINT( "0 Z@a", [](){extended_asset{asset{int64_t{0}, symbol{"Z", 0}}, name{"a"}}.print();} ) CHECK_PRINT( "0 Z@z", [](){extended_asset{asset{int64_t{0}, symbol{"Z", 0}}, name{"z"}}.print();} ) - + CHECK_PRINT( "1.1 A@1", [](){extended_asset{asset{int64_t{11}, symbol{"A", 1}}, name{"1"}}.print();} ) CHECK_PRINT( "1.1 A@5", [](){extended_asset{asset{int64_t{11}, symbol{"A", 1}}, name{"5"}}.print();} ) CHECK_PRINT( "1.1 Z@a", [](){extended_asset{asset{int64_t{11}, symbol{"Z", 1}}, name{"a"}}.print();} ) CHECK_PRINT( "1.1 Z@z", [](){extended_asset{asset{int64_t{11}, symbol{"Z", 1}}, name{"z"}}.print();} ) - + CHECK_PRINT( "0.000000000000000000000000000000000000000000000000000000000000011 A@1", [](){extended_asset{asset{int64_t{11}, symbol{"A", 63}}, name{"1"}}.print();} ) CHECK_PRINT( "0.000000000000000000000000000000000000000000000000000000000000011 A@5", @@ -401,12 +395,12 @@ EOSIO_TEST_BEGIN(extended_asset_type_test) CHECK_PRINT( "0 AAAAAAA@555555555555j", [](){extended_asset{asset{int64_t{0}, symbol{"AAAAAAA", 0}}, name{"555555555555j"}}.print();} ) CHECK_PRINT( "0 ZZZZZZZ@aaaaaaaaaaaaj", [](){extended_asset{asset{int64_t{0}, symbol{"ZZZZZZZ", 0}}, name{"aaaaaaaaaaaaj"}}.print();} ) CHECK_PRINT( "0 ZZZZZZZ@zzzzzzzzzzzzj", [](){extended_asset{asset{int64_t{0}, symbol{"ZZZZZZZ", 0}}, name{"zzzzzzzzzzzzj"}}.print();} ) - + CHECK_PRINT( "11 AAAAAAA@111111111111j", [](){extended_asset{asset{int64_t{11}, symbol{"AAAAAAA", 0}}, name{"111111111111j"}}.print();} ) CHECK_PRINT( "11 AAAAAAA@555555555555j", [](){extended_asset{asset{int64_t{11}, symbol{"AAAAAAA", 0}}, name{"555555555555j"}}.print();} ) CHECK_PRINT( "11 ZZZZZZZ@aaaaaaaaaaaaj", [](){extended_asset{asset{int64_t{11}, symbol{"ZZZZZZZ", 0}}, name{"aaaaaaaaaaaaj"}}.print();} ) CHECK_PRINT( "11 ZZZZZZZ@zzzzzzzzzzzzj", [](){extended_asset{asset{int64_t{11}, symbol{"ZZZZZZZ", 0}}, name{"zzzzzzzzzzzzj"}}.print();} ) - + CHECK_PRINT( "0.000000000000000000000000000000000000000000000000000000000000011 AAAAAAA@111111111111j", [](){extended_asset{asset{int64_t{11}, symbol{"AAAAAAA", 63}}, name{"111111111111j"}}.print();} ) CHECK_PRINT( "0.000000000000000000000000000000000000000000000000000000000000011 AAAAAAA@555555555555j", @@ -415,7 +409,7 @@ EOSIO_TEST_BEGIN(extended_asset_type_test) [](){extended_asset{asset{int64_t{11}, symbol{"ZZZZZZZ", 63}}, name{"aaaaaaaaaaaaj"}}.print();} ) CHECK_PRINT( "0.000000000000000000000000000000000000000000000000000000000000011 ZZZZZZZ@zzzzzzzzzzzzj", [](){extended_asset{asset{int64_t{11}, symbol{"ZZZZZZZ", 63}}, name{"zzzzzzzzzzzzj"}}.print();} ) - + // ------------------------------- // extended_asset operator-()const CHECK_EQUAL( (-extended_asset{asset{ 0, sym_no_prec}, {}}.quantity), (extended_asset{asset_no_prec, {}}.quantity) ) @@ -523,11 +517,15 @@ EOSIO_TEST_BEGIN(extended_asset_type_test) return b; }) ) - - silence_output(false); EOSIO_TEST_END int main(int argc, char* argv[]) { + bool verbose = false; + if( argc >= 2 && std::strcmp( argv[1], "-v" ) == 0 ) { + verbose = true; + } + silence_output(!verbose); + EOSIO_TEST(asset_type_test); EOSIO_TEST(extended_asset_type_test); return has_failed(); diff --git a/tests/unit/binary_extension_tests.cpp b/tests/unit/binary_extension_tests.cpp index 057c8c3b23..9d4ffc0a30 100644 --- a/tests/unit/binary_extension_tests.cpp +++ b/tests/unit/binary_extension_tests.cpp @@ -13,8 +13,6 @@ using eosio::binary_extension; // Definitions in `eosio.cdt/libraries/eosio/binary_extension.hpp` EOSIO_TEST_BEGIN(binary_extension_test) - silence_output(true); - //// constexpr binary_extension() // constexpr bool has_value()const CHECK_EQUAL( (binary_extension{}.has_value()), false ) @@ -208,7 +206,7 @@ EOSIO_TEST_BEGIN(binary_extension_test) binary_extension be_str_emplace{"abcd"}; be_str_emplace.emplace(move("efgh")); CHECK_EQUAL( be_str_emplace.value() == "efgh", true ) - CHECK_EQUAL( be_str_emplace.value() != "abcd", true ) + CHECK_EQUAL( be_str_emplace.value() != "abcd", true ) // ------------ // void reset() @@ -221,11 +219,15 @@ EOSIO_TEST_BEGIN(binary_extension_test) CHECK_EQUAL( be_str_reset.has_value(), true ) be_str_reset.reset(); CHECK_EQUAL( be_str_reset.has_value(), false ) - - silence_output(false); EOSIO_TEST_END int main(int argc, char* argv[]) { + bool verbose = false; + if( argc >= 2 && std::strcmp( argv[1], "-v" ) == 0 ) { + verbose = true; + } + silence_output(!verbose); + EOSIO_TEST(binary_extension_test); return has_failed(); } diff --git a/tests/unit/crypto_tests.cpp b/tests/unit/crypto_tests.cpp index 8f24f40835..697e3228b2 100644 --- a/tests/unit/crypto_tests.cpp +++ b/tests/unit/crypto_tests.cpp @@ -11,8 +11,6 @@ using eosio::signature; // Definitions in `eosio.cdt/libraries/eosio/crypto.hpp` EOSIO_TEST_BEGIN(public_key_type_test) - silence_output(true); - // ----------------------------------------------------- // bool operator==(const public_key&, const public_key&) CHECK_EQUAL( (public_key{0, std::array{}} == public_key{0, std::array{}}), true ) @@ -22,14 +20,10 @@ EOSIO_TEST_BEGIN(public_key_type_test) // bool operator!=(const public_key&, const public_key&) CHECK_EQUAL( (public_key{0, std::array{}} != public_key{0, std::array{}}), false ) CHECK_EQUAL( (public_key{0, std::array{1}} != public_key{0, std::array{}}), true ) - - silence_output(false); EOSIO_TEST_END // Definitions in `eosio.cdt/libraries/eosio/crypto.hpp` EOSIO_TEST_BEGIN(signature_type_test) - silence_output(true); - // --------------------------------------------------- // bool operator==(const signature&, const signature&) CHECK_EQUAL( (signature{0, std::array{}} == signature{0, std::array{}}), true ) @@ -39,11 +33,15 @@ EOSIO_TEST_BEGIN(signature_type_test) // bool operator!=(const signature&, const signature&) CHECK_EQUAL( (signature{0, std::array{1}} != signature{0, std::array{}}), true ) CHECK_EQUAL( (signature{0, std::array{}} != signature{0, std::array{}}), false ) - - silence_output(false); EOSIO_TEST_END int main(int argc, char* argv[]) { + bool verbose = false; + if( argc >= 2 && std::strcmp( argv[1], "-v" ) == 0 ) { + verbose = true; + } + silence_output(!verbose); + EOSIO_TEST(public_key_type_test) EOSIO_TEST(signature_type_test) return has_failed(); diff --git a/tests/unit/datastream_tests.cpp b/tests/unit/datastream_tests.cpp index cdb2fc6c31..cf102ec1b0 100644 --- a/tests/unit/datastream_tests.cpp +++ b/tests/unit/datastream_tests.cpp @@ -56,8 +56,6 @@ struct be_test { // Definitions in `eosio.cdt/libraries/eosio/datastream.hpp` EOSIO_TEST_BEGIN(datastream_test) - silence_output(true); - static constexpr uint16_t buffer_size{256}; char datastream_buffer[buffer_size]{}; // Buffer for the datastream to point to char buffer[buffer_size]; // Buffer to compare `datastream_buffer` with @@ -74,7 +72,7 @@ EOSIO_TEST_BEGIN(datastream_test) CHECK_EQUAL( ds.pos(), datastream_buffer+1 ) ds.skip(-1); CHECK_EQUAL( ds.pos(), datastream_buffer ) - + // inline bool read(char*, size_t) CHECK_EQUAL( ds.read(buffer, 256), true ) CHECK_EQUAL( memcmp(buffer, datastream_buffer, 256), 0) @@ -104,7 +102,7 @@ EOSIO_TEST_BEGIN(datastream_test) ds.seekp(256); CHECK_ASSERT( "put", ([&]() {ds.put('c');}) ) - + // inline bool get(unsigned char&) unsigned char uch{}; @@ -141,14 +139,10 @@ EOSIO_TEST_BEGIN(datastream_test) CHECK_EQUAL( ds.remaining(), 0 ) ds.seekp(257); CHECK_EQUAL( ds.remaining(), -1) - - silence_output(false); EOSIO_TEST_END // Definitions in `eosio.cdt/libraries/eosio/datastream.hpp` EOSIO_TEST_BEGIN(datastream_specialization_test) - silence_output(true); - static constexpr uint16_t buffer_size{256}; char datastream_buffer[buffer_size]{}; // Buffer for the datastream to point to char buffer[buffer_size]; // Buffer to compare `datastream_buffer` with @@ -164,16 +158,16 @@ EOSIO_TEST_BEGIN(datastream_specialization_test) // inline size_t tellp()const CHECK_EQUAL( ds.skip(0), true) CHECK_EQUAL( ds.tellp(), 256) - + CHECK_EQUAL( ds.skip(1), true) CHECK_EQUAL( ds.tellp(), 257) - + CHECK_EQUAL( ds.skip(255), true) CHECK_EQUAL( ds.tellp(), 512) - + CHECK_EQUAL( ds.skip(1028), true) CHECK_EQUAL( ds.tellp(), 1540) - + // inline bool seekp(size_t) ds.seekp(0); CHECK_EQUAL( ds.tellp(), 0) @@ -187,7 +181,7 @@ EOSIO_TEST_BEGIN(datastream_specialization_test) // inline bool put(char) char ch{'c'}; - + ds.seekp(0); CHECK_EQUAL( ds.put(ch), true ) CHECK_EQUAL( ds.tellp(), 1 ) @@ -217,14 +211,10 @@ EOSIO_TEST_BEGIN(datastream_specialization_test) ds.seekp(257); CHECK_EQUAL( ds.remaining(), 0 ) - - silence_output(false); EOSIO_TEST_END // Definitions in `eosio.cdt/libraries/eosio/datastream.hpp` EOSIO_TEST_BEGIN(datastream_stream_test) - silence_output(true); - static constexpr uint16_t buffer_size{256}; char datastream_buffer[buffer_size]; // Buffer for the datastream to point to @@ -315,7 +305,7 @@ EOSIO_TEST_BEGIN(datastream_stream_test) ds.seekp(0); ds >> arr; CHECK_EQUAL( carr, arr ) - + // ---------- // std::deque ds.seekp(0); @@ -414,7 +404,7 @@ EOSIO_TEST_BEGIN(datastream_stream_test) ds.seekp(0); ds >> v; CHECK_EQUAL( cv, v ) - + // ----------- // std::vector struct vec_test { @@ -553,7 +543,7 @@ EOSIO_TEST_BEGIN(datastream_stream_test) ds.seekp(0); ds >> sym; CHECK_EQUAL( csym_prec, sym ) - + // ------------------ // eosio::symbol_code ds.seekp(0); @@ -564,14 +554,10 @@ EOSIO_TEST_BEGIN(datastream_stream_test) ds.seekp(0); ds >> sc; CHECK_EQUAL( csc, sc ) - - silence_output(false); EOSIO_TEST_END // Definitions in `eosio.cdt/libraries/eosio/datastream.hpp` EOSIO_TEST_BEGIN(misc_datastream_test) - silence_output(true); - // --------------------------- // vector pack(const T&) static const string pack_str{"abcdefghi"}; @@ -604,11 +590,15 @@ EOSIO_TEST_BEGIN(misc_datastream_test) unpack_ch = unpack(unpack_source_buffer+i, 9); CHECK_EQUAL( unpack_source_buffer[i], unpack_ch ) } - - silence_output(false); EOSIO_TEST_END int main(int argc, char* argv[]) { + bool verbose = false; + if( argc >= 2 && std::strcmp( argv[1], "-v" ) == 0 ) { + verbose = true; + } + silence_output(!verbose); + EOSIO_TEST(datastream_test); EOSIO_TEST(datastream_specialization_test); EOSIO_TEST(datastream_stream_test); diff --git a/tests/unit/fixed_bytes_tests.cpp b/tests/unit/fixed_bytes_tests.cpp index 965ed86362..d786aa3c26 100644 --- a/tests/unit/fixed_bytes_tests.cpp +++ b/tests/unit/fixed_bytes_tests.cpp @@ -14,8 +14,6 @@ using eosio::fixed_bytes; // Definitions in `eosio.cdt/libraries/eosio/fixed_bytes.hpp` EOSIO_TEST_BEGIN(fixed_bytes_test) - silence_output(true); - //// constexpr fixed_bytes() // static constexpr size_t padded_bytes() CHECK_EQUAL( fixed_bytes<20>{}.padded_bytes(), 12 ) @@ -175,11 +173,15 @@ EOSIO_TEST_BEGIN(fixed_bytes_test) // friend bool operator>= <>(const fixed_bytes, const fixed_bytes) CHECK_EQUAL( fb_cmp1 >= fb_cmp1, true ) CHECK_EQUAL( fb_cmp1 >= fb_cmp2, false ) - - silence_output(false); EOSIO_TEST_END int main(int argc, char* argv[]) { + bool verbose = false; + if( argc >= 2 && std::strcmp( argv[1], "-v" ) == 0 ) { + verbose = true; + } + silence_output(!verbose); + EOSIO_TEST(fixed_bytes_test); return has_failed(); } diff --git a/tests/unit/name_tests.cpp b/tests/unit/name_tests.cpp index d26a1e29e5..f5425b56ca 100644 --- a/tests/unit/name_tests.cpp +++ b/tests/unit/name_tests.cpp @@ -19,8 +19,6 @@ static constexpr uint64_t u64max = numeric_limits::max(); // 184467440 // Definitions in `eosio.cdt/libraries/eosio/name.hpp` EOSIO_TEST_BEGIN(name_type_test) - silence_output(true); - //// constexpr name() CHECK_EQUAL( name{}.value, 0ULL ) @@ -50,7 +48,7 @@ EOSIO_TEST_BEGIN(name_type_test) CHECK_EQUAL( name{"123."}.value, 614178399182651392ULL ) CHECK_EQUAL( name{"123........."}.value, 614178399182651392ULL ) CHECK_EQUAL( name{".a.b.c.1.2.3."}.value, 108209673814966320ULL ) - + CHECK_EQUAL( name{"abc.123"}.value, 3589369488740450304ULL ) CHECK_EQUAL( name{"123.abc"}.value, 614181822271586304ULL ) @@ -86,14 +84,14 @@ EOSIO_TEST_BEGIN(name_type_test) CHECK_EQUAL( name::char_to_value(c), expected_value ) ++expected_value; } - + CHECK_ASSERT( "character is not in allowed character set for names", ([]() {name::char_to_value(char{'-'});}) ) CHECK_ASSERT( "character is not in allowed character set for names", ([]() {name::char_to_value(char{'/'});}) ) CHECK_ASSERT( "character is not in allowed character set for names", ([]() {name::char_to_value(char{'6'});}) ) CHECK_ASSERT( "character is not in allowed character set for names", ([]() {name::char_to_value(char{'A'});}) ) CHECK_ASSERT( "character is not in allowed character set for names", ([]() {name::char_to_value(char{'Z'});}) ) CHECK_ASSERT( "character is not in allowed character set for names", ([]() {name::char_to_value(char{'`'});}) ) - CHECK_ASSERT( "character is not in allowed character set for names", ([]() {name::char_to_value(char{'{'});}) ); + CHECK_ASSERT( "character is not in allowed character set for names", ([]() {name::char_to_value(char{'{'});}) ); // ------------------------------- // constexpr uint8_t length()cosnt @@ -113,7 +111,7 @@ EOSIO_TEST_BEGIN(name_type_test) CHECK_EQUAL( name{"eosioaccountj"}.length(), 13 ) CHECK_ASSERT( "string is too long to be a valid name", ([]() {name{"12345abcdefghj"}.length();}) ) - + // ---------------------------- // constexpr name suffix()const CHECK_EQUAL( name{".eosioaccounj"}.suffix(), name{"eosioaccounj"} ) @@ -159,7 +157,7 @@ EOSIO_TEST_BEGIN(name_type_test) CHECK_EQUAL( name{"555555555555j"}.operator name::raw(), static_cast(2975281302211218015ULL) ) CHECK_EQUAL( name{"aaaaaaaaaaaaj"}.operator name::raw(), static_cast(3570337562653461615ULL) ) CHECK_EQUAL( name{"zzzzzzzzzzzzj"}.operator name::raw(), static_cast(u64max) ) - + // --------------------------------------- // constexpr explicit operator bool()const // Note that I must be explicit about calling the operator because it is defined as `explicit` @@ -177,7 +175,7 @@ EOSIO_TEST_BEGIN(name_type_test) // char* write_as_string(char*, char*)const static constexpr uint8_t buffer_size{32}; char buffer[buffer_size]{}; - + string str{"1"}; name{str}.write_as_string( buffer, buffer + sizeof(buffer) ); CHECK_EQUAL( memcmp(str.c_str(), buffer, strlen(str.c_str())), 0 ) @@ -242,7 +240,7 @@ EOSIO_TEST_BEGIN(name_type_test) CHECK_EQUAL( name{"123."}.to_string(), "123" ) CHECK_EQUAL( name{"123........."}.to_string(), "123" ) CHECK_EQUAL( name{".a.b.c.1.2.3."}.to_string(), ".a.b.c.1.2.3" ) - + CHECK_EQUAL( name{"abc.123"}.to_string(), "abc.123" ) CHECK_EQUAL( name{"123.abc"}.to_string(), "123.abc" ) @@ -270,7 +268,7 @@ EOSIO_TEST_BEGIN(name_type_test) CHECK_EQUAL( name{"123."} == name{"123"}, true ) CHECK_EQUAL( name{"123........."} == name{"123"}, true ) CHECK_EQUAL( name{".a.b.c.1.2.3."} == name{".a.b.c.1.2.3"}, true ) - + CHECK_EQUAL( name{"abc.123"} == name{"abc.123"}, true ) CHECK_EQUAL( name{"123.abc"} == name{"123.abc"}, true ) @@ -298,7 +296,7 @@ EOSIO_TEST_BEGIN(name_type_test) CHECK_EQUAL( name{"123."} != name{}, true ) CHECK_EQUAL( name{"123........."} != name{}, true ) CHECK_EQUAL( name{".a.b.c.1.2.3."} != name{}, true ) - + CHECK_EQUAL( name{"abc.123"} != name{}, true ) CHECK_EQUAL( name{"123.abc"} != name{}, true ) @@ -326,7 +324,7 @@ EOSIO_TEST_BEGIN(name_type_test) CHECK_EQUAL( name{} < name{"123."}, true ) CHECK_EQUAL( name{} < name{"123........."}, true ) CHECK_EQUAL( name{} < name{".a.b.c.1.2.3."}, true ) - + CHECK_EQUAL( name{} < name{"abc.123"}, true ) CHECK_EQUAL( name{} < name{"123.abc"}, true ) @@ -342,7 +340,7 @@ EOSIO_TEST_BEGIN(name_type_test) // ------------------------------------ // inline constexpr name operator""_n() CHECK_EQUAL( name{}, ""_n ) - + CHECK_EQUAL( name{"1"}, "1"_n ) CHECK_EQUAL( name{"5"}, "5"_n ) CHECK_EQUAL( name{"a"}, "a"_n ) @@ -356,7 +354,7 @@ EOSIO_TEST_BEGIN(name_type_test) CHECK_EQUAL( name{"123."}, "123."_n ) CHECK_EQUAL( name{"123........."}, "123........."_n ) CHECK_EQUAL( name{".a.b.c.1.2.3."}, ".a.b.c.1.2.3."_n ) - + CHECK_EQUAL( name{"abc.123"}, "abc.123"_n ) CHECK_EQUAL( name{"123.abc"}, "123.abc"_n ) @@ -368,11 +366,15 @@ EOSIO_TEST_BEGIN(name_type_test) CHECK_EQUAL( name{"555555555555j"}, "555555555555j"_n ) CHECK_EQUAL( name{"aaaaaaaaaaaaj"}, "aaaaaaaaaaaaj"_n ) CHECK_EQUAL( name{"zzzzzzzzzzzzj"}, "zzzzzzzzzzzzj"_n ) - - silence_output(false); EOSIO_TEST_END int main(int argc, char* argv[]) { + bool verbose = false; + if( argc >= 2 && std::strcmp( argv[1], "-v" ) == 0 ) { + verbose = true; + } + silence_output(!verbose); + EOSIO_TEST(name_type_test); return has_failed(); } diff --git a/tests/unit/print_tests.cpp b/tests/unit/print_tests.cpp index ccaf61a44b..2486f5f867 100644 --- a/tests/unit/print_tests.cpp +++ b/tests/unit/print_tests.cpp @@ -4,7 +4,6 @@ using namespace eosio::native; EOSIO_TEST_BEGIN(print_test) - silence_output(false); CHECK_PRINT("27", [](){ eosio::print((uint8_t)27); }); CHECK_PRINT("34", [](){ eosio::print((int)34); }); CHECK_PRINT([](std::string s){return s[0] == 'a';}, [](){ eosio::print((char)'a'); }); @@ -21,10 +20,15 @@ EOSIO_TEST_BEGIN(print_test) CHECK_PRINT("-404000000", [](){ eosio::print((int64_t)-404000000); }); CHECK_PRINT("0x0066000000000000", [](){ eosio::print((uint128_t)102); }); CHECK_PRINT("0xffffff9affffffffffffffffffffffff", [](){ eosio::print((int128_t)-102); }); - silence_output(false); EOSIO_TEST_END int main(int argc, char** argv) { + bool verbose = false; + if( argc >= 2 && std::strcmp( argv[1], "-v" ) == 0 ) { + verbose = true; + } + silence_output(!verbose); + EOSIO_TEST(print_test); return has_failed(); } diff --git a/tests/unit/rope_tests.cpp b/tests/unit/rope_tests.cpp index b6ef61814d..c9ddd185d0 100644 --- a/tests/unit/rope_tests.cpp +++ b/tests/unit/rope_tests.cpp @@ -11,7 +11,6 @@ using namespace eosio::native; EOSIO_TEST_BEGIN(rope_test) - silence_output(false); eosio::rope r("test string 0"); r += ", test string 1"; r += ", test string 2"; @@ -20,7 +19,7 @@ EOSIO_TEST_BEGIN(rope_test) r += ", test string 5"; r += ", test string 6"; r += ", test string 7"; - + std::string s("test string 0"); s += ", test string 1"; s += ", test string 2"; @@ -36,23 +35,23 @@ EOSIO_TEST_BEGIN(rope_test) r2 += eosio::rope("rvalue +="); s2 += std::string("rvalue +="); - r2 = r2 + r2; + r2 = r2 + r2; s2 = s2 + s2; r2 += "the end"; s2 += "the end"; - + eosio::rope r3(r2); std::string s3(s2); REQUIRE_EQUAL(s.compare(std::string(r.c_str())), 0); REQUIRE_EQUAL(s2.compare(std::string(r2.c_str())), 0); REQUIRE_EQUAL(s3.compare(std::string(r3.c_str())), 0); - - REQUIRE_EQUAL(s.length(), r.length()); - REQUIRE_EQUAL(s2.length(), r2.length()); - REQUIRE_EQUAL(s3.length(), r3.length()); - + + REQUIRE_EQUAL(s.length(), r.length()); + REQUIRE_EQUAL(s2.length(), r2.length()); + REQUIRE_EQUAL(s3.length(), r3.length()); + for (int i=0; i < s.length(); i++) { REQUIRE_EQUAL(s[i], r[i]); } @@ -64,11 +63,15 @@ EOSIO_TEST_BEGIN(rope_test) for (int i=0; i < s3.length(); i++) { REQUIRE_EQUAL(s3[i], r3[i]); } - - silence_output(false); EOSIO_TEST_END int main(int argc, char** argv) { + bool verbose = false; + if( argc >= 2 && std::strcmp( argv[1], "-v" ) == 0 ) { + verbose = true; + } + silence_output(!verbose); + EOSIO_TEST(rope_test); return has_failed(); } diff --git a/tests/unit/serialize_tests.cpp b/tests/unit/serialize_tests.cpp index 4792038673..940cf9a7c4 100644 --- a/tests/unit/serialize_tests.cpp +++ b/tests/unit/serialize_tests.cpp @@ -52,8 +52,6 @@ struct D2 : public D1 { // Definitions in `eosio.cdt/libraries/eosio/serialize.hpp` EOSIO_TEST_BEGIN(serialize_test) - silence_output(true); - static constexpr uint16_t buffer_size{256}; char ds_buffer[buffer_size]{}; // Buffer for the datastream to point to char ds_expected_buffer[buffer_size]{}; // Buffer to compare `ds_buffer` with @@ -81,7 +79,7 @@ EOSIO_TEST_BEGIN(serialize_test) ds_expected << d1.c << d1.i; ds << d1; REQUIRE_EQUAL( memcmp( ds_buffer, ds_expected_buffer, 256), 0 ) - + ds.seekp(0); ds >> dd1; REQUIRE_EQUAL( d1, dd1 ) @@ -97,15 +95,19 @@ EOSIO_TEST_BEGIN(serialize_test) ds_expected << d2.c << d2.i << d2.v; ds << d2; REQUIRE_EQUAL( memcmp( ds_buffer, ds_expected_buffer, 256), 0 ) - + ds.seekp(0); ds >> dd2; REQUIRE_EQUAL( d2, dd2 ) - - silence_output(false); EOSIO_TEST_END int main(int argc, char* argv[]) { + bool verbose = false; + if( argc >= 2 && std::strcmp( argv[1], "-v" ) == 0 ) { + verbose = true; + } + silence_output(!verbose); + EOSIO_TEST(serialize_test) return has_failed(); } diff --git a/tests/unit/symbol_tests.cpp b/tests/unit/symbol_tests.cpp index 0d0bde5a54..1b564da0f7 100644 --- a/tests/unit/symbol_tests.cpp +++ b/tests/unit/symbol_tests.cpp @@ -22,8 +22,6 @@ static constexpr uint64_t u64max = numeric_limits::max(); // 184467440 // Definitions in `eosio.cdt/libraries/eosio/symbol.hpp` EOSIO_TEST_BEGIN(symbol_code_type_test) - silence_output(true); - //// constexpr symbol_code() // constexpr uint64_t raw()const CHECK_EQUAL( symbol_code{}.raw(), 0ULL ) @@ -119,14 +117,10 @@ EOSIO_TEST_BEGIN(symbol_code_type_test) CHECK_EQUAL( symbol_code{} < symbol_code{"Z"}, true ) CHECK_EQUAL( symbol_code{} < symbol_code{"AAAAAAA"}, true ) CHECK_EQUAL( symbol_code{} < symbol_code{"ZZZZZZZ"}, true ) - - silence_output(false); EOSIO_TEST_END // Definitions in `eosio.cdt/libraries/eosio/symbol.hpp` EOSIO_TEST_BEGIN(symbol_type_test) - silence_output(true); - static constexpr symbol_code sc0{"A"}; static constexpr symbol_code sc1{"Z"}; static constexpr symbol_code sc2{"AAAAAAA"}; @@ -195,7 +189,7 @@ EOSIO_TEST_BEGIN(symbol_type_test) CHECK_EQUAL( (symbol{"SYMBOLL", 0}.operator bool()), true ) CHECK_EQUAL( (!symbol{"", 0}.operator bool()), true ) CHECK_EQUAL( (!symbol{"SYMBOLL", 0}.operator bool()), false ) - + // --------------------- // void print(bool)const // Note: @@ -225,14 +219,10 @@ EOSIO_TEST_BEGIN(symbol_type_test) CHECK_EQUAL( (symbol{} < symbol{sc1, 0}), true ) CHECK_EQUAL( (symbol{} < symbol{sc2, 0}), true ) CHECK_EQUAL( (symbol{} < symbol{sc3, 0}), true ) - - silence_output(false); EOSIO_TEST_END // Definitions in `eosio.cdt/libraries/eosio/symbol.hpp` EOSIO_TEST_BEGIN(extended_symbol_type_test) - silence_output(true); - static constexpr name n0{"1"}; static constexpr name n1{"5"}; static constexpr name n2{"a"}; @@ -252,7 +242,7 @@ EOSIO_TEST_BEGIN(extended_symbol_type_test) // constexpr name get_contract() CHECK_EQUAL( (extended_symbol{{}, {}}.get_symbol().raw()), 0ULL ) CHECK_EQUAL( (extended_symbol{{}, {}}.get_contract().value), 0ULL ) - + //// constexpr extended_symbol(symbol, name) CHECK_EQUAL( (extended_symbol{s0, n0}.get_symbol().raw()), 16640ULL ) CHECK_EQUAL( (extended_symbol{s0, n1}.get_symbol().code().raw()), 65ULL ) @@ -270,7 +260,7 @@ EOSIO_TEST_BEGIN(extended_symbol_type_test) CHECK_EQUAL( (extended_symbol{s2, n5}.get_contract().value), 2975281302211218015ULL ) CHECK_EQUAL( (extended_symbol{s3, n6}.get_contract().value), 3570337562653461615ULL ) CHECK_EQUAL( (extended_symbol{s3, n7}.get_contract().value), u64max ) - + // --------------------- // void print(bool)const // Note: @@ -304,11 +294,15 @@ EOSIO_TEST_BEGIN(extended_symbol_type_test) CHECK_EQUAL( (extended_symbol{} < extended_symbol{s1, {}}), true ) CHECK_EQUAL( (extended_symbol{} < extended_symbol{s2, {}}), true ) CHECK_EQUAL( (extended_symbol{} < extended_symbol{s3, {}}), true ) - - silence_output(false); EOSIO_TEST_END int main(int argc, char* argv[]) { + bool verbose = false; + if( argc >= 2 && std::strcmp( argv[1], "-v" ) == 0 ) { + verbose = true; + } + silence_output(!verbose); + EOSIO_TEST(symbol_code_type_test); EOSIO_TEST(symbol_type_test); EOSIO_TEST(extended_symbol_type_test); diff --git a/tests/unit/system_tests.cpp b/tests/unit/system_tests.cpp index 016fd0d3ca..3fa9ce7a45 100644 --- a/tests/unit/system_tests.cpp +++ b/tests/unit/system_tests.cpp @@ -15,8 +15,6 @@ using eosio::check; // Definitions in `eosio.cdt/libraries/eosiolib/system.hpp` EOSIO_TEST_BEGIN(system_test) - silence_output(true); - // ------------------------------------ // inline void check(bool, const char*) CHECK_ASSERT( "asserted", []() { const char* str{"asserted"}; check(false, str);} ); @@ -43,11 +41,15 @@ EOSIO_TEST_BEGIN(system_test) CHECK_ASSERT("100", []() { check(false, 100);} ); CHECK_ASSERT("18446744073709551615", []() { check(false, 18446744073709551615ULL);} ); CHECK_ASSERT("18446744073709551615", []() { check(false, -1ULL);} ); - - silence_output(false); EOSIO_TEST_END int main(int argc, char* argv[]) { + bool verbose = false; + if( argc >= 2 && std::strcmp( argv[1], "-v" ) == 0 ) { + verbose = true; + } + silence_output(!verbose); + EOSIO_TEST(system_test); return has_failed(); } diff --git a/tests/unit/time_tests.cpp b/tests/unit/time_tests.cpp index 6eb8265924..d5d46ff2b0 100644 --- a/tests/unit/time_tests.cpp +++ b/tests/unit/time_tests.cpp @@ -26,8 +26,6 @@ static constexpr uint32_t u32max = numeric_limits::max(); // 429496729 // Definitions in `eosio.cdt/libraries/eosio/time.hpp` EOSIO_TEST_BEGIN(microseconds_type_test) -silence_output(true); - //// explicit microseconds(uint64_t)/int64_t count() CHECK_EQUAL( microseconds{}._count, 0ULL ) CHECK_EQUAL( microseconds{i64max}._count, i64max ) @@ -120,20 +118,16 @@ silence_output(true); CHECK_EQUAL( days(0LL), microseconds{0LL} ) CHECK_EQUAL( days(1LL), microseconds{24LL*60LL*60LL*1000000LL} ) CHECK_EQUAL( days(60LL), microseconds{24LL*60LL*60LL*60LL*1000000LL} ) - - silence_output(false); EOSIO_TEST_END // Definitions in `eosio.cdt/libraries/eosio/time.hpp` EOSIO_TEST_BEGIN(time_point_type_test) - silence_output(true); - static const microseconds ms0 { 0LL}; static const microseconds ms1 { 1LL}; static const microseconds msn1{-1LL}; static const microseconds ms_min{i64min}; static const microseconds ms_max{i64max}; - + //// explicit time_point(microseconds) // microseconds& time_since_epoch() CHECK_EQUAL( time_point{ms0}.time_since_epoch(), ms0 ) @@ -172,7 +166,7 @@ EOSIO_TEST_BEGIN(time_point_type_test) // time_point& operator+=(const microseconds&) CHECK_EQUAL( (time_point{ms0} += ms1), time_point{ms1} ) CHECK_EQUAL( (time_point{msn1} += ms1), time_point{ms0} ) - + // ------------------------------------------- // time_point& operator-=(const microseconds&) CHECK_EQUAL( (time_point{ms0} -= ms1), time_point{msn1} ) @@ -207,14 +201,10 @@ EOSIO_TEST_BEGIN(time_point_type_test) // bool operator>=(const time_point&) CHECK_EQUAL( (time_point{ms1} >= time_point{ms1}), true ) CHECK_EQUAL( (time_point{ms0} >= time_point{ms1}), false ) - - silence_output(false); EOSIO_TEST_END // Definitions in `eosio.cdt/libraries/eosio/time.hpp` EOSIO_TEST_BEGIN(time_point_sec_type_test) - silence_output(true); - static const microseconds ms0 { 0LL}; static const microseconds ms1 { 1LL}; static const microseconds msn1{-1LL}; @@ -255,7 +245,7 @@ EOSIO_TEST_BEGIN(time_point_sec_type_test) // operator time_point()const CHECK_EQUAL( time_point_sec{u32min}.operator time_point(), time_point{microseconds{static_cast(u32min)*1000000}} ) CHECK_EQUAL( time_point_sec{u32max}.operator time_point(), time_point{microseconds{static_cast(u32max)*1000000}} ) - + // ------------------------------------------- // time_point_sec operator=(const time_point&) CHECK_EQUAL( (time_point_sec{} = tp0), time_point_sec{} ) @@ -280,14 +270,14 @@ EOSIO_TEST_BEGIN(time_point_sec_type_test) CHECK_EQUAL( (time_point_sec{0} - microseconds{-1000000LL}), time_point{microseconds{ 1000000LL}} ) CHECK_EQUAL( (time_point_sec{1} - microseconds{ 1000000LL}), time_point{microseconds{ 0LL}} ) CHECK_EQUAL( (time_point_sec{1} - microseconds{-1000000LL}), time_point{microseconds{ 2000000LL}} ) - + // --------------------------------------------------------------------------- // friend microseconds operator-(const time_point_sec&, const time_point_sec&) CHECK_EQUAL( (time_point_sec{0} - time_point_sec{0}), microseconds{ 0LL} ) CHECK_EQUAL( (time_point_sec{0} - time_point_sec{1}), microseconds{-1000000LL} ) CHECK_EQUAL( (time_point_sec{1} - time_point_sec{0}), microseconds{ 1000000LL} ) CHECK_EQUAL( (time_point_sec{1} - time_point_sec{1}), microseconds{ 0LL} ) - + // ----------------------------------------------------------------------- // friend microseconds operator-(const time_point&, const time_point_sec&) CHECK_EQUAL( (time_point{microseconds{0}} - time_point_sec{0}), microseconds{ 0LL} ) @@ -374,14 +364,10 @@ EOSIO_TEST_BEGIN(time_point_sec_type_test) // friend bool operator>=(const time_point_sec&, const time_point_sec&) CHECK_EQUAL( (time_point_sec{1} >= time_point_sec{1}), true ) CHECK_EQUAL( (time_point_sec{1} >= time_point_sec{2}), false ) - - silence_output(false); EOSIO_TEST_END // Definitions in `eosio.cdt/libraries/eosio/time.hpp` EOSIO_TEST_BEGIN(block_timestamp_type_test) - silence_output(true); - static const int64_t bt_epoch{946684800000LL}; static const microseconds ms0{bt_epoch*1000}; @@ -402,7 +388,7 @@ EOSIO_TEST_BEGIN(block_timestamp_type_test) CHECK_EQUAL( block_timestamp{}.slot, 0 ) CHECK_EQUAL( block_timestamp{u32min}.slot, u32min ) CHECK_EQUAL( block_timestamp{u32max}.slot, u32max ) - + //// block_timestamp(const time_point&) // void set_time_point(const time_point&) CHECK_EQUAL( block_timestamp{tp0}.slot, 0 ) @@ -436,7 +422,7 @@ EOSIO_TEST_BEGIN(block_timestamp_type_test) CHECK_EQUAL( block_timestamp{1}.to_time_point(), time_point{microseconds{(1*500+bt_epoch)*1000}} ) CHECK_EQUAL( block_timestamp{2}.to_time_point(), time_point{microseconds{(2*500+bt_epoch)*1000}} ) CHECK_EQUAL( block_timestamp{3}.to_time_point(), time_point{microseconds{(3*500+bt_epoch)*1000}} ) - + // -------------------------- // operator time_point()const CHECK_EQUAL( block_timestamp{1}.operator time_point(), time_point{microseconds{(1*500+bt_epoch)*1000}} ) @@ -482,11 +468,15 @@ EOSIO_TEST_BEGIN(block_timestamp_type_test) // bool operator>=(const block_timestamp&) CHECK_EQUAL( block_timestamp{1} >= block_timestamp{1}, true ) CHECK_EQUAL( block_timestamp{1} >= block_timestamp{2}, false ) - - silence_output(false); EOSIO_TEST_END int main(int argc, char* argv[]) { + bool verbose = false; + if( argc >= 2 && std::strcmp( argv[1], "-v" ) == 0 ) { + verbose = true; + } + silence_output(!verbose); + EOSIO_TEST(microseconds_type_test); EOSIO_TEST(time_point_type_test); EOSIO_TEST(time_point_sec_type_test); diff --git a/tests/unit/varint_tests.cpp b/tests/unit/varint_tests.cpp index 0eceda3122..f79313ddf0 100644 --- a/tests/unit/varint_tests.cpp +++ b/tests/unit/varint_tests.cpp @@ -23,13 +23,11 @@ static constexpr int32_t i32max = numeric_limits::max(); // 2147483647 // Defined in `eosio.cdt/libraries/eosio/varint.hpp` EOSIO_TEST_BEGIN(unsigned_int_type_test) - silence_output(false); - //// unsigned_int(uint32_t) CHECK_EQUAL( unsigned_int{}.value, 0 ) CHECK_EQUAL( unsigned_int{u32min}.value, 0 ) CHECK_EQUAL( unsigned_int{u32max}.value, 4294967295 ) - + //// unsigned_int(T) CHECK_EQUAL( unsigned_int{uint8_t{0}}.value, 0 ) CHECK_EQUAL( unsigned_int{uint16_t{1}}.value, 1 ) @@ -116,21 +114,17 @@ EOSIO_TEST_BEGIN(unsigned_int_type_test) char datastream_buffer[buffer_size]; // Buffer for the datastream to point to datastream ds{datastream_buffer, buffer_size}; - + static const unsigned_int cui{42}; unsigned_int ui{}; ds << cui; ds.seekp(0); ds >> ui; CHECK_EQUAL( cui, ui) - - silence_output(false); EOSIO_TEST_END // Defined in `eosio.cdt/libraries/eosio/varint.hpp` EOSIO_TEST_BEGIN(signed_int_type_test) - silence_output(false); - //// signed_int(uint32_t) CHECK_EQUAL( signed_int{}.value, 0 ) CHECK_EQUAL( signed_int{i32min}.value, -2147483648 ) @@ -256,11 +250,15 @@ EOSIO_TEST_BEGIN(signed_int_type_test) CHECK_EQUAL( b, bb ) CHECK_EQUAL( c, cc ) CHECK_EQUAL( d, dd ) - - silence_output(false); EOSIO_TEST_END int main(int argc, char* argv[]) { + bool verbose = false; + if( argc >= 2 && std::strcmp( argv[1], "-v" ) == 0 ) { + verbose = true; + } + silence_output(!verbose); + EOSIO_TEST(unsigned_int_type_test) EOSIO_TEST(signed_int_type_test); return has_failed(); From 379c4a181ced3645cc7b52d15fea1f26c94c06c8 Mon Sep 17 00:00:00 2001 From: arhag Date: Wed, 28 Aug 2019 15:43:22 -0400 Subject: [PATCH 52/99] revert changes to CHECK_ASSERT and CHECK_PRINT --- libraries/native/native/eosio/tester.hpp | 4 ++-- libraries/native/tester.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/native/native/eosio/tester.hpp b/libraries/native/native/eosio/tester.hpp index 1a232e56b5..93a5e9e432 100644 --- a/libraries/native/native/eosio/tester.hpp +++ b/libraries/native/native/eosio/tester.hpp @@ -81,13 +81,13 @@ inline bool expect_print(bool check, const std::string& li, const char (&expecte } #define CHECK_ASSERT(...) \ - ___has_failed |= expect_assert(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); + ___has_failed &= expect_assert(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define REQUIRE_ASSERT(...) \ expect_assert(false, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define CHECK_PRINT(...) \ - ___has_failed |= expect_print(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); + ___has_failed &= expect_print(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define REQUIRE_PRINT(...) \ expect_print(false, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); diff --git a/libraries/native/tester.hpp b/libraries/native/tester.hpp index 17a3840201..846ff38b86 100644 --- a/libraries/native/tester.hpp +++ b/libraries/native/tester.hpp @@ -83,13 +83,13 @@ inline bool expect_print(bool check, const std::string& li, const char (&expecte } #define CHECK_ASSERT(...) \ - ___has_failed |= expect_assert(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); + ___has_failed &= expect_assert(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define REQUIRE_ASSERT(...) \ expect_assert(false, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define CHECK_PRINT(...) \ - ___has_failed |= expect_print(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); + ___has_failed &= expect_print(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define REQUIRE_PRINT(...) \ expect_print(false, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); From c98f04bc17b754335cba7e5d1eab7ceba9a9137e Mon Sep 17 00:00:00 2001 From: arhag Date: Thu, 29 Aug 2019 13:05:51 -0400 Subject: [PATCH 53/99] fix CHECK_ASSERT and CHECK_PRINT macros; fix bug in symbol::print; correct symbol_tests unit test --- libraries/eosiolib/core/eosio/symbol.hpp | 2 +- libraries/native/native/eosio/tester.hpp | 6 +++--- libraries/native/tester.hpp | 6 +++--- tests/unit/symbol_tests.cpp | 10 ++++------ 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/libraries/eosiolib/core/eosio/symbol.hpp b/libraries/eosiolib/core/eosio/symbol.hpp index 6e1c180bd0..b9cb7704ae 100644 --- a/libraries/eosiolib/core/eosio/symbol.hpp +++ b/libraries/eosiolib/core/eosio/symbol.hpp @@ -302,7 +302,7 @@ namespace eosio { char buffer[7]; auto end = code().write_as_string( buffer, buffer + sizeof(buffer) ); if( buffer < end ) - ::eosio::print( buffer, (end-buffer) ); + printl( buffer, (end-buffer) ); } /** diff --git a/libraries/native/native/eosio/tester.hpp b/libraries/native/native/eosio/tester.hpp index 93a5e9e432..e824ef4a83 100644 --- a/libraries/native/native/eosio/tester.hpp +++ b/libraries/native/native/eosio/tester.hpp @@ -66,7 +66,7 @@ inline bool expect_print(bool check, const std::string& li, Pred&& pred, F&& fun if (!check) eosio::check(passed, std::string("error : wrong print message {"+li+"}").c_str()); if (!passed) - eosio::print("error : wrong print message9 {"+li+"}\n"); + eosio::print("error : wrong print message {"+li+"}\n"); silence_output(disable_out); return passed; } @@ -81,13 +81,13 @@ inline bool expect_print(bool check, const std::string& li, const char (&expecte } #define CHECK_ASSERT(...) \ - ___has_failed &= expect_assert(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); + ___has_failed |= !expect_assert(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define REQUIRE_ASSERT(...) \ expect_assert(false, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define CHECK_PRINT(...) \ - ___has_failed &= expect_print(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); + ___has_failed |= !expect_print(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define REQUIRE_PRINT(...) \ expect_print(false, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); diff --git a/libraries/native/tester.hpp b/libraries/native/tester.hpp index 846ff38b86..21ce01f33b 100644 --- a/libraries/native/tester.hpp +++ b/libraries/native/tester.hpp @@ -68,7 +68,7 @@ inline bool expect_print(bool check, const std::string& li, Pred&& pred, F&& fun if (!check) eosio_assert(passed, std::string("error : wrong print message {"+li+"}").c_str()); if (!passed) - eosio::print("error : wrong print message9 {"+li+"}\n"); + eosio::print("error : wrong print message {"+li+"}\n"); silence_output(disable_out); return passed; } @@ -83,13 +83,13 @@ inline bool expect_print(bool check, const std::string& li, const char (&expecte } #define CHECK_ASSERT(...) \ - ___has_failed &= expect_assert(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); + ___has_failed |= !expect_assert(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define REQUIRE_ASSERT(...) \ expect_assert(false, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define CHECK_PRINT(...) \ - ___has_failed &= expect_print(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); + ___has_failed |= !expect_print(true, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); #define REQUIRE_PRINT(...) \ expect_print(false, std::string(__FILE__)+":"+__func__+":"+(std::to_string(__LINE__)), __VA_ARGS__); diff --git a/tests/unit/symbol_tests.cpp b/tests/unit/symbol_tests.cpp index 1b564da0f7..a5a363a243 100644 --- a/tests/unit/symbol_tests.cpp +++ b/tests/unit/symbol_tests.cpp @@ -192,12 +192,10 @@ EOSIO_TEST_BEGIN(symbol_type_test) // --------------------- // void print(bool)const - // Note: - // This function prints the length of the symbol at the very end - CHECK_PRINT( "0,A1", [&](){symbol{"A", 0}.print(true);} ); - CHECK_PRINT( "0,Z1", [&](){symbol{"Z", 0}.print(true);} ); - CHECK_PRINT( "255,AAAAAAA7", [&](){symbol{"AAAAAAA", 255}.print(true);} ); - CHECK_PRINT( "255,ZZZZZZZ7", [&](){symbol{"ZZZZZZZ", 255}.print(true);} ); + CHECK_PRINT( "0,A", [&](){symbol{"A", 0}.print(true);} ); + CHECK_PRINT( "0,Z", [&](){symbol{"Z", 0}.print(true);} ); + CHECK_PRINT( "255,AAAAAAA", [&](){symbol{"AAAAAAA", 255}.print(true);} ); + CHECK_PRINT( "255,ZZZZZZZ", [&](){symbol{"ZZZZZZZ", 255}.print(true);} ); // -------------------------------------------------------------- // friend constexpr bool operator==(const symbol&, const symbol&) From d7131bf836a8b2df85269c18196cca544e0088e0 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Mon, 2 Sep 2019 08:29:56 +0000 Subject: [PATCH 54/99] Handle `-x` compile option --- tools/include/compiler_options.hpp.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/include/compiler_options.hpp.in b/tools/include/compiler_options.hpp.in index e860c2fc25..99684e15f6 100644 --- a/tools/include/compiler_options.hpp.in +++ b/tools/include/compiler_options.hpp.in @@ -635,6 +635,10 @@ static Options CreateOptions(bool add_defaults=true) { } if(g_opt) copts.emplace_back("-g"); + if (!x_opt.empty()) { + // x_opt should precede input files + copts.insert(copts.begin(), "-x"+x_opt); + } if (color_diag_opt) { copts.emplace_back("-fcolor-diagnostics"); } From e8e009b2aaa86d9433121b6d69aff426a691a7cb Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Mon, 2 Sep 2019 08:31:07 +0000 Subject: [PATCH 55/99] Enforce cxx build when compiling c with eosio-cpp --- tools/cc/eosio-cpp.cpp.in | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/cc/eosio-cpp.cpp.in b/tools/cc/eosio-cpp.cpp.in index d8a1cdd288..62087d434e 100644 --- a/tools/cc/eosio-cpp.cpp.in +++ b/tools/cc/eosio-cpp.cpp.in @@ -113,6 +113,8 @@ void generate(const std::vector& base_options, std::string input, s options.push_back(input); // don't remove oddity of CommonOptionsParser? options.push_back(input); options.push_back("--"); + if (llvm::sys::path::extension(input).equals(".c")) + options.push_back("-xc++"); for (size_t i=1; i < base_options.size(); i++) { options.push_back(base_options[i]); } @@ -217,6 +219,9 @@ int main(int argc, const char **argv) { new_opts.insert(new_opts.begin(), "-o "+output); outputs.push_back(output); + if (llvm::sys::path::extension(input).equals(".c")) + new_opts.insert(new_opts.begin(), "-xc++"); + if (!eosio::cdt::environment::exec_subprogram("clang-7", new_opts)) { llvm::sys::fs::remove(tmp_file); return -1; From e7515cd08a3690607196f0b1967dab3bd0eafaba Mon Sep 17 00:00:00 2001 From: arhag Date: Tue, 3 Sep 2019 21:53:56 -0400 Subject: [PATCH 56/99] reimplement asset::to_string() and asset::print() by using the new asset::write_as_string function; update name::write_as_string and symbol_code::write_as_string to be more flexible; augment asset_tests --- libraries/eosiolib/asset.hpp | 90 ++++++++++++++---------- libraries/eosiolib/core/eosio/asset.hpp | 88 +++++++++++++---------- libraries/eosiolib/core/eosio/name.hpp | 17 +++-- libraries/eosiolib/core/eosio/powers.hpp | 88 +++++++++++++++++++++++ libraries/eosiolib/core/eosio/symbol.hpp | 16 +++-- libraries/eosiolib/eosiolib.cpp | 69 ++++++++++++++++++ libraries/eosiolib/name.hpp | 17 +++-- libraries/eosiolib/symbol.hpp | 16 +++-- tests/unit/asset_tests.cpp | 22 ++++-- 9 files changed, 320 insertions(+), 103 deletions(-) create mode 100644 libraries/eosiolib/core/eosio/powers.hpp diff --git a/libraries/eosiolib/asset.hpp b/libraries/eosiolib/asset.hpp index 681804fb89..6c7528e3fc 100644 --- a/libraries/eosiolib/asset.hpp +++ b/libraries/eosiolib/asset.hpp @@ -10,12 +10,15 @@ #warning " is deprecated use " namespace eosio { - /** - * Defines C++ API for managing assets - * @addtogroup asset Asset C++ API - * @ingroup core - * @{ - */ + + char* write_decimal( char* begin, char* end, bool dry_run, uint64_t number, uint8_t num_decimal_places, bool negative ); + + /** + * Defines C++ API for managing assets + * @addtogroup asset Asset C++ API + * @ingroup core + * @{ + */ /** * @struct Stores information for owner of asset @@ -315,42 +318,49 @@ namespace eosio { } /** - * %asset to std::string + * Writes the asset as a string to the provided char buffer * - * @brief %asset to std::string - */ - std::string to_string()const { - int64_t p = (int64_t)symbol.precision(); - int64_t p10 = 1; - bool negative = false; - int64_t invert = 1; - - while( p > 0 ) { - p10 *= 10; --p; + * @brief Writes the asset as a string to the provided char buffer + * @pre is_valid() == true + * @pre The range [begin, end) must be a valid range of memory to write to. + * @param begin - The start of the char buffer + * @param end - Just past the end of the char buffer + * @param dry_run - If true, do not actually write anything into the range. + * @return char* - Just past the end of the last character that would be written assuming dry_run == false and end was large enough to provide sufficient space. (Meaning only applies if returned pointer >= begin.) + * @post If the output string fits within the range [begin, end) and dry_run == false, the range [begin, returned pointer) contains the string representation of the asset. Nothing is written if dry_run == true or returned pointer > end (insufficient space) or if returned pointer < begin (overflow in calculating desired end). + */ + char* write_as_string( char* begin, char* end, bool dry_run = false )const { + bool negative = (amount < 0); + uint64_t abs_amount = static_cast(negative ? -amount : amount); + // 0 <= abs_amount <= std::numeric_limits::max() < 10^19 < std::numeric_limits::max() + + uint8_t precision = symbol.precision(); + + int sufficient_size = std::max(static_cast(precision), 19) + 11; + if( dry_run || (begin + sufficient_size < begin) || (begin + sufficient_size > end) ) { + char* start_of_symbol = write_decimal( begin, end, true, abs_amount, precision, negative ) + 1; + char* actual_end = symbol.code().write_as_string( start_of_symbol, end, true ); + if( dry_run || (actual_end < begin) || (actual_end > end) ) return actual_end; } - p = (int64_t)symbol.precision(); - char fraction[p+1]; - fraction[p] = '\0'; + char* end_of_number = write_decimal( begin, end, false, abs_amount, precision, negative ); + *(end_of_number) = ' ';; - if (amount < 0) { - invert = -1; - negative = true; - } + return symbol.code().write_as_string( end_of_number + 1, end ); + } - auto change = (amount % p10) * invert; + /** + * %asset to std::string + * + * @brief %asset to std::string + */ + std::string to_string()const { + int buffer_size = std::max(static_cast(symbol.precision()), 19) + 11; + char buffer[buffer_size]; + char* end = write_as_string( buffer, buffer + buffer_size ); + check( end <= buffer + buffer_size, "insufficient space in buffer" ); // should never fail - for( int64_t i = p -1; i >= 0; --i ) { - fraction[i] = (change % 10) + '0'; - change /= 10; - } - char str[p+32]; - snprintf(str, sizeof(str), "%lld%s%s %s", - (int64_t)(amount/p10), - (fraction[0]) ? "." : "", - fraction, - symbol.code().to_string().c_str()); - return {str}; + return {buffer, end}; } /** @@ -359,7 +369,13 @@ namespace eosio { * @brief %Print the asset */ void print()const { - eosio::print(to_string()); + int buffer_size = std::max(static_cast(symbol.precision()), 19) + 11; + char buffer[buffer_size]; + char* end = write_as_string( buffer, buffer + buffer_size ); + check( end <= buffer + buffer_size, "insufficient space in buffer" ); // should never fail + + if( buffer < end ) + printl( buffer, (end-buffer) ); } EOSLIB_SERIALIZE( asset, (amount)(symbol) ) diff --git a/libraries/eosiolib/core/eosio/asset.hpp b/libraries/eosiolib/core/eosio/asset.hpp index c6d276c9bd..b0635bb779 100644 --- a/libraries/eosiolib/core/eosio/asset.hpp +++ b/libraries/eosiolib/core/eosio/asset.hpp @@ -9,11 +9,14 @@ #include namespace eosio { - /** - * @defgroup asset Asset - * @ingroup core - * @brief Defines C++ API for managing assets - */ + + char* write_decimal( char* begin, char* end, bool dry_run, uint64_t number, uint8_t num_decimal_places, bool negative ); + + /** + * @defgroup asset Asset + * @ingroup core + * @brief Defines C++ API for managing assets + */ /** * Stores information for owner of asset @@ -318,42 +321,49 @@ namespace eosio { /// @endcond /** - * %asset to std::string + * Writes the asset as a string to the provided char buffer * - * @brief %asset to std::string - */ - std::string to_string()const { - int64_t p = (int64_t)symbol.precision(); - int64_t p10 = 1; - bool negative = false; - int64_t invert = 1; - - while( p > 0 ) { - p10 *= 10; --p; + * @brief Writes the asset as a string to the provided char buffer + * @pre is_valid() == true + * @pre The range [begin, end) must be a valid range of memory to write to. + * @param begin - The start of the char buffer + * @param end - Just past the end of the char buffer + * @param dry_run - If true, do not actually write anything into the range. + * @return char* - Just past the end of the last character that would be written assuming dry_run == false and end was large enough to provide sufficient space. (Meaning only applies if returned pointer >= begin.) + * @post If the output string fits within the range [begin, end) and dry_run == false, the range [begin, returned pointer) contains the string representation of the asset. Nothing is written if dry_run == true or returned pointer > end (insufficient space) or if returned pointer < begin (overflow in calculating desired end). + */ + char* write_as_string( char* begin, char* end, bool dry_run = false )const { + bool negative = (amount < 0); + uint64_t abs_amount = static_cast(negative ? -amount : amount); + // 0 <= abs_amount <= std::numeric_limits::max() < 10^19 < std::numeric_limits::max() + + uint8_t precision = symbol.precision(); + + int sufficient_size = std::max(static_cast(precision), 19) + 11; + if( dry_run || (begin + sufficient_size < begin) || (begin + sufficient_size > end) ) { + char* start_of_symbol = write_decimal( begin, end, true, abs_amount, precision, negative ) + 1; + char* actual_end = symbol.code().write_as_string( start_of_symbol, end, true ); + if( dry_run || (actual_end < begin) || (actual_end > end) ) return actual_end; } - p = (int64_t)symbol.precision(); - char fraction[p+1]; - fraction[p] = '\0'; + char* end_of_number = write_decimal( begin, end, false, abs_amount, precision, negative ); + *(end_of_number) = ' ';; - if (amount < 0) { - invert = -1; - negative = true; - } + return symbol.code().write_as_string( end_of_number + 1, end ); + } - auto change = (amount % p10) * invert; + /** + * %asset to std::string + * + * @brief %asset to std::string + */ + std::string to_string()const { + int buffer_size = std::max(static_cast(symbol.precision()), 19) + 11; + char buffer[buffer_size]; + char* end = write_as_string( buffer, buffer + buffer_size ); + check( end <= buffer + buffer_size, "insufficient space in buffer" ); // should never fail - for( int64_t i = p -1; i >= 0; --i ) { - fraction[i] = (change % 10) + '0'; - change /= 10; - } - char str[p+32]; - snprintf(str, sizeof(str), "%lld%s%s %s", - (int64_t)(amount/p10), - (fraction[0]) ? "." : "", - fraction, - symbol.code().to_string().c_str()); - return {str}; + return {buffer, end}; } /** @@ -362,7 +372,13 @@ namespace eosio { * @brief %Print the asset */ void print()const { - ::eosio::print(to_string()); + int buffer_size = std::max(static_cast(symbol.precision()), 19) + 11; + char buffer[buffer_size]; + char* end = write_as_string( buffer, buffer + buffer_size ); + check( end <= buffer + buffer_size, "insufficient space in buffer" ); // should never fail + + if( buffer < end ) + printl( buffer, (end-buffer) ); } EOSLIB_SERIALIZE( asset, (amount)(symbol) ) diff --git a/libraries/eosiolib/core/eosio/name.hpp b/libraries/eosiolib/core/eosio/name.hpp index 40337893a4..525377104a 100644 --- a/libraries/eosiolib/core/eosio/name.hpp +++ b/libraries/eosiolib/core/eosio/name.hpp @@ -187,21 +187,24 @@ namespace eosio { /** * Writes the %name as a string to the provided char buffer * - * @pre Appropriate Size Precondition: (begin + 13) <= end and (begin + 13) does not overflow - * @pre Valid Memory Region Precondition: The range [begin, end) must be a valid range of memory to write to. + * @pre The range [begin, end) must be a valid range of memory to write to. * @param begin - The start of the char buffer * @param end - Just past the end of the char buffer - * @return char* - Just past the end of the last character written (returns begin if the Appropriate Size Precondition is not satisfied) - * @post If the Appropriate Size Precondition is satisfied, the range [begin, returned pointer) contains the string representation of the %name. + * @param dry_run - If true, do not actually write anything into the range. + * @return char* - Just past the end of the last character that would be written assuming dry_run == false and end was large enough to provide sufficient space. (Meaning only applies if returned pointer >= begin.) + * @post If the output string fits within the range [begin, end) and dry_run == false, the range [begin, returned pointer) contains the string representation of the %name. Nothing is written if dry_run == true or returned pointer > end (insufficient space) or if returned pointer < begin (overflow in calculating desired end). */ - char* write_as_string( char* begin, char* end )const { + char* write_as_string( char* begin, char* end, bool dry_run = false )const { static const char* charmap = ".12345abcdefghijklmnopqrstuvwxyz"; constexpr uint64_t mask = 0xF800000000000000ull; - if( (begin + 13) < begin || (begin + 13) > end ) return begin; + if( dry_run || (begin + 13 < begin) || (begin + 13 > end) ) { + char* actual_end = begin + length(); + if( dry_run || (actual_end < begin) || (actual_end > end) ) return actual_end; + } auto v = value; - for( auto i = 0; i < 13; ++i, v <<= 5 ) { + for( auto i = 0; i < 13; ++i, v <<= 5 ) { if( v == 0 ) return begin; auto indx = (v & mask) >> (i == 12 ? 60 : 59); diff --git a/libraries/eosiolib/core/eosio/powers.hpp b/libraries/eosiolib/core/eosio/powers.hpp new file mode 100644 index 0000000000..b0d53369d4 --- /dev/null +++ b/libraries/eosiolib/core/eosio/powers.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include "check.hpp" + +#include +#include +#include + +namespace eosio { + + namespace detail { + + template + constexpr auto generate_array_helper( Generator&& g, std::index_sequence ) + -> std::array + { + return {{g(Is, sizeof...(Is))...}}; + } + + template + constexpr auto generate_array( Generator&& g ) { + return generate_array_helper( std::forward(g), std::make_index_sequence{} ); + } + + template + struct largest_power_helper { + private: + static_assert( std::is_integral_v && std::is_unsigned_v &&!std::is_same_v ); + static_assert( Base > 1 ); + constexpr static T next_value = Value * Base; + using next = largest_power_helper; + public: + constexpr static T value = next::value; + constexpr static uint8_t exponent = next::exponent; + }; + + template + struct largest_power_helper { + private: + static_assert( std::is_integral_v && std::is_unsigned_v &&!std::is_same_v ); + static_assert( Base > 1 ); + static_assert( Exponent < 255 ); + public: + constexpr static T value = Value; + constexpr static uint8_t exponent = Exponent; + }; + + template + struct largest_power { + private: + using helper = largest_power_helper; + public: + constexpr static T value = helper::value; + constexpr static uint8_t exponent = helper::exponent; + }; + + template + constexpr T pow( T base, uint8_t exponent ) { + if( base <= 1 ) check( false, "base must be at least 2" ); + T prior = 1; + T result = prior; + for( uint8_t i = 0; i < exponent; ++i, prior = result ) { + result = prior * base; + if( result <= prior ) check( false, "overflow" ); + } + return result; + } + + template + constexpr T pow_generator( std::size_t i, std::size_t ) { + return pow( Base, static_cast(i) ); + } + + } + + template + inline constexpr auto powers_of_base = detail::generate_array::exponent + 1>( detail::pow_generator ); + + /** @returns Base^exponent */ + template + constexpr T pow( uint8_t exponent ) { + const auto& lookup_table = powers_of_base; + if( exponent >= lookup_table.size() ) check( false, "overflow" ); + + return lookup_table[exponent]; + } + +} diff --git a/libraries/eosiolib/core/eosio/symbol.hpp b/libraries/eosiolib/core/eosio/symbol.hpp index b9cb7704ae..631a9272f1 100644 --- a/libraries/eosiolib/core/eosio/symbol.hpp +++ b/libraries/eosiolib/core/eosio/symbol.hpp @@ -125,17 +125,21 @@ namespace eosio { * * * @brief Writes the symbol_code as a string to the provided char buffer - * @pre Appropriate Size Precondition: (begin + 7) <= end and (begin + 7) does not overflow - * @pre Valid Memory Region Precondition: The range [begin, end) must be a valid range of memory to write to. + * @pre is_valid() == true + * @pre The range [begin, end) must be a valid range of memory to write to. * @param begin - The start of the char buffer * @param end - Just past the end of the char buffer - * @return char* - Just past the end of the last character written (returns begin if the Appropriate Size Precondition is not satisfied) - * @post If the Appropriate Size Precondition is satisfied, the range [begin, returned pointer) contains the string representation of the symbol_code. + * @param dry_run - If true, do not actually write anything into the range. + * @return char* - Just past the end of the last character that would be written assuming dry_run == false and end was large enough to provide sufficient space. (Meaning only applies if returned pointer >= begin.) + * @post If the output string fits within the range [begin, end) and dry_run == false, the range [begin, returned pointer) contains the string representation of the symbol_code. Nothing is written if dry_run == true or returned pointer > end (insufficient space) or if returned pointer < begin (overflow in calculating desired end). */ - char* write_as_string( char* begin, char* end )const { + char* write_as_string( char* begin, char* end, bool dry_run = false )const { constexpr uint64_t mask = 0xFFull; - if( (begin + 7) < begin || (begin + 7) > end ) return begin; + if( dry_run || (begin + 7 < begin) || (begin + 7 > end) ) { + char* actual_end = begin + length(); + if( dry_run || (actual_end < begin) || (actual_end > end) ) return actual_end; + } auto v = value; for( auto i = 0; i < 7; ++i, v >>= 8 ) { diff --git a/libraries/eosiolib/eosiolib.cpp b/libraries/eosiolib/eosiolib.cpp index 0f36c26d5e..b92bb10130 100644 --- a/libraries/eosiolib/eosiolib.cpp +++ b/libraries/eosiolib/eosiolib.cpp @@ -1,7 +1,10 @@ #include "core/eosio/datastream.hpp" +#include "core/eosio/powers.hpp" #include "contracts/eosio/system.hpp" #include "contracts/eosio/privileged.hpp" +#include + namespace eosio { extern "C" { __attribute__((eosio_wasm_import)) @@ -58,4 +61,70 @@ namespace eosio { return active_prods; } + // powers.hpp + template const std::array powers_of_base<10, uint64_t>; + + /** + * Writes a number as a string to the provided char buffer + * + * @brief Writes number x 10^(-num_decimal_places) (optionally negative) as a string to the provided char buffer + * @pre The range [begin, end) must be a valid range of memory to write to. + * @param begin - The start of the char buffer + * @param end - Just past the end of the char buffer + * @param dry_run - If true, do not actually write anything into the range. + * @param number - The number to print before shifting the decimal point to the left by num_decimal_places. + * @param num_decimal_places - The number of decimal places to shift the decimal point. + * @param negative - Whether to print a minus sign in the front. + * @return char* - Just past the end of the last character that would be written assuming dry_run == false and end was large enough to provide sufficient space. (Meaning only applies if returned pointer >= begin.) + * @post If the output string fits within the range [begin, end), the range [begin, returned pointer) contains the string representation of the number. Nothing is written if dry_run == true or returned pointer > end (insufficient space) or if returned pointer < begin (overflow in calculating desired end). + */ + char* write_decimal( char* begin, char* end, bool dry_run, uint64_t number, uint8_t num_decimal_places, bool negative ) { + constexpr static uint8_t log10_max_uint64 = powers_of_base<10, uint64_t>.size() - 1; // 19 + const auto& powers_of_ten = powers_of_base<10, uint64_t>; + + uint8_t num_digits = (std::upper_bound( powers_of_ten.begin(), powers_of_ten.end(), number ) - powers_of_ten.begin()); // num_digits == 0 iff number == 0 + // 0 <= num_digits <= 20 + + uint16_t characters_needed = std::max( num_digits, num_decimal_places ); + uint16_t decimal_point_pos = num_digits; + if( num_decimal_places >= num_digits ) { + ++characters_needed; // space needing for additional leading zero digit + decimal_point_pos = 1; + } else { + decimal_point_pos -= num_decimal_places; + } + if( num_decimal_places > 0 ) ++characters_needed; // space for decimal point + uint16_t after_minus_pos = 0; + if( negative ) { + ++characters_needed; // space for minus sign + ++after_minus_pos; + ++decimal_point_pos; + } + // 1 <= characters_needed <= 258 + // 1 <= decimal_point_pos <= num_digits + 1 <= 21 + + char* actual_end = begin + characters_needed; + if( dry_run || (actual_end < begin) || (actual_end > end) ) return actual_end; + + int i = characters_needed - 1; + for( ; number > 0 && i > decimal_point_pos; --i ) { + *(begin + i) = (number % 10) + '0'; + number /= 10; + } + for( ; i > decimal_point_pos; --i ) { + *(begin + i) = '0'; + } + if( i == decimal_point_pos ) { + *(begin + i) = '.'; + --i; + } + for( ; i >= after_minus_pos; --i ) { + *(begin + i) = (number % 10) + '0'; + number /= 10; + } + if( i == 0 ) *(begin + i) = '-'; + + return actual_end; + } + } // namespace eosio diff --git a/libraries/eosiolib/name.hpp b/libraries/eosiolib/name.hpp index 05efdd15e7..33f0783c42 100644 --- a/libraries/eosiolib/name.hpp +++ b/libraries/eosiolib/name.hpp @@ -179,21 +179,24 @@ namespace eosio { /** * Writes the %name as a string to the provided char buffer * - * @pre Appropriate Size Precondition: (begin + 13) <= end and (begin + 13) does not overflow - * @pre Valid Memory Region Precondition: The range [begin, end) must be a valid range of memory to write to. + * @pre The range [begin, end) must be a valid range of memory to write to. * @param begin - The start of the char buffer * @param end - Just past the end of the char buffer - * @return char* - Just past the end of the last character written (returns begin if the Appropriate Size Precondition is not satisfied) - * @post If the Appropriate Size Precondition is satisfied, the range [begin, returned pointer) contains the string representation of the %name. + * @param dry_run - If true, do not actually write anything into the range. + * @return char* - Just past the end of the last character that would be written assuming dry_run == false and end was large enough to provide sufficient space. (Meaning only applies if returned pointer >= begin.) + * @post If the output string fits within the range [begin, end) and dry_run == false, the range [begin, returned pointer) contains the string representation of the %name. Nothing is written if dry_run == true or returned pointer > end (insufficient space) or if returned pointer < begin (overflow in calculating desired end). */ - char* write_as_string( char* begin, char* end )const { + char* write_as_string( char* begin, char* end, bool dry_run = false )const { static const char* charmap = ".12345abcdefghijklmnopqrstuvwxyz"; constexpr uint64_t mask = 0xF800000000000000ull; - if( (begin + 13) < begin || (begin + 13) > end ) return begin; + if( dry_run || (begin + 13 < begin) || (begin + 13 > end) ) { + char* actual_end = begin + length(); + if( dry_run || (actual_end < begin) || (actual_end > end) ) return actual_end; + } auto v = value; - for( auto i = 0; i < 13; ++i, v <<= 5 ) { + for( auto i = 0; i < 13; ++i, v <<= 5 ) { if( v == 0 ) return begin; auto indx = (v & mask) >> (i == 12 ? 60 : 59); diff --git a/libraries/eosiolib/symbol.hpp b/libraries/eosiolib/symbol.hpp index 0c736a8c88..3a8d265118 100644 --- a/libraries/eosiolib/symbol.hpp +++ b/libraries/eosiolib/symbol.hpp @@ -126,17 +126,21 @@ namespace eosio { * * * @brief Writes the symbol_code as a string to the provided char buffer - * @pre Appropriate Size Precondition: (begin + 7) <= end and (begin + 7) does not overflow - * @pre Valid Memory Region Precondition: The range [begin, end) must be a valid range of memory to write to. + * @pre is_valid() == true + * @pre The range [begin, end) must be a valid range of memory to write to. * @param begin - The start of the char buffer * @param end - Just past the end of the char buffer - * @return char* - Just past the end of the last character written (returns begin if the Appropriate Size Precondition is not satisfied) - * @post If the Appropriate Size Precondition is satisfied, the range [begin, returned pointer) contains the string representation of the symbol_code. + * @param dry_run - If true, do not actually write anything into the range. + * @return char* - Just past the end of the last character that would be written assuming dry_run == false and end was large enough to provide sufficient space. (Meaning only applies if returned pointer >= begin.) + * @post If the output string fits within the range [begin, end) and dry_run == false, the range [begin, returned pointer) contains the string representation of the symbol_code. Nothing is written if dry_run == true or returned pointer > end (insufficient space) or if returned pointer < begin (overflow in calculating desired end). */ - char* write_as_string( char* begin, char* end )const { + char* write_as_string( char* begin, char* end, bool dry_run = false )const { constexpr uint64_t mask = 0xFFull; - if( (begin + 7) < begin || (begin + 7) > end ) return begin; + if( dry_run || (begin + 7 < begin) || (begin + 7 > end) ) { + char* actual_end = begin + length(); + if( dry_run || (actual_end < begin) || (actual_end > end) ) return actual_end; + } auto v = value; for( auto i = 0; i < 7; ++i, v >>= 8 ) { diff --git a/tests/unit/asset_tests.cpp b/tests/unit/asset_tests.cpp index 841e76720d..e52515ca48 100644 --- a/tests/unit/asset_tests.cpp +++ b/tests/unit/asset_tests.cpp @@ -122,15 +122,29 @@ EOSIO_TEST_BEGIN(asset_type_test) CHECK_EQUAL( (asset{ 1LL, sym_no_prec}.to_string()), "1 SYMBOLL" ) CHECK_EQUAL( (asset{-1LL, sym_no_prec}.to_string()), "-1 SYMBOLL" ) + CHECK_EQUAL( (asset{-1LL, symbol{"SYMBOLL", 1}}.to_string()), "-0.1 SYMBOLL" ) + CHECK_EQUAL( (asset{ 1LL, symbol{"SYMBOLL", 1}}.to_string()), "0.1 SYMBOLL" ) + CHECK_EQUAL( (asset{-12LL, sym_no_prec}.to_string()), "-12 SYMBOLL" ) + CHECK_EQUAL( (asset{ 12LL, sym_no_prec}.to_string()), "12 SYMBOLL" ) + CHECK_EQUAL( (asset{-123LL, sym_no_prec}.to_string()), "-123 SYMBOLL" ) + CHECK_EQUAL( (asset{ 123LL, sym_no_prec}.to_string()), "123 SYMBOLL" ) + CHECK_EQUAL( (asset{-12LL, symbol{"SYMBOLL", 2}}.to_string()), "-0.12 SYMBOLL" ) + CHECK_EQUAL( (asset{ 12LL, symbol{"SYMBOLL", 2}}.to_string()), "0.12 SYMBOLL" ) + CHECK_EQUAL( (asset{-12LL, symbol{"SYMBOLL", 1}}.to_string()), "-1.2 SYMBOLL" ) + CHECK_EQUAL( (asset{ 12LL, symbol{"SYMBOLL", 1}}.to_string()), "1.2 SYMBOLL" ) + CHECK_EQUAL( (asset{-123LL, symbol{"SYMBOLL", 2}}.to_string()), "-1.23 SYMBOLL" ) + CHECK_EQUAL( (asset{ 123LL, symbol{"SYMBOLL", 2}}.to_string()), "1.23 SYMBOLL" ) CHECK_EQUAL( (asset{ 1LL, sym_prec}.to_string()), "0.000000000000000000000000000000000000000000000000000000000000001 SYMBOLL" ) CHECK_EQUAL( (asset{-1LL, sym_prec}.to_string()), - "0.000000000000000000000000000000000000000000000000000000000000001 SYMBOLL" ) + "-0.000000000000000000000000000000000000000000000000000000000000001 SYMBOLL" ) CHECK_EQUAL( (asset{asset_min, sym_no_prec}.to_string()), "-4611686018427387903 SYMBOLL" ) CHECK_EQUAL( (asset{asset_max, sym_no_prec}.to_string()), "4611686018427387903 SYMBOLL" ) + CHECK_EQUAL( (asset{asset_min, symbol{"SYMBOLL", 2}}.to_string()), "-46116860184273879.03 SYMBOLL" ) + CHECK_EQUAL( (asset{asset_max, symbol{"SYMBOLL", 2}}.to_string()), "46116860184273879.03 SYMBOLL" ) CHECK_EQUAL( (asset{asset_min, sym_prec}.to_string()), - "0.000000000000000000000000000000000000000000004611686018427387903 SYMBOLL" ) + "-0.000000000000000000000000000000000000000000004611686018427387903 SYMBOLL" ) CHECK_EQUAL( (asset{asset_max, sym_prec}.to_string()), "0.000000000000000000000000000000000000000000004611686018427387903 SYMBOLL" ) @@ -156,7 +170,7 @@ EOSIO_TEST_BEGIN(asset_type_test) asset{1LL, sym_prec}.print(); }) ) - CHECK_PRINT( "0.000000000000000000000000000000000000000000000000000000000000001 SYMBOLL", ( + CHECK_PRINT( "-0.000000000000000000000000000000000000000000000000000000000000001 SYMBOLL", ( [&]() { asset{-1LL, sym_prec}.print(); }) @@ -164,7 +178,7 @@ EOSIO_TEST_BEGIN(asset_type_test) CHECK_PRINT( "-4611686018427387903 SYMBOLL", [&](){asset{asset_min, sym_no_prec}.print();} ); CHECK_PRINT( "4611686018427387903 SYMBOLL", [&](){asset{asset_max, sym_no_prec}.print();} ); - CHECK_PRINT( "0.000000000000000000000000000000000000000000004611686018427387903 SYMBOLL", ( + CHECK_PRINT( "-0.000000000000000000000000000000000000000000004611686018427387903 SYMBOLL", ( [&]() { asset{asset_min, sym_prec}.print(); }) From 5314e96685ca81e2f2199551266a6627e2162e5d Mon Sep 17 00:00:00 2001 From: arhag Date: Wed, 4 Sep 2019 12:10:07 -0400 Subject: [PATCH 57/99] remove unnecessary semicolon --- libraries/eosiolib/asset.hpp | 2 +- libraries/eosiolib/core/eosio/asset.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/eosiolib/asset.hpp b/libraries/eosiolib/asset.hpp index 6c7528e3fc..af767e6f37 100644 --- a/libraries/eosiolib/asset.hpp +++ b/libraries/eosiolib/asset.hpp @@ -344,7 +344,7 @@ namespace eosio { } char* end_of_number = write_decimal( begin, end, false, abs_amount, precision, negative ); - *(end_of_number) = ' ';; + *(end_of_number) = ' '; return symbol.code().write_as_string( end_of_number + 1, end ); } diff --git a/libraries/eosiolib/core/eosio/asset.hpp b/libraries/eosiolib/core/eosio/asset.hpp index b0635bb779..8ffd57704d 100644 --- a/libraries/eosiolib/core/eosio/asset.hpp +++ b/libraries/eosiolib/core/eosio/asset.hpp @@ -347,7 +347,7 @@ namespace eosio { } char* end_of_number = write_decimal( begin, end, false, abs_amount, precision, negative ); - *(end_of_number) = ' ';; + *(end_of_number) = ' '; return symbol.code().write_as_string( end_of_number + 1, end ); } From 8f7cf75f11322b9828f205c9a964f657e6adc72e Mon Sep 17 00:00:00 2001 From: arhag Date: Fri, 6 Sep 2019 21:12:03 -0400 Subject: [PATCH 58/99] fix multi_index::upper_bound documentation --- libraries/eosiolib/contracts/eosio/multi_index.hpp | 2 +- libraries/eosiolib/multi_index.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/eosiolib/contracts/eosio/multi_index.hpp b/libraries/eosiolib/contracts/eosio/multi_index.hpp index 5d3dac4e40..1a288bd9d5 100644 --- a/libraries/eosiolib/contracts/eosio/multi_index.hpp +++ b/libraries/eosiolib/contracts/eosio/multi_index.hpp @@ -1246,7 +1246,7 @@ class multi_index } /** - * Searches for the `object_type` with the highest primary key that is less than or equal to a given primary key. + * 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 diff --git a/libraries/eosiolib/multi_index.hpp b/libraries/eosiolib/multi_index.hpp index 24938c2561..a55ea8f5b7 100644 --- a/libraries/eosiolib/multi_index.hpp +++ b/libraries/eosiolib/multi_index.hpp @@ -1057,7 +1057,7 @@ class multi_index } /** - * Searches for the `object_type` with the highest primary key that is less than or equal to a given primary key. + * Searches for the `object_type` with the lowest primary key that is greater than a given primary key. * * @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`. From 64f75885a7618151f2f99a637a48b42f5607cff2 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Sun, 25 Aug 2019 12:51:41 +0000 Subject: [PATCH 59/99] Fix segfault during ABI generation for tuple When passed parameter of clang::QualType is not clang::ElaboratedType, segfault occurs during ABI generation. --- tools/include/eosio/abigen.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/eosio/abigen.hpp b/tools/include/eosio/abigen.hpp index b7bea3715f..8e445b243d 100644 --- a/tools/include/eosio/abigen.hpp +++ b/tools/include/eosio/abigen.hpp @@ -110,7 +110,7 @@ namespace eosio { namespace cdt { void add_tuple(const clang::QualType& type) { auto pt = llvm::dyn_cast(type.getTypePtr()); - auto tst = llvm::dyn_cast(pt->desugar().getTypePtr()); + auto tst = llvm::dyn_cast((pt) ? pt->desugar().getTypePtr() : type.getTypePtr()); if (!tst) throw abigen_ex; abi_struct tup; From 18e4117615fbf089a8cb14d0ed9ee8e349165a92 Mon Sep 17 00:00:00 2001 From: Scott Arnette Date: Fri, 27 Sep 2019 15:33:02 -0400 Subject: [PATCH 60/99] Port PR #679 to r1.6. Fixes broken macOS PR builds on Buildkite. --- .cicd/pipeline.yml | 12 ++++-- .cicd/submodule-regression-checker.sh | 60 ++++++++++++++++----------- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/.cicd/pipeline.yml b/.cicd/pipeline.yml index 7a673c24c8..8c75bb6f09 100644 --- a/.cicd/pipeline.yml +++ b/.cicd/pipeline.yml @@ -49,7 +49,9 @@ steps: - label: ":darwin: macOS 10.14 - Build" command: - "brew install git automake libtool wget cmake gmp gettext doxygen graphviz lcov python@3" - - "git clone $BUILDKITE_REPO eosio.cdt && cd eosio.cdt && git checkout $BUILDKITE_COMMIT && git submodule update --init --recursive" + - "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: @@ -116,7 +118,9 @@ steps: - label: ":darwin: macOS 10.14 - Unit Tests" command: - "brew install git automake libtool wget cmake gmp gettext doxygen graphviz lcov python@3" - - "git clone $BUILDKITE_REPO eosio.cdt && cd eosio.cdt && git checkout $BUILDKITE_COMMIT && git submodule update --init --recursive" + - "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/tests.sh" plugins: @@ -180,7 +184,9 @@ steps: - label: ":darwin: Mojave - Package Builder" command: - - "git clone $BUILDKITE_REPO eosio.cdt && cd eosio.cdt && git checkout $BUILDKITE_COMMIT" + - "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/package.sh" plugins: diff --git a/.cicd/submodule-regression-checker.sh b/.cicd/submodule-regression-checker.sh index df15b56f1f..9befa9d072 100644 --- a/.cicd/submodule-regression-checker.sh +++ b/.cicd/submodule-regression-checker.sh @@ -1,44 +1,56 @@ -#!/usr/bin/env bash +#!/bin/bash set -eo pipefail - declare -A PR_MAP declare -A BASE_MAP - # Support Travis and BK if ${TRAVIS:-false}; then - BASE_BRANCH=$TRAVIS_BRANCH - CURRENT_BRANCH=${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} # We default to TRAVIS_BRANCH if it's not a PR so it passes on non PR runs + [[ -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 - BASE_BRANCH=${BUILDKITE_PULL_REQUEST_BASE_BRANCH:-$BUILDKITE_BRANCH} - CURRENT_BRANCH=$BUILDKITE_BRANCH + [[ -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 fi echo "getting submodule info for $CURRENT_BRANCH" while read -r a b; do - PR_MAP[$a]=$b + PR_MAP[$a]=$b done < <(git submodule --quiet foreach --recursive 'echo $path `git log -1 --format=%ct`') echo "getting submodule info for $BASE_BRANCH" git checkout $BASE_BRANCH &> /dev/null git submodule update --init &> /dev/null while read -r a b; do - BASE_MAP[$a]=$b + BASE_MAP[$a]=$b done < <(git submodule --quiet foreach --recursive 'echo $path `git log -1 --format=%ct`') -for k in "${!BASE_MAP[@]}"; do - base_ts=${BASE_MAP[$k]} - pr_ts=${PR_MAP[$k]} - echo "submodule $k" - echo " timestamp on $CURRENT_BRANCH: $pr_ts" - echo " timestamp on $BASE_BRANCH: $base_ts" - if (( $pr_ts < $base_ts)); then - echo "$k is older on $CURRENT_BRANCH than $BASE_BRANCH; investigating..." +# 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: &> /dev/null + echo "switching back to $TRAVIS_COMMIT" + echo 'git checkout -qf FETCH_HEAD' + git checkout -qf FETCH_HEAD &> /dev/null +elif [[ $BUILDKITE == true ]]; then + echo "switching back to $CURRENT_BRANCH" + git checkout -f $CURRENT_BRANCH &> /dev/null +fi - if for c in `git log $CURRENT_BRANCH ^$BASE_BRANCH --pretty=format:"%H"`; do git show --pretty="" --name-only $c; done | grep -q "^$k$"; then - echo "ERROR: $k has regressed" - exit 1 - else - echo "$k was not in the diff; no regression detected" +for k in "${!BASE_MAP[@]}"; do + base_ts=${BASE_MAP[$k]} + pr_ts=${PR_MAP[$k]} + echo "submodule $k" + echo " timestamp on $CURRENT_BRANCH: $pr_ts" + echo " timestamp on $BASE_BRANCH: $base_ts" + if (( $pr_ts < $base_ts)); then + echo "$k is older on $CURRENT_BRANCH than $BASE_BRANCH; investigating the difference between $CURRENT_BRANCH and $BASE_BRANCH to look for $k changing..." + if [[ ! -z $(for c in $(git --no-pager log $CURRENT_BRANCH ^$BASE_BRANCH --pretty=format:"%H"); do git show --pretty="" --name-only $c; done | grep "^$k$") ]]; then + echo "ERROR: $k has regressed" + exit 1 + else + echo "$k was not in the diff; no regression detected" + fi fi - fi -done +done \ No newline at end of file From ec59e63c601e7dbd1c544d208d1df687575d145a Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Sun, 25 Aug 2019 10:11:35 +0000 Subject: [PATCH 61/99] merge cherry-pick --- tools/include/eosio/abigen.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/include/eosio/abigen.hpp b/tools/include/eosio/abigen.hpp index 8e445b243d..d3ec265917 100644 --- a/tools/include/eosio/abigen.hpp +++ b/tools/include/eosio/abigen.hpp @@ -433,6 +433,12 @@ namespace eosio { namespace cdt { if (as.base == td.new_type_name) return true; } + for ( auto v : _abi.variants ) { + for ( auto vt : v.types ) { + if ( remove_suffix(vt) == td.new_type_name ) + return true; + } + } for ( auto t : _abi.tables ) if ( t.type == td.new_type_name ) return true; From 999e66189bf0770d4fc4893b8d6ad2d36ab6e554 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Sun, 25 Aug 2019 08:10:55 +0000 Subject: [PATCH 62/99] Fix segfault when using nested typedef When typedef is nested in other namespace, `clang::ElaboratedType` is passed to `get_type_alias` instead of `clang::TypedefType`. When dynamic cast to clang::TypedefType fails, try casting to ElaboratedType once mroe to avoid build fail by segfault. --- tools/include/eosio/gen.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/include/eosio/gen.hpp b/tools/include/eosio/gen.hpp index 151c82facd..ee740e8a9a 100644 --- a/tools/include/eosio/gen.hpp +++ b/tools/include/eosio/gen.hpp @@ -569,12 +569,16 @@ struct generation_utils { inline std::string get_type_alias_string( const clang::QualType& t ) { if (auto dt = llvm::dyn_cast(t.getTypePtr())) return get_type(dt->desugar()); + else if (auto dt = llvm::dyn_cast(t.getTypePtr())) + return get_type_alias_string(dt->desugar()); return get_type(t); } inline std::vector get_type_alias( const clang::QualType& t ) { if (auto dt = llvm::dyn_cast(t.getTypePtr())) return {dt->desugar()}; + else if (auto dt = llvm::dyn_cast(t.getTypePtr())) + return get_type_alias(dt->desugar()); return {}; } From 24fb732016c4d95b2547149c1caaf63a58a8bc1c Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Sun, 25 Aug 2019 07:25:24 +0000 Subject: [PATCH 63/99] Fix stack overflow caused by recursive add_type `add_type` calls itself recursively to add all types it refers to. If user-defined types refer to each other or themselves, it causes stack overflow by cycle. --- tools/include/eosio/abigen.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/include/eosio/abigen.hpp b/tools/include/eosio/abigen.hpp index d3ec265917..cd6c70c50e 100644 --- a/tools/include/eosio/abigen.hpp +++ b/tools/include/eosio/abigen.hpp @@ -247,6 +247,9 @@ namespace eosio { namespace cdt { } void add_type( const clang::QualType& t ) { + if (evaluated.count(t.getTypePtr())) + return; + evaluated.insert(t.getTypePtr()); auto type = get_ignored_type(t); if (!is_builtin_type(translate_type(type))) { if (is_aliasing(type)) @@ -485,5 +488,6 @@ namespace eosio { namespace cdt { std::set tables; std::set ctables; std::map rcs; + std::set evaluated; }; }} // ns eosio::cdt From 82e210729aba7ebe58e6a65d206248852d9f7be1 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Wed, 7 Aug 2019 23:52:48 +0000 Subject: [PATCH 64/99] fix cherry-pick conflict --- tools/include/compiler_options.hpp.in | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/tools/include/compiler_options.hpp.in b/tools/include/compiler_options.hpp.in index 633deec6fa..1c2ed83b0b 100644 --- a/tools/include/compiler_options.hpp.in +++ b/tools/include/compiler_options.hpp.in @@ -536,9 +536,8 @@ static Options CreateOptions(bool add_defaults=true) { for ( auto input_filename : input_filename_opt ) { #ifdef ONLY_LD ldopts.push_back(input_filename); -#else - inputs.push_back(input_filename); #endif + inputs.push_back(input_filename); } #ifdef ONLY_LD @@ -751,25 +750,23 @@ static Options CreateOptions(bool add_defaults=true) { #ifndef ONLY_LD if (inputs.size() == 1) { llvm::SmallString<256> fn = llvm::sys::path::filename(inputs[0]); - llvm::SmallString<256> fn2 = fn; - llvm::sys::path::replace_extension(fn, ".wasm"); + llvm::sys::path::replace_extension(fn, fnative_opt ? "" : ".wasm"); output_fn = fn.str(); - llvm::SmallString<256> res; - llvm::sys::path::system_temp_directory(true, res); - ldopts.emplace_back(std::string(std::string(res.str())+"/"+std::string(fn2.str())+".o")); } else { ldopts.emplace_back("a.out"); } -#endif +#else if (inputs.size() == 1) { llvm::SmallString<256> fn = llvm::sys::path::filename(inputs[0]); - llvm::sys::path::replace_extension(fn, ".wasm"); - ldopts.emplace_back("-o "+output_fn); + llvm::sys::path::replace_extension(fn, ""); + llvm::sys::path::replace_extension(fn, fnative_opt ? "" : ".wasm"); output_fn = fn.str(); + ldopts.emplace_back("-o "+output_fn); } else { - ldopts.emplace_back("-o a.out"); output_fn = "a.out"; + ldopts.emplace_back("-o "+output_fn); } +#endif } else { ldopts.emplace_back("-o "+o_opt); @@ -834,5 +831,13 @@ static Options CreateOptions(bool add_defaults=true) { if (fuse_main_opt) ldopts.emplace_back("-fuse-main"); #endif +<<<<<<< HEAD +======= + +#ifndef ONLY_LD +>>>>>>> a153ef9c... Set default output filename return {output_fn, inputs, link, abigen, pp_dir, abigen_output, abigen_contract, copts, ldopts, agopts, agresources, debug, fnative_opt}; +#else + return {output_fn, {}, link, abigen, pp_dir, abigen_output, abigen_contract, copts, ldopts, agopts, agresources, debug, fnative_opt}; +#endif } From 5076c9048dafad666b963471ff0a1368cc959485 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Fri, 27 Sep 2019 16:31:29 -0400 Subject: [PATCH 65/99] merge fixes --- libraries/eosiolib/chain.h | 1 + tools/include/compiler_options.hpp.in | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/eosiolib/chain.h b/libraries/eosiolib/chain.h index 0647814ab6..73c111e313 100644 --- a/libraries/eosiolib/chain.h +++ b/libraries/eosiolib/chain.h @@ -33,6 +33,7 @@ extern "C" { * @endcode */ __attribute__((eosio_wasm_import)) + [[deprecated("dep uint32_t get_active_producers( capi_name* producers, uint32_t datalen ); } diff --git a/tools/include/compiler_options.hpp.in b/tools/include/compiler_options.hpp.in index 1c2ed83b0b..030cfdb4c3 100644 --- a/tools/include/compiler_options.hpp.in +++ b/tools/include/compiler_options.hpp.in @@ -831,11 +831,8 @@ static Options CreateOptions(bool add_defaults=true) { if (fuse_main_opt) ldopts.emplace_back("-fuse-main"); #endif -<<<<<<< HEAD -======= #ifndef ONLY_LD ->>>>>>> a153ef9c... Set default output filename return {output_fn, inputs, link, abigen, pp_dir, abigen_output, abigen_contract, copts, ldopts, agopts, agresources, debug, fnative_opt}; #else return {output_fn, {}, link, abigen, pp_dir, abigen_output, abigen_contract, copts, ldopts, agopts, agresources, debug, fnative_opt}; From bd71437f27efad116b815029ca2d1baabb92a0e1 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Fri, 27 Sep 2019 17:44:00 -0400 Subject: [PATCH 66/99] perfection --- libraries/eosiolib/chain.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/eosiolib/chain.h b/libraries/eosiolib/chain.h index 73c111e313..0647814ab6 100644 --- a/libraries/eosiolib/chain.h +++ b/libraries/eosiolib/chain.h @@ -33,7 +33,6 @@ extern "C" { * @endcode */ __attribute__((eosio_wasm_import)) - [[deprecated("dep uint32_t get_active_producers( capi_name* producers, uint32_t datalen ); } From 7968a44340aa1ac36d3ca2e5638387a1a914c985 Mon Sep 17 00:00:00 2001 From: Vadim Date: Fri, 26 Jul 2019 14:57:57 +0300 Subject: [PATCH 67/99] put buffer_size as a second argument to get_active_producers --- libraries/eosiolib/eosiolib.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libraries/eosiolib/eosiolib.cpp b/libraries/eosiolib/eosiolib.cpp index b92bb10130..f97f67237a 100644 --- a/libraries/eosiolib/eosiolib.cpp +++ b/libraries/eosiolib/eosiolib.cpp @@ -55,10 +55,11 @@ namespace eosio { } std::vector get_active_producers() { - auto prod_cnt = get_active_producers(nullptr, 0)/8; - std::vector active_prods(prod_cnt); - get_active_producers((uint64_t*)active_prods.data(), active_prods.size()); - return active_prods; + const auto buffer_size = get_active_producers(nullptr, 0); + const auto prod_cnt = buffer_size / sizeof(name); + std::vector active_prods(prod_cnt); + get_active_producers((uint64_t*)active_prods.data(), buffer_size); + return active_prods; } // powers.hpp From 3bc02226f449d0f11a3979131752ceac9dc60868 Mon Sep 17 00:00:00 2001 From: Scott Arnette Date: Mon, 30 Sep 2019 09:21:44 -0400 Subject: [PATCH 68/99] Revert submodule check changes. --- .cicd/submodule-regression-checker.sh | 60 +++++++++++---------------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/.cicd/submodule-regression-checker.sh b/.cicd/submodule-regression-checker.sh index 9befa9d072..df15b56f1f 100644 --- a/.cicd/submodule-regression-checker.sh +++ b/.cicd/submodule-regression-checker.sh @@ -1,56 +1,44 @@ -#!/bin/bash +#!/usr/bin/env bash 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. + BASE_BRANCH=$TRAVIS_BRANCH + CURRENT_BRANCH=${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} # We default to TRAVIS_BRANCH if it's not a PR so it passes on non PR runs else - [[ -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 + BASE_BRANCH=${BUILDKITE_PULL_REQUEST_BASE_BRANCH:-$BUILDKITE_BRANCH} + CURRENT_BRANCH=$BUILDKITE_BRANCH fi echo "getting submodule info for $CURRENT_BRANCH" while read -r a b; do - PR_MAP[$a]=$b + PR_MAP[$a]=$b done < <(git submodule --quiet foreach --recursive 'echo $path `git log -1 --format=%ct`') echo "getting submodule info for $BASE_BRANCH" git checkout $BASE_BRANCH &> /dev/null git submodule update --init &> /dev/null while read -r a b; do - BASE_MAP[$a]=$b + 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: &> /dev/null - echo "switching back to $TRAVIS_COMMIT" - echo 'git checkout -qf FETCH_HEAD' - git checkout -qf FETCH_HEAD &> /dev/null -elif [[ $BUILDKITE == true ]]; then - echo "switching back to $CURRENT_BRANCH" - git checkout -f $CURRENT_BRANCH &> /dev/null -fi - for k in "${!BASE_MAP[@]}"; do - base_ts=${BASE_MAP[$k]} - pr_ts=${PR_MAP[$k]} - echo "submodule $k" - echo " timestamp on $CURRENT_BRANCH: $pr_ts" - echo " timestamp on $BASE_BRANCH: $base_ts" - if (( $pr_ts < $base_ts)); then - echo "$k is older on $CURRENT_BRANCH than $BASE_BRANCH; investigating the difference between $CURRENT_BRANCH and $BASE_BRANCH to look for $k changing..." - if [[ ! -z $(for c in $(git --no-pager log $CURRENT_BRANCH ^$BASE_BRANCH --pretty=format:"%H"); do git show --pretty="" --name-only $c; done | grep "^$k$") ]]; then - echo "ERROR: $k has regressed" - exit 1 - else - echo "$k was not in the diff; no regression detected" - fi + base_ts=${BASE_MAP[$k]} + pr_ts=${PR_MAP[$k]} + echo "submodule $k" + echo " timestamp on $CURRENT_BRANCH: $pr_ts" + echo " timestamp on $BASE_BRANCH: $base_ts" + if (( $pr_ts < $base_ts)); then + echo "$k is older on $CURRENT_BRANCH than $BASE_BRANCH; investigating..." + + if for c in `git log $CURRENT_BRANCH ^$BASE_BRANCH --pretty=format:"%H"`; do git show --pretty="" --name-only $c; done | grep -q "^$k$"; then + echo "ERROR: $k has regressed" + exit 1 + else + echo "$k was not in the diff; no regression detected" fi -done \ No newline at end of file + fi +done From d7e7f9863cf935fd0233cddb2522aa8df9140d21 Mon Sep 17 00:00:00 2001 From: Scott Arnette Date: Wed, 2 Oct 2019 10:53:52 -0400 Subject: [PATCH 69/99] Revert "Revert submodule check changes." This reverts commit 3bc02226f449d0f11a3979131752ceac9dc60868. --- .cicd/submodule-regression-checker.sh | 60 ++++++++++++++++----------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/.cicd/submodule-regression-checker.sh b/.cicd/submodule-regression-checker.sh index df15b56f1f..9befa9d072 100644 --- a/.cicd/submodule-regression-checker.sh +++ b/.cicd/submodule-regression-checker.sh @@ -1,44 +1,56 @@ -#!/usr/bin/env bash +#!/bin/bash set -eo pipefail - declare -A PR_MAP declare -A BASE_MAP - # Support Travis and BK if ${TRAVIS:-false}; then - BASE_BRANCH=$TRAVIS_BRANCH - CURRENT_BRANCH=${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} # We default to TRAVIS_BRANCH if it's not a PR so it passes on non PR runs + [[ -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 - BASE_BRANCH=${BUILDKITE_PULL_REQUEST_BASE_BRANCH:-$BUILDKITE_BRANCH} - CURRENT_BRANCH=$BUILDKITE_BRANCH + [[ -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 fi echo "getting submodule info for $CURRENT_BRANCH" while read -r a b; do - PR_MAP[$a]=$b + PR_MAP[$a]=$b done < <(git submodule --quiet foreach --recursive 'echo $path `git log -1 --format=%ct`') echo "getting submodule info for $BASE_BRANCH" git checkout $BASE_BRANCH &> /dev/null git submodule update --init &> /dev/null while read -r a b; do - BASE_MAP[$a]=$b + BASE_MAP[$a]=$b done < <(git submodule --quiet foreach --recursive 'echo $path `git log -1 --format=%ct`') -for k in "${!BASE_MAP[@]}"; do - base_ts=${BASE_MAP[$k]} - pr_ts=${PR_MAP[$k]} - echo "submodule $k" - echo " timestamp on $CURRENT_BRANCH: $pr_ts" - echo " timestamp on $BASE_BRANCH: $base_ts" - if (( $pr_ts < $base_ts)); then - echo "$k is older on $CURRENT_BRANCH than $BASE_BRANCH; investigating..." +# 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: &> /dev/null + echo "switching back to $TRAVIS_COMMIT" + echo 'git checkout -qf FETCH_HEAD' + git checkout -qf FETCH_HEAD &> /dev/null +elif [[ $BUILDKITE == true ]]; then + echo "switching back to $CURRENT_BRANCH" + git checkout -f $CURRENT_BRANCH &> /dev/null +fi - if for c in `git log $CURRENT_BRANCH ^$BASE_BRANCH --pretty=format:"%H"`; do git show --pretty="" --name-only $c; done | grep -q "^$k$"; then - echo "ERROR: $k has regressed" - exit 1 - else - echo "$k was not in the diff; no regression detected" +for k in "${!BASE_MAP[@]}"; do + base_ts=${BASE_MAP[$k]} + pr_ts=${PR_MAP[$k]} + echo "submodule $k" + echo " timestamp on $CURRENT_BRANCH: $pr_ts" + echo " timestamp on $BASE_BRANCH: $base_ts" + if (( $pr_ts < $base_ts)); then + echo "$k is older on $CURRENT_BRANCH than $BASE_BRANCH; investigating the difference between $CURRENT_BRANCH and $BASE_BRANCH to look for $k changing..." + if [[ ! -z $(for c in $(git --no-pager log $CURRENT_BRANCH ^$BASE_BRANCH --pretty=format:"%H"); do git show --pretty="" --name-only $c; done | grep "^$k$") ]]; then + echo "ERROR: $k has regressed" + exit 1 + else + echo "$k was not in the diff; no regression detected" + fi fi - fi -done +done \ No newline at end of file From 9da76231e12378af001c6ad68bacd413246e21bc Mon Sep 17 00:00:00 2001 From: Scott Arnette Date: Wed, 2 Oct 2019 10:55:15 -0400 Subject: [PATCH 70/99] Re-enable new submodule checker, changing redirection of STDERR. --- .cicd/submodule-regression-checker.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) mode change 100644 => 100755 .cicd/submodule-regression-checker.sh diff --git a/.cicd/submodule-regression-checker.sh b/.cicd/submodule-regression-checker.sh old mode 100644 new mode 100755 index 9befa9d072..47b4bcacc4 --- a/.cicd/submodule-regression-checker.sh +++ b/.cicd/submodule-regression-checker.sh @@ -20,8 +20,8 @@ while read -r a b; do done < <(git submodule --quiet foreach --recursive 'echo $path `git log -1 --format=%ct`') echo "getting submodule info for $BASE_BRANCH" -git checkout $BASE_BRANCH &> /dev/null -git submodule update --init &> /dev/null +git checkout $BASE_BRANCH 1> /dev/null +git submodule update --init 1> /dev/null while read -r a b; do BASE_MAP[$a]=$b done < <(git submodule --quiet foreach --recursive 'echo $path `git log -1 --format=%ct`') @@ -29,13 +29,13 @@ done < <(git submodule --quiet foreach --recursive 'echo $path `git log -1 --for # 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: &> /dev/null + 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 &> /dev/null + git checkout -qf FETCH_HEAD 1> /dev/null elif [[ $BUILDKITE == true ]]; then echo "switching back to $CURRENT_BRANCH" - git checkout -f $CURRENT_BRANCH &> /dev/null + git checkout -f $CURRENT_BRANCH 1> /dev/null fi for k in "${!BASE_MAP[@]}"; do From 79e8b7c0ff0173be10d10bf36f60b6736314741f Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Wed, 2 Oct 2019 13:29:13 -0400 Subject: [PATCH 71/99] Remove filter as the later steps handle this anyway --- tools/include/eosio/codegen.hpp | 37 +++++++-------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/tools/include/eosio/codegen.hpp b/tools/include/eosio/codegen.hpp index 28388eecd0..8312164b27 100644 --- a/tools/include/eosio/codegen.hpp +++ b/tools/include/eosio/codegen.hpp @@ -302,21 +302,11 @@ namespace eosio { namespace cdt { if (*itr != name) emitError(*ci, decl->getLocation(), "action declaration doesn't match previous declaration"); } - if (cg.actions.count(decl->getNameAsString()) == 0) { - if (cg.actions.count(name) == 0) - create_action_dispatch(decl); - else - emitError(*ci, decl->getLocation(), "action already defined elsewhere"); + std::string full_action_name = decl->getNameAsString() + ((decl->getParent()) ? decl->getParent()->getNameAsString() : ""); + if (cg.actions.count(full_action_name) == 0) { + create_action_dispatch(decl); } - cg.actions.insert(decl->getNameAsString()); // insert the method action, so we don't create the dispatcher twice - cg.actions.insert(name); - /* - for (auto param : decl->parameters()) { - if (auto tp = dyn_cast(param->getOriginalType().getTypePtr()->getAsCXXRecordDecl())) { - cg.datastream_uses.insert(tp->getQualifiedNameAsString()); - } - } - */ + cg.actions.insert(full_action_name); // insert the method action, so we don't create the dispatcher twice } else if (decl->isEosioNotify()) { @@ -335,24 +325,13 @@ namespace eosio { namespace cdt { emitError(*ci, decl->getLocation(), "notify handler declaration doesn't match previous declaration"); } - if (cg.notify_handlers.count(decl->getNameAsString()) == 0) { - if (cg.notify_handlers.count(name) == 0) - create_notify_dispatch(decl); - else - emitError(*ci, decl->getLocation(), "notification handler already defined elsewhere"); - } - cg.notify_handlers.insert(decl->getNameAsString()); // insert the method action, so we don't create the dispatcher twice - cg.notify_handlers.insert(name); - /* - for (auto param : decl->parameters()) { - if (auto tp = dyn_cast(param->getOriginalType().getTypePtr()->getAsCXXRecordDecl())) { - cg.datastream_uses.insert(tp->getQualifiedNameAsString()); - } + std::string full_notify_name = decl->getNameAsString() + ((decl->getParent()) ? decl->getParent()->getNameAsString() : ""); + if (cg.notify_handlers.count(full_notify_name) == 0) { + create_notify_dispatch(decl); } - */ + cg.notify_handlers.insert(full_notify_name); // insert the method action, so we don't create the dispatcher twice } - //cg.cxx_methods.emplace(name, decl); return true; } From cf182b36e28b1efc2db83f04dfd3894e4070162a Mon Sep 17 00:00:00 2001 From: Jeffrey Smith II Date: Thu, 3 Oct 2019 08:42:19 -0400 Subject: [PATCH 72/99] Install cleanup - fix files that were copied instead of symlinked --- CMakeLists.txt | 4 ++-- modules/InstallCDT.cmake | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 61f04653c6..373c6169c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,16 +36,16 @@ set(WASM_SDK_BUILD true) ### Configure the EosioWasmToolchain.cmakes set(CDT_ROOT_DIR ${CMAKE_BINARY_DIR}) + configure_file(${CMAKE_SOURCE_DIR}/modules/eosio.cdt-config.cmake ${CMAKE_BINARY_DIR}/lib/cmake/eosio.cdt/eosio.cdt-config.cmake @ONLY) configure_file(${CMAKE_SOURCE_DIR}/modules/EosioCDTMacros.cmake.in ${CMAKE_BINARY_DIR}/lib/cmake/eosio.cdt/EosioCDTMacros.cmake @ONLY) configure_file(${CMAKE_SOURCE_DIR}/modules/EosioWasmToolchain.cmake.in ${CMAKE_BINARY_DIR}/lib/cmake/eosio.cdt/EosioWasmToolchain.cmake @ONLY) 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) -install(FILES ${CMAKE_BINARY_DIR}/modules/eosio.cdt-config.cmake DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/eosio.cdt) 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/EosioWasmToolchain.cmake DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/eosio.cdt) install(FILES ${CMAKE_BINARY_DIR}/modules/EosioCDTMacros.cmake DESTINATION ${CDT_INSTALL_PREFIX}/lib/cmake/eosio.cdt) set(CDT_ROOT_DIR "_PREFIX_") diff --git a/modules/InstallCDT.cmake b/modules/InstallCDT.cmake index 4ba5a88502..36ec86f0a0 100644 --- a/modules/InstallCDT.cmake +++ b/modules/InstallCDT.cmake @@ -36,6 +36,12 @@ macro( eosio_tool_install_and_symlink file symlink ) install(CODE "execute_process( COMMAND ${CMAKE_COMMAND} -E create_symlink ${CDT_INSTALL_PREFIX}/bin/${file} ${CMAKE_INSTALL_PREFIX}/bin/${symlink})") endmacro( eosio_tool_install_and_symlink ) +macro( eosio_cmake_install_and_symlink file symlink ) + set(BINARY_DIR ${CMAKE_BINARY_DIR}/modules) + install(CODE "execute_process( COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_INSTALL_PREFIX}/lib/cmake/eosio.cdt)") + install(CODE "execute_process( COMMAND ${CMAKE_COMMAND} -E create_symlink ${CDT_INSTALL_PREFIX}/lib/cmake/eosio.cdt/${file} ${CMAKE_INSTALL_PREFIX}/lib/cmake/eosio.cdt/${symlink})") +endmacro( eosio_cmake_install_and_symlink ) + macro( eosio_libraries_install) execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/lib) execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/include) @@ -51,6 +57,7 @@ eosio_clang_install_and_symlink(llvm-objdump eosio-objdump) eosio_clang_install_and_symlink(llvm-readobj eosio-readobj) eosio_clang_install_and_symlink(llvm-readelf eosio-readelf) eosio_clang_install_and_symlink(llvm-strip eosio-strip) + eosio_clang_install(opt) eosio_clang_install(llc) eosio_clang_install(lld) @@ -58,6 +65,7 @@ eosio_clang_install(ld.lld) eosio_clang_install(ld64.lld) eosio_clang_install(clang-7) eosio_clang_install(wasm-ld) + eosio_tool_install_and_symlink(eosio-pp eosio-pp) eosio_tool_install_and_symlink(eosio-wast2wasm eosio-wast2wasm) eosio_tool_install_and_symlink(eosio-wasm2wast eosio-wasm2wast) @@ -67,7 +75,13 @@ eosio_tool_install_and_symlink(eosio-ld eosio-ld) eosio_tool_install_and_symlink(eosio-abigen eosio-abigen) eosio_tool_install_and_symlink(eosio-abidiff eosio-abidiff) eosio_tool_install_and_symlink(eosio-init eosio-init) + eosio_clang_install(../lib/LLVMEosioApply${CMAKE_SHARED_LIBRARY_SUFFIX}) eosio_clang_install(../lib/LLVMEosioSoftfloat${CMAKE_SHARED_LIBRARY_SUFFIX}) eosio_clang_install(../lib/eosio_plugin${CMAKE_SHARED_LIBRARY_SUFFIX}) + +eosio_cmake_install_and_symlink(eosio.cdt-config.cmake eosio.cdt-config.cmake) +eosio_cmake_install_and_symlink(EosioWasmToolchain.cmake EosioWasmToolchain.cmake) +eosio_cmake_install_and_symlink(EosioCDTMacros.cmake EosioCDTMacros.cmake) + eosio_libraries_install() From 4696eb56cfbbaad436f2e1be03c14bf4083e48cb Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Thu, 3 Oct 2019 14:07:42 -0400 Subject: [PATCH 73/99] update README to proper filenames --- README.md | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index d9d3697486..a21f9c7780 100644 --- a/README.md +++ b/README.md @@ -16,38 +16,30 @@ EOSIO.CDT currently supports Mac OS X brew, Linux x86_64 Debian packages, and Li $ brew tap eosio/eosio.cdt $ brew install eosio.cdt ``` + #### Mac OS X Brew Uninstall ```sh $ brew remove eosio.cdt ``` + #### Debian Package Install ```sh -$ wget https://github.com/eosio/eosio.cdt/releases/download/v1.6.2/eosio.cdt_1.6.2-1_amd64.deb -$ sudo apt install ./eosio.cdt_1.6.2-1_amd64.deb +$ 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 ``` + #### Debian Package Uninstall ```sh $ sudo apt remove eosio.cdt ``` -#### Fedora RPM Package Install -```sh -$ wget https://github.com/eosio/eosio.cdt/releases/download/v1.6.2/eosio.cdt-1.6.2-1.fedora-x86_64.rpm -$ sudo yum install ./eosio.cdt-1.6.2-1.fedora-x86_64.rpm -``` - -#### Fedora RPM Package Uninstall -```sh -$ sudo yum remove eosio.cdt -``` - -#### Centos RPM Package Install +#### RPM Package Install ```sh -$ wget https://github.com/eosio/eosio.cdt/releases/download/v1.6.2/eosio.cdt-1.6.2-1.centos-x86_64.rpm -$ sudo yum install ./eosio.cdt-1.6.2-1.centos-x86_64.rpm +$ 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 ``` -#### Centos RPM Package Uninstall +#### RPM Package Uninstall ```sh $ sudo yum remove eosio.cdt ``` From bec79bb2111399280857a9eb25dfc7b5345e4226 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Thu, 3 Oct 2019 17:52:08 -0400 Subject: [PATCH 74/99] bump version --- CMakeLists.txt | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 373c6169c8..ab579c9eb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ endif() set(VERSION_MAJOR 1) set(VERSION_MINOR 6) -set(VERSION_PATCH 2) +set(VERSION_PATCH 3) #set(VERSION_SUFFIX rc2) if (VERSION_SUFFIX) diff --git a/README.md b/README.md index a21f9c7780..d5ba5eca1a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # EOSIO.CDT (Contract Development Toolkit) -## Version : 1.6.2 +## Version : 1.6.3 EOSIO.CDT is a toolchain for WebAssembly (WASM) and set of tools to facilitate contract writing 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 not available or incomplete. From 6378297f9789f62a3d135b43f07406b50d74b1ba Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Mon, 7 Oct 2019 12:12:50 -0400 Subject: [PATCH 75/99] update version to rc1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b84284295..02e4207cd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ endif() set(VERSION_MAJOR 1) set(VERSION_MINOR 7) set(VERSION_PATCH 0) -set(VERSION_SUFFIX develop) +set(VERSION_SUFFIX rc1) if (VERSION_SUFFIX) set(VERSION_FULL "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}-${VERSION_SUFFIX}") From 6b90aed8aaaad0069108dac79ec1e21ea3ae95d2 Mon Sep 17 00:00:00 2001 From: Nathan Pierce Date: Tue, 8 Oct 2019 10:04:10 -0400 Subject: [PATCH 76/99] eosio/producer -> eosio/ci --- .cicd/helpers/docker-hash.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cicd/helpers/docker-hash.sh b/.cicd/helpers/docker-hash.sh index 3bbb644c06..6dcd24b3c2 100644 --- a/.cicd/helpers/docker-hash.sh +++ b/.cicd/helpers/docker-hash.sh @@ -17,7 +17,7 @@ function determine-hash() { if [[ ! -z $IMAGE_TAG ]]; then determine-hash "$CICD_DIR/docker/${IMAGE_TAG}.dockerfile" - export FULL_TAG="eosio/producer:eosio-cdt-$HASHED_IMAGE_TAG" + export FULL_TAG="eosio/ci:eosio-cdt-$HASHED_IMAGE_TAG" else echo "Please set ENV::IMAGE_TAG to match the name of a platform dockerfile..." exit 1 From 0e3b582b60c16d5fbd8b3d127e5b6359a497fe4b Mon Sep 17 00:00:00 2001 From: Nathan Pierce Date: Tue, 8 Oct 2019 12:15:07 -0400 Subject: [PATCH 77/99] eosio/producer -> eosio/ci (#702) --- .cicd/helpers/docker-hash.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cicd/helpers/docker-hash.sh b/.cicd/helpers/docker-hash.sh index 3bbb644c06..6dcd24b3c2 100644 --- a/.cicd/helpers/docker-hash.sh +++ b/.cicd/helpers/docker-hash.sh @@ -17,7 +17,7 @@ function determine-hash() { if [[ ! -z $IMAGE_TAG ]]; then determine-hash "$CICD_DIR/docker/${IMAGE_TAG}.dockerfile" - export FULL_TAG="eosio/producer:eosio-cdt-$HASHED_IMAGE_TAG" + export FULL_TAG="eosio/ci:eosio-cdt-$HASHED_IMAGE_TAG" else echo "Please set ENV::IMAGE_TAG to match the name of a platform dockerfile..." exit 1 From ec546d2a5ad44965e3553c99fcd511d90daed410 Mon Sep 17 00:00:00 2001 From: Nathan Pierce Date: Fri, 11 Oct 2019 15:25:08 -0400 Subject: [PATCH 78/99] 10.14.4 -> 10.14.6 --- .cicd/pipeline.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.cicd/pipeline.yml b/.cicd/pipeline.yml index e5b4e486a9..f2541124d2 100644 --- a/.cicd/pipeline.yml +++ b/.cicd/pipeline.yml @@ -58,7 +58,7 @@ steps: - chef/anka#v0.5.1: no-volume: true inherit-environment-vars: true - vm-name: 10.14.4_6C_14G_40G + vm-name: 10.14.6_6C_14G_40G vm-registry-tag: "clean::cicd::git-ssh::nas::brew::buildkite-agent" modify-cpu: 12 modify-ram: 24 @@ -127,7 +127,7 @@ steps: - chef/anka#v0.5.1: no-volume: true inherit-environment-vars: true - vm-name: 10.14.4_6C_14G_40G + vm-name: 10.14.6_6C_14G_40G vm-registry-tag: "clean::cicd::git-ssh::nas::brew::buildkite-agent" modify-cpu: 12 modify-ram: 24 @@ -193,7 +193,7 @@ steps: - chef/anka#v0.5.1: no-volume: true inherit-environment-vars: true - vm-name: 10.14.4_6C_14G_40G + vm-name: 10.14.6_6C_14G_40G vm-registry-tag: "clean::cicd::git-ssh::nas::brew::buildkite-agent" always-pull: true debug: true From 8681650d089c62ffb74540bee7139a9bbd78f102 Mon Sep 17 00:00:00 2001 From: Nathan Pierce Date: Tue, 15 Oct 2019 08:57:41 -0400 Subject: [PATCH 79/99] 10.14.4 -> 10.14.6 (#710) --- .cicd/pipeline.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.cicd/pipeline.yml b/.cicd/pipeline.yml index 8c75bb6f09..b3da279625 100644 --- a/.cicd/pipeline.yml +++ b/.cicd/pipeline.yml @@ -58,7 +58,7 @@ steps: - chef/anka#v0.5.1: no-volume: true inherit-environment-vars: true - vm-name: 10.14.4_6C_14G_40G + vm-name: 10.14.6_6C_14G_40G vm-registry-tag: "clean::cicd::git-ssh::nas::brew::buildkite-agent" modify-cpu: 12 modify-ram: 24 @@ -127,7 +127,7 @@ steps: - chef/anka#v0.5.1: no-volume: true inherit-environment-vars: true - vm-name: 10.14.4_6C_14G_40G + vm-name: 10.14.6_6C_14G_40G vm-registry-tag: "clean::cicd::git-ssh::nas::brew::buildkite-agent" modify-cpu: 12 modify-ram: 24 @@ -193,7 +193,7 @@ steps: - chef/anka#v0.5.1: no-volume: true inherit-environment-vars: true - vm-name: 10.14.4_6C_14G_40G + vm-name: 10.14.6_6C_14G_40G vm-registry-tag: "clean::cicd::git-ssh::nas::brew::buildkite-agent" always-pull: true debug: true From fd171fa612483bed36fe149bf90028fb932e626d Mon Sep 17 00:00:00 2001 From: Scott Arnette Date: Thu, 24 Oct 2019 15:53:46 -0400 Subject: [PATCH 80/99] Fix for broken test metrics. --- .cicd/tests.sh | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/.cicd/tests.sh b/.cicd/tests.sh index e0a68e452f..f1317a6a14 100755 --- a/.cicd/tests.sh +++ b/.cicd/tests.sh @@ -12,8 +12,11 @@ if [[ $(uname) == 'Darwin' ]]; then # You can't use chained commands in execute cd $BUILD_DIR + set +e bash -c "$TEST" - + EXIT_STATUS=$? + cd $ROOT_DIR + else # Linux ARGS=${ARGS:-"--rm --init -v $(pwd):$MOUNTED_DIR"} @@ -29,7 +32,29 @@ else # Linux evars="$evars --env ${var%%=*}" done < "$BUILDKITE_ENV_FILE" fi - + set +e eval docker run $ARGS $evars $FULL_TAG bash -c \"$COMMANDS\" - + EXIT_STATUS=$? +fi +# buildkite +if [[ "$BUILDKITE" == 'true' ]]; then + cd build + # upload artifacts + echo '+++ :arrow_up: Uploading Artifacts' + echo 'Compressing core dumps...' + [[ $((`ls -1 core.* 2>/dev/null | wc -l`)) != 0 ]] && tar czf core.tar.gz core.* || : # collect core dumps + echo 'Exporting xUnit XML' + mv -f ./Testing/$(ls ./Testing/ | grep '2' | tail -n 1)/Test.xml test-results.xml + echo 'Uploading artifacts' + [[ -f config.ini ]] && buildkite-agent artifact upload config.ini + [[ -f core.tar.gz ]] && buildkite-agent artifact upload core.tar.gz + [[ -f genesis.json ]] && buildkite-agent artifact upload genesis.json + [[ -f mongod.log ]] && buildkite-agent artifact upload mongod.log + 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 From 28b2eabe3f69eb5967e8a6c27ce50c17b4a3230a Mon Sep 17 00:00:00 2001 From: Scott Arnette Date: Thu, 24 Oct 2019 17:04:42 -0400 Subject: [PATCH 81/99] Remove unneeded artifact uploads. --- .cicd/tests.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.cicd/tests.sh b/.cicd/tests.sh index f1317a6a14..41a4537b36 100755 --- a/.cicd/tests.sh +++ b/.cicd/tests.sh @@ -41,15 +41,9 @@ if [[ "$BUILDKITE" == 'true' ]]; then cd build # upload artifacts echo '+++ :arrow_up: Uploading Artifacts' - echo 'Compressing core dumps...' - [[ $((`ls -1 core.* 2>/dev/null | wc -l`)) != 0 ]] && tar czf core.tar.gz core.* || : # collect core dumps echo 'Exporting xUnit XML' mv -f ./Testing/$(ls ./Testing/ | grep '2' | tail -n 1)/Test.xml test-results.xml echo 'Uploading artifacts' - [[ -f config.ini ]] && buildkite-agent artifact upload config.ini - [[ -f core.tar.gz ]] && buildkite-agent artifact upload core.tar.gz - [[ -f genesis.json ]] && buildkite-agent artifact upload genesis.json - [[ -f mongod.log ]] && buildkite-agent artifact upload mongod.log buildkite-agent artifact upload test-results.xml echo 'Done uploading artifacts.' fi From c2aed4adc34b2a5cf1e38fb2f7448ce53a86c192 Mon Sep 17 00:00:00 2001 From: Scott Arnette Date: Thu, 7 Nov 2019 09:34:39 -0500 Subject: [PATCH 82/99] Switch CI fleet. --- .cicd/pipeline.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.cicd/pipeline.yml b/.cicd/pipeline.yml index b3da279625..a9f896bd6c 100644 --- a/.cicd/pipeline.yml +++ b/.cicd/pipeline.yml @@ -9,7 +9,7 @@ steps: env: IMAGE_TAG: "amazonlinux-2" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-builder-fleet" timeout: ${TIMEOUT:-10} skip: $SKIP_AMAZON_LINUX_2 @@ -20,7 +20,7 @@ steps: env: IMAGE_TAG: "centos-7.6" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-builder-fleet" timeout: ${TIMEOUT:-10} skip: $SKIP_CENTOS_7 @@ -31,7 +31,7 @@ steps: env: IMAGE_TAG: "ubuntu-16.04" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-builder-fleet" timeout: ${TIMEOUT:-10} skip: $SKIP_UBUNTU_16 @@ -42,7 +42,7 @@ steps: env: IMAGE_TAG: "ubuntu-18.04" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-builder-fleet" timeout: ${TIMEOUT:-10} skip: $SKIP_UBUNTU_18 @@ -78,7 +78,7 @@ steps: env: IMAGE_TAG: "amazonlinux-2" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-tester-fleet" timeout: ${TIMEOUT:-10} skip: ${SKIP_AMAZON_LINUX_2}${SKIP_UNIT_TESTS} @@ -89,7 +89,7 @@ steps: env: IMAGE_TAG: "centos-7.6" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-tester-fleet" timeout: ${TIMEOUT:-10} skip: ${SKIP_CENTOS_7}${SKIP_UNIT_TESTS} @@ -100,7 +100,7 @@ steps: env: IMAGE_TAG: "ubuntu-16.04" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-tester-fleet" timeout: ${TIMEOUT:-10} skip: ${SKIP_UBUNTU_16}${SKIP_UNIT_TESTS} @@ -111,7 +111,7 @@ steps: env: IMAGE_TAG: "ubuntu-18.04" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-tester-fleet" timeout: ${TIMEOUT:-10} skip: ${SKIP_UBUNTU_18}${SKIP_UNIT_TESTS} @@ -148,7 +148,7 @@ steps: echo '+++ :javascript: Running test-metrics.js' node --max-old-space-size=32768 test-metrics.js agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-tester-fleet" timeout: 10 soft_fail: true @@ -164,7 +164,7 @@ steps: OS: "centos" # OS and PKGTYPE required for lambdas PKGTYPE: "rpm" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-tester-fleet" timeout: ${TIMEOUT:-10} skip: ${SKIP_CENTOS_7}${SKIP_PACKAGE_BUILDER} @@ -178,7 +178,7 @@ steps: OS: "ubuntu-18.04" # OS and PKGTYPE required for lambdas PKGTYPE: "deb" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-tester-fleet" timeout: ${TIMEOUT:-10} skip: ${SKIP_UBUNTU_18}${SKIP_PACKAGE_BUILDER} From ad2f5666f57fc1a565cae1d9c04c783a08cecfa0 Mon Sep 17 00:00:00 2001 From: Scott Arnette Date: Thu, 7 Nov 2019 09:47:59 -0500 Subject: [PATCH 83/99] Switch CI fleet. --- .cicd/pipeline.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.cicd/pipeline.yml b/.cicd/pipeline.yml index f2541124d2..99a312a2e3 100644 --- a/.cicd/pipeline.yml +++ b/.cicd/pipeline.yml @@ -9,7 +9,7 @@ steps: env: IMAGE_TAG: "amazonlinux-2" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-builder-fleet" timeout: ${TIMEOUT:-10} skip: $SKIP_AMAZON_LINUX_2 @@ -20,7 +20,7 @@ steps: env: IMAGE_TAG: "centos-7.6" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-builder-fleet" timeout: ${TIMEOUT:-10} skip: $SKIP_CENTOS_7 @@ -31,7 +31,7 @@ steps: env: IMAGE_TAG: "ubuntu-16.04" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-builder-fleet" timeout: ${TIMEOUT:-10} skip: $SKIP_UBUNTU_16 @@ -42,7 +42,7 @@ steps: env: IMAGE_TAG: "ubuntu-18.04" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-builder-fleet" timeout: ${TIMEOUT:-10} skip: $SKIP_UBUNTU_18 @@ -78,7 +78,7 @@ steps: env: IMAGE_TAG: "amazonlinux-2" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-tester-fleet" timeout: ${TIMEOUT:-10} skip: ${SKIP_AMAZON_LINUX_2}${SKIP_UNIT_TESTS} @@ -89,7 +89,7 @@ steps: env: IMAGE_TAG: "centos-7.6" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-tester-fleet" timeout: ${TIMEOUT:-10} skip: ${SKIP_CENTOS_7}${SKIP_UNIT_TESTS} @@ -100,7 +100,7 @@ steps: env: IMAGE_TAG: "ubuntu-16.04" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-tester-fleet" timeout: ${TIMEOUT:-10} skip: ${SKIP_UBUNTU_16}${SKIP_UNIT_TESTS} @@ -111,7 +111,7 @@ steps: env: IMAGE_TAG: "ubuntu-18.04" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-tester-fleet" timeout: ${TIMEOUT:-10} skip: ${SKIP_UBUNTU_18}${SKIP_UNIT_TESTS} @@ -148,7 +148,7 @@ steps: echo '+++ :javascript: Running test-metrics.js' node --max-old-space-size=32768 test-metrics.js agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-tester-fleet" timeout: 10 soft_fail: true @@ -164,7 +164,7 @@ steps: OS: "centos" # OS and PKGTYPE required for lambdas PKGTYPE: "rpm" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-tester-fleet" timeout: ${TIMEOUT:-10} skip: ${SKIP_CENTOS_7}${SKIP_PACKAGE_BUILDER} @@ -178,7 +178,7 @@ steps: OS: "ubuntu-18.04" # OS and PKGTYPE required for lambdas PKGTYPE: "deb" agents: - queue: "automation-eos-builder-fleet" + queue: "automation-eks-eos-tester-fleet" timeout: ${TIMEOUT:-10} skip: ${SKIP_UBUNTU_18}${SKIP_PACKAGE_BUILDER} From c6617e444d21456c1c7cc5f7976619affb6e0f0c Mon Sep 17 00:00:00 2001 From: ovi Date: Sat, 16 Nov 2019 01:03:43 +0200 Subject: [PATCH 84/99] Prepare the merge of branch 'docs/starter' of https://github.com/EOSIO/eosio.cdt into release/1.7.x branch --- README.md | 72 +- docs/02_installation.md | 89 ++ .../eosio-abidiff.md | 4 +- .../eosio-abigen.md | 6 +- docs/03_command-reference/eosio-cc.md | 72 ++ .../eosio-cpp.md | 8 +- .../eosio-init.md | 2 +- .../eosio-ld.md | 5 +- .../{upgrading => 04_upgrading}/1.2-to-1.3.md | 19 +- .../{upgrading => 04_upgrading}/1.5-to-1.6.md | 4 + .../05_best-practices/03_resource-planning.md | 21 + .../04_data-design-and-migration.md | 41 + .../05_securing_your_contract.md | 14 + docs/05_best-practices/07_error_handling.md | 11 + ...abi-code-generator-attributes-explained.md | 91 ++ ...02_manually_write_an_ABI_file_explained.md | 11 + .../09_deferred_transactions.md | 8 + .../10_native-tester-compilation.md} | 14 +- .../11_debugging_a_smart_contract.md | 116 +++ .../binary-extension.md | 0 .../01_compile-a-contract-via-cli.md | 18 + .../01_compile/02_how-to-configure-cmake.md | 52 ++ .../03_compiling-contracts-with-cmake.md | 19 + .../how-to-define-a-primary-index.md | 75 ++ .../how-to-define-a-secondary-index.md | 90 ++ .../how-to-define-a-singleton.md | 114 +++ ...to-delete-data-from-a-multi-index-table.md | 31 + ...to-insert-data-into-a-multi-index-table.md | 34 + .../how-to-instantiate-a-multi-index-table.md | 131 +++ ...ti_index-table-based-on-secondary-index.md | 173 ++++ ...terate-and-retrieve-a-multi_index-table.md | 155 ++++ ...w-to-modify-data-in-a-multi-index-table.md | 39 + ...4_how_to_create_and_use_action_wrappers.md | 40 + ...to_restrict_access_to_an_action_by_user.md | 31 + docs/08_troubleshooting.md | 132 +++ docs/09_tutorials/01_binary-extension.md | 787 ++++++++++++++++++ docs/09_tutorials/02_abi-variants.md | 157 ++++ docs/guides/cmake.md | 39 - docs/guides/first-smart-contract.md | 32 - docs/guides/generator-attributes.md | 98 --- docs/index.md | 24 + examples/hello/README.txt | 18 +- examples/hello/include/hello.hpp | 8 +- examples/hello/src/hello.cpp | 6 +- examples/multi_index_example/README.txt | 18 +- .../include/multi_index_example.hpp | 24 +- .../src/multi_index_example.cpp | 31 +- examples/send_inline/README.txt | 18 +- examples/send_inline/include/send_inline.hpp | 5 +- examples/send_inline/src/send_inline.cpp | 3 +- examples/singleton_example/CMakeLists.txt | 17 + examples/singleton_example/README.txt | 20 + .../include/singleton_example.hpp | 29 + .../ricardian/singleton_example.contracts.md | 3 + examples/singleton_example/src/CMakeLists.txt | 8 + .../src/singleton_example.cpp | 26 + 56 files changed, 2857 insertions(+), 256 deletions(-) create mode 100644 docs/02_installation.md rename docs/{tools => 03_command-reference}/eosio-abidiff.md (84%) rename docs/{tools => 03_command-reference}/eosio-abigen.md (91%) create mode 100644 docs/03_command-reference/eosio-cc.md rename docs/{tools => 03_command-reference}/eosio-cpp.md (94%) rename docs/{tools => 03_command-reference}/eosio-init.md (97%) rename docs/{tools => 03_command-reference}/eosio-ld.md (89%) rename docs/{upgrading => 04_upgrading}/1.2-to-1.3.md (91%) rename docs/{upgrading => 04_upgrading}/1.5-to-1.6.md (99%) create mode 100644 docs/05_best-practices/03_resource-planning.md create mode 100644 docs/05_best-practices/04_data-design-and-migration.md create mode 100644 docs/05_best-practices/05_securing_your_contract.md create mode 100644 docs/05_best-practices/07_error_handling.md create mode 100644 docs/05_best-practices/08_abi/01_abi-code-generator-attributes-explained.md create mode 100644 docs/05_best-practices/08_abi/02_manually_write_an_ABI_file_explained.md create mode 100644 docs/05_best-practices/09_deferred_transactions.md rename docs/{guides/native-tester.md => 05_best-practices/10_native-tester-compilation.md} (76%) create mode 100644 docs/05_best-practices/11_debugging_a_smart_contract.md rename docs/{guides => 05_best-practices}/binary-extension.md (100%) create mode 100644 docs/06_how-to-guides/01_compile/01_compile-a-contract-via-cli.md create mode 100644 docs/06_how-to-guides/01_compile/02_how-to-configure-cmake.md create mode 100644 docs/06_how-to-guides/01_compile/03_compiling-contracts-with-cmake.md create mode 100644 docs/06_how-to-guides/02_multi-index/how-to-define-a-primary-index.md create mode 100644 docs/06_how-to-guides/02_multi-index/how-to-define-a-secondary-index.md create mode 100644 docs/06_how-to-guides/02_multi-index/how-to-define-a-singleton.md create mode 100644 docs/06_how-to-guides/02_multi-index/how-to-delete-data-from-a-multi-index-table.md create mode 100644 docs/06_how-to-guides/02_multi-index/how-to-insert-data-into-a-multi-index-table.md create mode 100644 docs/06_how-to-guides/02_multi-index/how-to-instantiate-a-multi-index-table.md create mode 100644 docs/06_how-to-guides/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table-based-on-secondary-index.md create mode 100644 docs/06_how-to-guides/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table.md create mode 100644 docs/06_how-to-guides/02_multi-index/how-to-modify-data-in-a-multi-index-table.md create mode 100644 docs/06_how-to-guides/04_how_to_create_and_use_action_wrappers.md create mode 100644 docs/06_how-to-guides/05_authorization/how_to_restrict_access_to_an_action_by_user.md create mode 100644 docs/08_troubleshooting.md create mode 100644 docs/09_tutorials/01_binary-extension.md create mode 100644 docs/09_tutorials/02_abi-variants.md delete mode 100644 docs/guides/cmake.md delete mode 100644 docs/guides/first-smart-contract.md delete mode 100644 docs/guides/generator-attributes.md create mode 100644 docs/index.md create mode 100644 examples/singleton_example/CMakeLists.txt create mode 100644 examples/singleton_example/README.txt create mode 100644 examples/singleton_example/include/singleton_example.hpp create mode 100644 examples/singleton_example/ricardian/singleton_example.contracts.md create mode 100644 examples/singleton_example/src/CMakeLists.txt create mode 100644 examples/singleton_example/src/singleton_example.cpp diff --git a/README.md b/README.md index 88b6652d43..950e2ce10f 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,79 @@ # EOSIO.CDT (Contract Development Toolkit) ## Version : 1.7.0 -EOSIO.CDT is a toolchain for WebAssembly (WASM) and set of tools to facilitate contract writing 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 not available or incomplete. +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 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. ### Attention -- Please see the [Upgrading Guide](https://eosio.github.io/eosio.cdt/1.6.0/upgrading/) between new versions of EOSIO.CDT to be aware of any breaking changes. +- Please see the [Upgrading Guide 1.2 to 1.3](https://eosio.github.io/eosio.cdt/latest/upgrading/1.2-to-1.3) and [Upgrading Guide 1.5 to 1.6](https://eosio.github.io/eosio.cdt/latest/upgrading/1.5-to-1.6) to be aware of any breaking changes. - There is currently a known issue that a minimum of 2 CPU cores is required for using EOSIO.CDT -### Binary Releases +## Binary Releases EOSIO.CDT currently supports Mac OS X brew, Linux x86_64 Debian packages, and Linux x86_64 RPM packages. -**If you have previously installed EOSIO.CDT, please run the `uninstall` script (it is in the directory where you cloned EOSIO.CDT) before downloading and using the binary releases.** +**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 +### 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 +### Mac OS X Brew Uninstall ```sh -$ brew remove eosio.cdt +brew remove eosio.cdt ``` -#### Debian Package Install +### 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 ``` -#### Debian Package Uninstall +### Debian Package Uninstall ```sh -$ sudo apt remove eosio.cdt +sudo apt remove eosio.cdt ``` -#### RPM Package Install +### 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 ``` -#### RPM Package Uninstall +### RPM Package Uninstall ```sh -$ sudo yum remove eosio.cdt +sudo yum remove eosio.cdt ``` -### Guided Installation (Building from Scratch) +## Guided Installation or Building from Scratch ```sh -$ git clone --recursive https://github.com/eosio/eosio.cdt -$ cd eosio.cdt -$ ./build.sh -$ sudo ./install.sh +git clone --recursive https://github.com/eosio/eosio.cdt +cd eosio.cdt +mkdir build +cd build +cmake .. +make -j8 ``` -### Installed Tools +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). +Or you can install globally by running this command: + +```sh +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-* +``` + +## Installed Tools --- * eosio-cpp * eosio-cc @@ -70,15 +87,22 @@ $ sudo ./install.sh * 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/docs/02_installation.md b/docs/02_installation.md new file mode 100644 index 0000000000..193510a018 --- /dev/null +++ b/docs/02_installation.md @@ -0,0 +1,89 @@ +## Binary Releases +EOSIO.CDT currently supports Mac OS X brew, Linux x86_64 Debian packages, and Linux x86_64 RPM packages. + +**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 +``` + +### Mac OS X Brew Uninstall +```sh +$ 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 +``` + +### Debian Package Uninstall +```sh +$ 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 +``` + +### RPM Package Uninstall +```sh +$ 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 +``` + +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). +Or you can install globally by running this command + +```sh +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-* +``` + + +## Installed Tools +--- +* eosio-cpp +* eosio-cc +* eosio-ld +* eosio-init +* eosio-abidiff +* eosio-wasm2wast +* eosio-wast2wasm +* eosio-ranlib +* eosio-ar +* 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 + + +License +---- +[MIT](../LICENSE) diff --git a/docs/tools/eosio-abidiff.md b/docs/03_command-reference/eosio-abidiff.md similarity index 84% rename from docs/tools/eosio-abidiff.md rename to docs/03_command-reference/eosio-abidiff.md index bcb93ac8f9..adba0081f1 100644 --- a/docs/tools/eosio-abidiff.md +++ b/docs/03_command-reference/eosio-abidiff.md @@ -1,6 +1,6 @@ -# eosio-abidiff +## eosio-abidiff tool -Tool to diff two ABI files to flag and output differences. +The eosio-abidiff tool is used to diff two ABI files to flag and output differences. To report differences with ```eosio-abidiff```, you only need to pass the two ABI file names as command line arguments. Example: diff --git a/docs/tools/eosio-abigen.md b/docs/03_command-reference/eosio-abigen.md similarity index 91% rename from docs/tools/eosio-abigen.md rename to docs/03_command-reference/eosio-abigen.md index 0855b94e4f..a2a563f902 100644 --- a/docs/tools/eosio-abigen.md +++ b/docs/03_command-reference/eosio-abigen.md @@ -1,5 +1,7 @@ -# eosio-abigen -### This tool is deprecated, please use `eosio-cpp` for generation of your ABIs +## eosio-abigen tool + +### This tool is deprecated, use `eosio-cpp` for generation of your ABIs + To generate an ABI with ```eosio-abigen```, only requires that you give the main '.cpp' file to compile and the output filename `--output` and generating against the contract name `--contract`. Example: diff --git a/docs/03_command-reference/eosio-cc.md b/docs/03_command-reference/eosio-cc.md new file mode 100644 index 0000000000..003e129210 --- /dev/null +++ b/docs/03_command-reference/eosio-cc.md @@ -0,0 +1,72 @@ +## eosio-cc tool + +To manually compile the source code, use `eosio-cc` and `eosio-ld` as if it were __clang__ and __lld__. All the includes and options specific to EOSIO and CDT are baked in. + +``` +USAGE: eosio-cc [options] ... + +OPTIONS: + +Generic Options: + + -help - Display available options (-help-hidden for more) + -help-list - Display list of available options (-help-list-hidden for more) + -version - Display the version of this program + +compiler options: + + -C - Include comments in preprocessed output + -CC - Include comments from within macros in preprocessed output + -D= - Define to (or 1 if omitted) + -E - Only run the preprocessor + -I= - Add directory to include search path + -L= - Add directory to library search path + -MD - Write depfile containing user and system headers + -MF= - Write depfile output + -MMD - Write depfile containing user + -MT= - Specify name of main file output in depfile + -O= - Optimization level s, 0-3 + -R= - Add a resource path for inclusion + -S - Only run preprocess and compilation steps + -U= - Undefine macro + -W= - Enable the specified warning + -abigen - Generate ABI + -abigen_output= - ABIGEN output + -c - Only run preprocess, compile, and assemble steps + -contract= - Contract name + -dD - Print macro definitions in -E mode in addition to normal output + -dI - Print include directives in -E mode in addition to normal output + -dM - Print macro definitions in -E mode instead to normal output + -emit-ast - Emit Clang AST files for source inputs + -emit-llvm - Use the LLVM representation for assembler and object files + -fasm - Assemble file for x86-64 + -fcolor-diagnostics - Use colors in diagnostics + -finline-functions - Inline suitable functions + -finline-hint-functions - Inline functions which are (explicitly or implicitly) marked inline + -fmerge-all-constants - Allow merging of constants + -fnative - Compile and link for x86-64 + -fno-cfl-aa - Disable CFL Alias Analysis + -fno-elide-constructors - Disable C++ copy constructor elision + -fno-lto - Disable LTO + -fno-post-pass - Don't run post processing pass + -fno-stack-first - Don't set the stack first in memory + -fquery - Produce binaries for wasmql + -fquery-client - Produce binaries for wasmql + -fquery-server - Produce binaries for wasmql + -fstack-protector - Enable stack protectors for functions potentially vulnerable to stack smashing + -fstack-protector-all - Force the usage of stack protectors for all functions + -fstack-protector-strong - Use a strong heuristic to apply stack protectors to functions + -fstrict-enums - Enable optimizations based on the strict definition of an enum's value range + -fstrict-return - Always treat control flow paths that fall off the end of a non-void function as unreachable + -fstrict-vtable-pointers - Enable optimizations based on the strict rules for overwriting polymorphic C++ objects + -fuse-main - Use main as entry + -include= - Include file before parsing + -isystem= - Add directory to SYSTEM include search path + -l= - Root name of library to link + -lto-opt= - LTO Optimization level (O0-O3) + -o= - Write output to + -stack-size= - Specifies the maximum stack size for the contract. Defaults to 8192 bytes. + -sysroot= - Set the system root directory + -v - Show commands to run and use verbose output + -w - Suppress all warnings +``` diff --git a/docs/tools/eosio-cpp.md b/docs/03_command-reference/eosio-cpp.md similarity index 94% rename from docs/tools/eosio-cpp.md rename to docs/03_command-reference/eosio-cpp.md index 2cde5b53df..c4cdc74e09 100644 --- a/docs/tools/eosio-cpp.md +++ b/docs/03_command-reference/eosio-cpp.md @@ -1,9 +1,7 @@ -### Usage ---- -To manually compile the source code, use `eosio-cpp/eosio-cc` and `eosio-ld` as if it were __clang__ and __lld__. All the includes and options specific to EOSIO and CDT are baked in. +## eosio-cpp tool + +To manually compile the source code, use `eosio-cpp` and `eosio-ld` as if it were __clang__ and __lld__. All the includes and options specific to EOSIO and CDT are baked in. -###$ eosio-cpp ---- ``` USAGE: eosio-cpp [options] ... diff --git a/docs/tools/eosio-init.md b/docs/03_command-reference/eosio-init.md similarity index 97% rename from docs/tools/eosio-init.md rename to docs/03_command-reference/eosio-init.md index f721ba33b4..0702b09585 100644 --- a/docs/tools/eosio-init.md +++ b/docs/03_command-reference/eosio-init.md @@ -1,4 +1,4 @@ -# eosio-init +## 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. diff --git a/docs/tools/eosio-ld.md b/docs/03_command-reference/eosio-ld.md similarity index 89% rename from docs/tools/eosio-ld.md rename to docs/03_command-reference/eosio-ld.md index 87f368e834..07bad1e2af 100644 --- a/docs/tools/eosio-ld.md +++ b/docs/03_command-reference/eosio-ld.md @@ -1,4 +1,7 @@ -### eosio-ld +## eosio-ld tool + +The eosio-ld tool is a the custom web assembly linker for EOSIO platform smart contracts. + --- ``` USAGE: eosio-ld [options] ... diff --git a/docs/upgrading/1.2-to-1.3.md b/docs/04_upgrading/1.2-to-1.3.md similarity index 91% rename from docs/upgrading/1.2-to-1.3.md rename to docs/04_upgrading/1.2-to-1.3.md index d76ef1f15e..c5d254380c 100644 --- a/docs/upgrading/1.2-to-1.3.md +++ b/docs/04_upgrading/1.2-to-1.3.md @@ -203,24 +203,23 @@ typedef eosio::multi_index<"tablename"_n, testtable> testtable_t; ``` If you don't want to use the multi-index you can explicitly specify the name in the attribute ```c++ [[eosio::table("")]]```. -For an example contract of ABI generation please see the file ./examples/abigen_test/test.cpp. You can generate the ABI for this file with `eosio-abigen test.cpp --output=test.abi`. +For an example contract of ABI generation see the file ./examples/abigen_test/test.cpp. You can generate the ABI for this file with `eosio-abigen test.cpp --output=test.abi`. ### 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 generators 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. +- 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. ### 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. -- The Ricardian contracts should be housed in a file with the name .contracts.md and the clauses should be in a file named .clauses.md. - - For each Ricardian contract the header `

ActionName

` should be used, as this directs the ABI generator to attach this Ricardian contract to the specified action. - - For each Ricardian clause the header `

ClauseID

` should be used, as this directs the ABI generator to the clause id and the subsequent body. - - The option `-R` has been added to eosio-cpp and eosio-abigen to add "resource" paths to search from, so you can place these files in any directory structure you like and use `-R` in the same vein as `-I` for include paths. - - To see these in use please see ./examples/hello/hello.contracts.md and ./examples/hello/hello.clauses.md. +- 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. +- The Ricardian contract should be housed in a file with the name .contracts.md and the clauses should be in a file named .clauses.md. +- For each Ricardian contract, the header `

ActionName

` should be used, as this directs the ABI generator to attach this Ricardian contract to the specified action. +- For each Ricardian clause, the header `

ClauseID

` should be used, as this directs the ABI generator to the clause id and the subsequent body. +- The option `-R` has been added to eosio-cpp and eosio-abigen to add "resource" paths to search from, so you can place these files in any directory structure you like and use `-R` in the same vein as `-I` for include paths. +- For exemplification see [hello.contracts.md](https://github.com/EOSIO/eosio.cdt/blob/master/examples/hello/ricardian/hello.contracts.md). License ---- - -MIT +[MIT](../../LICENSE) diff --git a/docs/upgrading/1.5-to-1.6.md b/docs/04_upgrading/1.5-to-1.6.md similarity index 99% rename from docs/upgrading/1.5-to-1.6.md rename to docs/04_upgrading/1.5-to-1.6.md index c78d18a4ee..a870b8bec0 100644 --- a/docs/upgrading/1.5-to-1.6.md +++ b/docs/04_upgrading/1.5-to-1.6.md @@ -138,3 +138,7 @@ If the dispatcher fails to find a suitable action to dispatch, then the new patt If the dispatcher is in notification handling mode and if your contract receives an `eosio::onerror` notification, then the contract will assert with an error code. You can circumvent this check if you explicitly supply an error handler for it ([[eosio::on_notify("eosio::onerror")]]). 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/03_resource-planning.md b/docs/05_best-practices/03_resource-planning.md new file mode 100644 index 0000000000..1ba7fb6f62 --- /dev/null +++ b/docs/05_best-practices/03_resource-planning.md @@ -0,0 +1,21 @@ +## Resource planning + +How much RAM do I need? This is not an easy question to answer, and there's really no perfect answer for it. You need to find out by measuring your contracts' actions and by planning accordingly based on your predictions on how fast and how much your blockchain application will grow. If your blockchain application growth is requiring more storage capacity you'll need to buy more RAM. If it requires more actions to be executed in the 3 day window (the staking time) you need to stake more tokens for CPU bandwidth. If your blockchain application growth means more actions will be stored on the blockchain then you also will need to expand your NET bandwidth maximum limit by staking more tokens for NET bandwidth. + +*Ok, you say, but how much?* + +You need to test and simulate various business scenarios that apply to your blockchain application and measure their resource usage. Hence, the existence of the public test networks. These allow you to measure how much RAM, CPU, and NET each action consumes, and to measure worst and best case business scenarios. You can then extrapolate and build a fairly good view of your blockchain application's resource needs. + +Once you have a fair idea of how your contract, blockchain application, and user base are consuming blockchain resources on a public test-net you can estimate what you'll need to start with on any EOSIO-based networks, public or private. From that point onward, as with any other application, it is advisable to have monitors that tell you statistics and metrics about your application performance. + +Of course some aspects might differ from network to network, because each network might have altered its system contracts. The EOSIO code base is open sourced and it can be tailored to each network's requirements. You need to be aware of these differences and take them into account if this is the case with a network you're testing on. + +The EOSIO community is also providing tools that can help you in this endeavor. One example is https://www.eosrp.io +Because the RAM price varies and because the CPU and NET bandwidth allocations vary too, as it is explained in the previous section, this tool can help you estimate how much of each resource you can allocate based on a specific amount of tokens and vice-versa. + +Another aspect of resource planning involves making sure your contract is efficient, that is, not consuming resources unnecessarily. Therefore, it is beneficial for you to find answers to the following questions when writing your own smart contracts and blockchain applications: + + * Is your smart contract storing only the information that is necessary to be stored on a blockchain and for the rest is using alternative ways for storing data (e.g. IPFS)? + * If you have multiple smart contracts, are they communicating between them too much via inline actions? Could some of the smart contracts be merged into one and thus eliminate the need to spawn inline actions between them, reducing the overall inline actions count and thus resource consumption? + * Could you change your smart contracts so that your clients pay for some parts of the RAM used? Recall how originally the addressbook contract was making each new account added to the book pay for the RAM needed to store its individual data? + * Or conversely, are you making your clients pay too much RAM or CPU in order to access your contracts' actions, to the point where you are prohibiting their use of your smart contract? Would it be better for your blockchain application's growth and success to take on some of those costs? diff --git a/docs/05_best-practices/04_data-design-and-migration.md b/docs/05_best-practices/04_data-design-and-migration.md new file mode 100644 index 0000000000..ec126b4250 --- /dev/null +++ b/docs/05_best-practices/04_data-design-and-migration.md @@ -0,0 +1,41 @@ +# Data design and migration + +EOSIO based blockchains allow developers to easily update their smart contract code. However, a few things need to be considered when it comes to data update and/or migration. The main structure for storing data in EOSIO based blockchains is the multi index table. Once a multi index table has been created with a first version of a smart contract, it has some limitations when it comes to changing its structure. Below you will find a few possible approaches which you can consider when you design your smart contract data and its migration. + +# How to modify the structure of a multi index table + +Modifying a multi-index table structure that has already been deployed to an EOSIO-based blockchain may be done by selecting one of the different strategies outlined below, depending on your requirements: + +## 1. If you don't mind losing the existing data + +If you don't mind losing the data from the initial table you can follow these two steps: +1. Erase all records from first table +2. Deploy a new contract with modified table structure + +## 2. If you want to keep the existing data + +If you want to keep the existing data there are two ways to do it: + +### 2.1. Using binary extentions +To learn how to modify the structure using binary extensions read this [tutorial](../09_tutorials/01_binary-extension.md). + +### 2.2. Using ABI variants +To learn how to modify the structure using ABI variants read this [tutorial](../09_tutorials/02_abi-variants.md). + +### 2.3. Migrate the existing data to a second table + +#### 2.3.1. Migration without downtime, but slower + +1. Create the new version of your multi index table alongside the old one; +2. Transfer data from the old table to the new one. You may do so as part of your normal access pattern, first checking the new table to see if the entry you seek is present and if not, check the original table, and if it's present, migrate it while adding the data for the new field, then remove it from the original table to save RAM costs. +3. You must retain both versions of your multi index table until you have completed this migration, at which point you may update your contract to remove the original version of your multi index table. + +#### 2.3.2. Migration with downtime, but faster + +If you prefer less code complexity and can accept downtime for your application: + +1. Deploy a version of your contract solely for migration purposes, and run migration transactions on every row of your table until complete. If the first table is big, e.g. has a large number of rows, the transaction time limit could be reached while running the migration transactions. To mitigate this implement the migrate function to move a limited number of rows each time it runs; +2. Deploy a new contract using only the new version of the table, at which point, your migration and downtime is complete. + +[[caution]] +| Both of the above migration methods require some pre-planning (like the ability to put your contract into a maintenance mode for user feedback) diff --git a/docs/05_best-practices/05_securing_your_contract.md b/docs/05_best-practices/05_securing_your_contract.md new file mode 100644 index 0000000000..2bcf2f39b8 --- /dev/null +++ b/docs/05_best-practices/05_securing_your_contract.md @@ -0,0 +1,14 @@ +## Securing your contract +These are basic recommendations that should be the foundation of securing your smart contract: + +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). + +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. + +3. Have a solid and comprehensive development process that includes security considerations from day one of the product planning and development. + +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. + +5. Conduct independent smart contract audits, at least two from different organizations. + +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 diff --git a/docs/05_best-practices/07_error_handling.md b/docs/05_best-practices/07_error_handling.md new file mode 100644 index 0000000000..162977af7d --- /dev/null +++ b/docs/05_best-practices/07_error_handling.md @@ -0,0 +1,11 @@ +## Error handling + +Contracts are able to use `uint64_t` error codes as an alternative (and cheaper) means of signaling error conditions as opposed to string error messages. However, EOSIO and EOSIO.CDT reserve certain ranges of the `uint64_t` value space for their own purposes. They assume that the contract develop respects the following restrictions: + +1. 0 (inclusive) to 5,000,000,000,000,000,000 (exclusive): Available for contract developers to use to signal errors specific to their contracts. + +2. 5,000,000,000,000,000,000 (inclusive) to 8,000,000,000,000,000,000 (exclusive): Reserved for the EOSIO.CDT compiler to allocate as appropriate. Although the WASM code generated by the EOSIO.CDT compiler may use error code values that were automatically generated from within this range, the error codes in this range are meant to have meaning specific to the particular compiled contract (the meaning would typically be conveyed through the mapping between the error code value and strings in the associated generated ABI file). + +3. 8,000,000,000,000,000,000 (inclusive) to 10,000,000,000,000,000,000 (exclusive): Reserved for the EOSIO.CDT compiler to allocate as appropriate. The error codes in this range are not specific to any contract but rather are used to convey general runtime error conditions associated with the generated code by EOSIO.CDT. + +4. 10,000,000,000,000,000,000 (inclusive) to 18,446,744,073,709,551,615 (inclusive): Reserved for EOSIO to represent system-level error conditions. EOSIO will actually enforce this by restricting the ability for `eosio_assert_code` to be used to fail with error code values used within this range. \ No newline at end of file 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 new file mode 100644 index 0000000000..937f18cab4 --- /dev/null +++ b/docs/05_best-practices/08_abi/01_abi-code-generator-attributes-explained.md @@ -0,0 +1,91 @@ +## ABI/Code generator attributes explained +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. +Example (four ways to declare an action for ABI generation): +```cpp +// this is the C++11 and greater style attribute +[[eosio::action]] +void testa( name n ) { + // do something +} + +// this is the GNU style attribute, this can be used in C code and prior to C++ 11 +__attribute__((eosio_action)) +void testa( name n ){ + // do something +} + +struct [[eosio::action]] testa { + name n; + EOSLIB_SERIALIZE( testa, (n) ) +}; + +struct __attribute__((eosio_action)) testa { + name n; + EOSLIB_SERIALIZE( testa, (n) ) +}; +``` + +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("")]]``` + +### [[eosio::table]] +Example (two ways to declare a table for ABI generation): +```cpp +struct [[eosio::table]] testtable { + uint64_t owner; + /* all other fields */ +}; + +struct __attribute__((eosio_table)) testtable { + uint64_t owner; + /* all other fields */ +}; + +typedef eosio::multi_index<"tablename"_n, testtable> testtable_t; +``` + +If you don't want to use the multi-index you can explicitly specify the name in the attribute ```c++ [[eosio::table("")]]```. + +### [[eosio::contract("ANY_NAME_YOU_LIKE")]] +```cpp +class [[eosio::contract("ANY_NAME_YOU_LIKE")]] test_contract : public eosio::contract { +}; +``` + +The code above will mark this `class` as being an `EOSIO` contract, this allows for namespacing of contracts, i.e. you can include headers like `eosio::token` and not have `eosio::token`'s actions/tables wind up in you ABI or generated dispatcher. + +### [[eosio::on_notify("VALID_EOSIO_ACCOUNT_NAME::VALID_EOSIO_ACTION_NAME")]] +```cpp +[[eosio::on_notify("eosio.token::transfer")]] +void on_token_transfer(name from, name to, assert quantity, std::string memo) { + // do something on eosio.token contract's transfer action from any account to the account where the contract is deployed. +} + +[[eosio::on_notify("*::transfer")]] +void on_any_transfer(name from, name to, assert quantity, std::string memo) { + // do something on any contract's transfer action from any account to the account where the contract is deployed. +} +``` + +### [[eosio::wasm_entry]] +```cpp +[[eosio::wasm_entry]] +void some_function(...) { + // do something +} +``` + +The code above will mark an arbitrary function as an entry point, which will then wrap the function with global constructors (ctors) and global destructors (dtors). This will allow for the eosio.cdt toolchain to produce WASM binaries for other ecosystems. + +### [[eosio::wasm_import]] +```cpp +extern "C" { + __attribute__((eosio_wasm_import)) + void some_intrinsic(...); +} +``` + +The code above will mark a function declaration as being a WebAssembly import. This allows for other compilation modes to specify which functions are import only (i.e. do not link) without having to maintain a secondary file with duplicate declarations. + 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 new file mode 100644 index 0000000000..e2c10c7396 --- /dev/null +++ b/docs/05_best-practices/08_abi/02_manually_write_an_ABI_file_explained.md @@ -0,0 +1,11 @@ +## Manually write, or fix, 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. + +### 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. +- The Ricardian contracts should be housed in a file with the name `.contracts.md` and the clauses should be in a file named `.clauses.md`. + - For each Ricardian contract the header `

ActionName

` should be used, as this directs the ABI generator to attach this Ricardian contract to the specified action. + - For each Ricardian clause, the header `

ClauseID

` should be used, as this directs the ABI generator to the clause id and the subsequent body. + - The option `-R` has been added to [`eosio-cpp`](../../03_command-reference/eosio-cpp.md) and [`eosio-abigen`](../../03_command-reference/eosio-abigen.md) to add "resource" paths to search from, so you can place these files in any directory structure you like and use `-R` in the same vein as `-I` for include paths. + - For exemplification see [hello.contracts.md](https://github.com/EOSIO/eosio.cdt/blob/master/examples/hello/ricardian/hello.contracts.md). diff --git a/docs/05_best-practices/09_deferred_transactions.md b/docs/05_best-practices/09_deferred_transactions.md new file mode 100644 index 0000000000..0989b6372c --- /dev/null +++ b/docs/05_best-practices/09_deferred_transactions.md @@ -0,0 +1,8 @@ +## Deferred transactions + +Deferred communication conceptually takes the form of action notifications sent to a peer transaction. Deferred actions get scheduled to run, at best, at a later time, at the producer's discretion. There is no guarantee that a deferred action will be executed. + +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. diff --git a/docs/guides/native-tester.md b/docs/05_best-practices/10_native-tester-compilation.md similarity index 76% rename from docs/guides/native-tester.md rename to docs/05_best-practices/10_native-tester-compilation.md index b52e450f74..e677980021 100644 --- a/docs/guides/native-tester.md +++ b/docs/05_best-practices/10_native-tester-compilation.md @@ -1,7 +1,7 @@ -## Native Tester/Compilation -As of v1.5.0 native compilation can be performed and a new set of libraries to facilitate native testing and native "scratch pad" compilation. `eosio-cc\cpp` and `eosio-ld` now support building "smart contracts" and unit tests natively for quick tests to help facilitate faster development \(note the default implementations of eosio `intrinsics` are currently asserts that state they are unavailable, these are user definable.\) +## How to use native tester/compilation +As of v1.5.0 native compilation can be performed and a new set of libraries to facilitate native testing and native "scratch pad" compilation. [`eosio-cc`](../03_command-reference/eosio-cc.md), [`eosio-cpp`](../03_command-reference/eosio-cpp.md) and [`eosio-ld`](../03_command-reference/eosio-ld.md) now support building "smart contracts" and unit tests natively for quick tests to help facilitate faster development \(note the default implementations of eosio `intrinsics` are currently asserts that state they are unavailable, these are user definable.\) -#### Getting Started +### Getting Started Once you have your smart contract written then a test source file can be written. `hello.hpp` @@ -10,11 +10,11 @@ Once you have your smart contract written then a test source file can be written using namespace eosio; -CONTRACT hello : public eosio::contract { +class [[eosio::contract]] hello : public eosio::contract { public: using contract::contract; - ACTION hi( name user ); + [[eosio::action]] void hi( name user ); // accessor for external contracts to easily send inline actions to your contract using hi_action = action_wrapper<"hi"_n, &hello::hi>; @@ -34,7 +34,7 @@ using namespace eosio::native; EOSIO_TEST_BEGIN(hello_test) // These can be redefined by the user to suit there needs per unit test - // the idea is that in a future release we will have a base library that + // the idea is that in a future release there will be a base library that // initializes these to "useable" default implementations and probably // helpers to more easily define read_action_data and action_data_size intrinsics // like these" @@ -88,7 +88,7 @@ int main(int argc, char** argv) { } ``` -Every `intrinsic` that is defined for eosio (prints, require_auth, etc.) is redefinable given the `intrinsics::set_intrinsics()` functions. These take a lambda whose arguments and return type should match that of the intrinsic you are trying to define. This gives the contract writer the flexibility to modify behavior to suit the unit test being written. A sister function `intrinsics::get_intrinsics()` will return the function object that currently defines the behavior for said intrinsic. This pattern can be used to mock functionality and allow for easier testing of smart contracts. For more information please see, either the `./tests` directory or `./examples/hello/tests/hello_test.cpp` for working examples. +Every `intrinsic` that is defined for eosio (prints, require_auth, etc.) is re-definable given the `intrinsics::set_intrinsics()` functions. These take a lambda whose arguments and return type should match that of the intrinsic you are trying to define. This gives the contract writer the flexibility to modify behavior to suit the unit test being written. A sister function `intrinsics::get_intrinsics()` will return the function object that currently defines the behavior for said intrinsic. This pattern can be used to mock functionality and allow for easier testing of smart contracts. For more information see, either the [tests](https://github.com/EOSIO/eosio.cdt/blob/master/examples/hello/tests/) directory or [hello_test.cpp](https://github.com/EOSIO/eosio.cdt/blob/master/examples/hello/tests/hello_test.cpp) for working examples. ### Compiling Native Code - Raw `eosio-cpp` to compile the test or program the only addition needed to the command line is to add the flag `-fnative` this will then generate native code instead of `wasm` code. diff --git a/docs/05_best-practices/11_debugging_a_smart_contract.md b/docs/05_best-practices/11_debugging_a_smart_contract.md new file mode 100644 index 0000000000..7c03da1f91 --- /dev/null +++ b/docs/05_best-practices/11_debugging_a_smart_contract.md @@ -0,0 +1,116 @@ +## Debugging a smart contract + +In order to be able to debug your smart contract, you will need to setup a local nodeos node. This local nodeos node can be run as separate private testnet or as an extension of a public testnet. This local node also needs to be run with the contracts-console option on, either `--contracts-console` via the command line or `contracts-console = true` via the config.ini and/or by setting up logging on your running nodeos node and checking the output logs. See below for details on logging. + +When you are creating your smart contract for the first time, it is recommended to test and debug your smart contract on a private testnet first, since you have full control of the whole blockchain and can easily add suitable logging. This enables you to have unlimited amount of eos needed and you can just reset the state of the blockchain whenever you want. When it is ready for production, debugging on the public testnet (or official testnet) can be done by connecting your local nodeos to the public testnet (or official testnet) so you can see the log of the testnet in your local nodeos. + +The concept is the same, so for the following guide, debugging on the private testnet will be covered. + +If you haven't set up your own local nodeos, follow the [setup guide](https://developers.eos.io/eosio-home/docs/getting-the-software). By default, your local nodeos will just run in a private testnet unless you modify the config.ini file to connect with public testnet (or official testnet) nodes. + +## Method +The main method used to debug smart contract is **Caveman Debugging**. Printing is utilized to inspect the value of a variable and check the flow of the contract. Printing in smart contracts can be done through the Print API. The C++ API is a wrapper for C API and is the recommended API. + +## Print +Print C API supports the following data type that you can print: +- prints - a null terminated char array (string) +- prints_l - any char array (string) with given size +- printi - 64-bit signed integer +- printui - 64-bit unsigned integer +- printi128 - 128-bit signed integer +- printui128 - 128-bit unsigned integer +- printsf - single-precision floating point number +- printdf - double encoded as 64-bit unsigned integer +- printqf - quadruple encoded as 64-bit unsigned integer +- printn - 64 bit names as base32 encoded string +- printhex - hex given binary of data and its size + +The Print C++ API wraps some of the above C API by overriding the print() function, so the user doesn't need to determine which specific print function to use. Print C++ API supports: +- a null terminated char array (string) +- integer (128-bit unsigned, 64-bit unsigned, 32-bit unsigned, signed, unsigned) +- base32 string encoded as 64-bit unsigned integer +- struct that has print() method + +## Example +Here's an example contract for debugging + +### debug.hpp + +```cpp +namespace debug { + struct foo { + account_name from; + account_name to; + uint64_t amount; + void print() const { + eosio::print("Foo from ", eosio::name(from), " to ", eosio::name(to), " with amount ", amount, "\n"); + } + }; +} +``` +### debug.cpp + +```cpp +#include + +extern "C" { + + void apply( uint64_t code, uint64_t action ) { + if (code == N(debug)) { + eosio::print("Code is debug\n"); + if (action == N(foo)) { + eosio::print("Action is foo\n"); + debug::foo f = eosio::unpack_action_data(); + if (f.amount >= 100) { + eosio::print("Amount is larger or equal than 100\n"); + } else { + eosio::print("Amount is smaller than 100\n"); + eosio::print("Increase amount by 10\n"); + f.amount += 10; + eosio::print(f); + } + } + } + } +} // extern "C" +``` +### debug.abi + +```json +{ + "structs": [{ + "name": "foo", + "base": "", + "fields": { + "from": "account_name", + "to": "account_name", + "amount": "uint64" + } + } + ], + "actions": [{ + "action_name": "foo", + "type": "foo" + } + ] +} +``` +Deploy it and push an action to it. It is assumed you have a `debug` account created and have its key in your wallet. + +```bash +$ eosio-cpp -abigen debug.cpp -o debug.wasm +$ cleos set contract debug CONTRACT_DIR/debug -p youraccount@active +$ cleos push action debug foo '{"from":"inita", "to":"initb", "amount":10}' --scope debug +``` + +When you check your local `nodeos` node log, you will see the following lines after the above message is sent. + +``` +Code is debug +Action is foo +Amount is smaller than 100 +Increase amount by 10 +Foo from inita to initb with amount 20 +``` + +There, you can confirm that your message is going to the right control flow and the amount is updated correctly. You might see the above message at least 2 times and that's normal because each transaction is being applied during verification, block generation, and block application. \ No newline at end of file diff --git a/docs/guides/binary-extension.md b/docs/05_best-practices/binary-extension.md similarity index 100% rename from docs/guides/binary-extension.md rename to docs/05_best-practices/binary-extension.md 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 new file mode 100644 index 0000000000..c50c6155e6 --- /dev/null +++ b/docs/06_how-to-guides/01_compile/01_compile-a-contract-via-cli.md @@ -0,0 +1,18 @@ +## 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 new file mode 100644 index 0000000000..8811abf5d1 --- /dev/null +++ b/docs/06_how-to-guides/01_compile/02_how-to-configure-cmake.md @@ -0,0 +1,52 @@ +## 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 new file mode 100644 index 0000000000..70afbb5fd0 --- /dev/null +++ b/docs/06_how-to-guides/01_compile/03_compiling-contracts-with-cmake.md @@ -0,0 +1,19 @@ +## 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/02_multi-index/how-to-define-a-primary-index.md b/docs/06_how-to-guides/02_multi-index/how-to-define-a-primary-index.md new file mode 100644 index 0000000000..dc1eb6265d --- /dev/null +++ b/docs/06_how-to-guides/02_multi-index/how-to-define-a-primary-index.md @@ -0,0 +1,75 @@ +## How to define a primary index + +A primary key is required when defining a multi index table structure. See the following example: + +1. Include the `eosio.hpp` header and declare the `eosio` namespace usage +``` +#include +using namespace eosio; +``` +2. Define the data structure for the multi index table +```cpp + struct [[eosio::table]] test_table { + }; +``` +3. Add to the data structure the fields which define the multi index table +```diff + // the data structure which defines each row of the table + struct [[eosio::table]] test_table { ++ // this field stores a name for each row of the multi index table ++ name test_primary; ++ // additional data stored in table row, e.g. an uint64_t type data ++ uint64_t datum; + }; +``` +4. Add the definition of the primary index for the multi index table. The primary index type must be uint64_t and must be unique +```diff + // the data structure which defines each row of the table + struct [[eosio::table]] test_table { + // this field stores a name for each row of the multi index table + name test_primary; + // additional data stored in table row + uint64_t datum; ++ // mandatory definition for primary key getter ++ uint64_t primary_key( ) const { return test_primary.value; } + }; +``` + +[[Info | Secondary indexes information]] +| Other, secondary, indexes if they will be defined can have duplicates. You can have up to 16 additional indexes and the field types can be uint64_t, uint128_t, uint256_t, double or long double. + +5. For ease of use, define a type alias `test_tables` based on the `eosio::multi_index` template type, parametarized with a random name `"testtaba"` and the `test_table` data structure defined above +```diff + // the data structure which defines each row of the table + struct [[eosio::table]] test_table { + // this field stores a name for each row of the multi index table + name test_primary; + // additional data stored in table row + uint64_t datum; + // mandatory definition for primary key getter + uint64_t primary_key( ) const { return test_primary.value; } + }; + ++ typedef eosio::multi_index<"testtaba"_n, test_table> test_tables; +``` + +Declare the multi index table as a data member of type `test_tables`, as defined above. +```diff + // the data structure which defines each row of the table + struct [[eosio::table]] test_table { + // this field stores a name for each row of the multi index table + name test_primary; + // additional data stored in table row + uint64_t datum; + // mandatory definition for primary key getter + uint64_t primary_key( ) const { return test_primary.value; } + }; + + typedef eosio::multi_index<"testtaba"_n, test_table> test_tables; ++ test_tables testtab; +``` + +Now you have instantiated the `testtab` as a multi index table which has a primary index defined for its `test_primary` data member. + +[[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 diff --git a/docs/06_how-to-guides/02_multi-index/how-to-define-a-secondary-index.md b/docs/06_how-to-guides/02_multi-index/how-to-define-a-secondary-index.md new file mode 100644 index 0000000000..3036e20b45 --- /dev/null +++ b/docs/06_how-to-guides/02_multi-index/how-to-define-a-secondary-index.md @@ -0,0 +1,90 @@ +## How to define a secondary index + +### Preconditions +- It is assumed you already have a multi index table instance defined along with its mandatory primary index, otherwise take a look at the section [How to instantiate a multi index table](./how-to-instantiate-a-multi-index-table.md). + +The steps below show how to add a secondary index to the existing multi index table. + +1. Add a second field, `secondary`, to the data structure that defines the row of the table, in your case `test_table` +```diff + struct [[eosio::table]] test_table { + // this field stores a name for each row of the multi index table + name test_primary; ++ name secondary; + // additional data stored in table row + uint64_t datum; + // mandatory definition for primary key getter + uint64_t primary_key( ) const { return test_primary.value; } + }; +``` + +2. Add `by_secondary( )` method, which is the index accessor method to the new field value added. The secondary index, that will be added in step 3, will index this new data structure field. +```diff + struct [[eosio::table]] test_table { + // this field stores a name for each row of the multi index table + name test_primary; + name secondary; + // additional data stored in table row + uint64_t datum; + // mandatory definition for primary key getter + uint64_t primary_key( ) const { return test_primary.value; } ++ uint64_t by_secondary( ) const { return secondary.value; } + }; +``` + +3. In the `test_table` alias definition (typedef), add the definition of the secondary index by making use of the `eosio::indexed_by` template. `eosio::index_by` needs two parameters: the name of the index, `"secid"_n`, and a function call operator which extracts the value from the secondary data member as an index key. The function call operator is achieved by employing the `eosio::const_mem_fun` template which receives two parameters: the data structure `test_table` and the reference to the getter function member `by_secondary`. + +```diff +- typedef eosio::multi_index<"testtaba"_n, test_table> test_tables; ++ typedef eosio::multi_index<"testtaba"_n, test_table, eosio::indexed_by<"secid"_n, eosio::const_mem_fun>> test_tables; +``` + +The full contract definition code with all the changes described above could look like this: + +__multi_index_example.hpp__ +```cpp +#include +using namespace eosio; + +// multi index example contract class +class [[eosio::contract]] multi_index_example : public contract { + public: + using contract::contract; + + // contract class constructor + multi_index_example( name receiver, name code, datastream ds ) : + // contract base class contructor + contract(receiver, code, ds), + // instantiate multi index instance as data member (find it defined below) + testtab(receiver, receiver.value) + { } + + struct [[eosio::table]] test_table { + // this field stores a name for each row of the multi index table + name test_primary; + name secondary; + // additional data stored in table row + uint64_t datum; + // mandatory definition for primary key getter + uint64_t primary_key( ) const { return test_primary.value; } + uint64_t by_secondary( ) const { return secondary.value; } + }; + + // the multi index type definition, for ease of use a type alias `test_tables` is defined, + // based on the multi_index template type, parametarized with a random name, the + // test_table data structure, and the secondary index + typedef eosio::multi_index<"testtaba"_n, test_table, eosio::indexed_by<"secid"_n, eosio::const_mem_fun>> test_tables; + + // the multi index table instance declared as a data member of type test_tables + test_tables testtab; + + [[eosio::action]] void set( name user ); + [[eosio::action]] void print( name user ); + + using set_action = action_wrapper<"set"_n, &multi_index_example::set>; + using print_action = action_wrapper<"print"_n, &multi_index_example::print>; +}; +``` + +[[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 diff --git a/docs/06_how-to-guides/02_multi-index/how-to-define-a-singleton.md b/docs/06_how-to-guides/02_multi-index/how-to-define-a-singleton.md new file mode 100644 index 0000000000..ce861a6103 --- /dev/null +++ b/docs/06_how-to-guides/02_multi-index/how-to-define-a-singleton.md @@ -0,0 +1,114 @@ +## How to define a singleton + +To define a simple singleton, which is storing an account name as primary value and a uint64_t as secondary value in structure `testtable`, follow the steps below: + +1. Include the `eosio.hpp` and `singleton.hpp` headers and declare the `eosio` namespace usage +``` +#include +#include +using namespace eosio; +``` + +2. Define the data structure for the multi index table +```cpp +struct [[eosio::table]] testtable { + name primary_value; + uint64_t secondary_value; +}; +``` + +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 +```diff +struct [[eosio::table]] testtable { + name primary_value; + uint64_t secondary_value; +}; ++using singleton_type = eosio::singleton<"testsingletona"_n, testtable>; +``` + +4. Define the singleton table instance declared as a data member of type `singleton_type` defined in the privious step +```diff +struct [[eosio::table]] testtable { + name primary_value; + uint64_t secondary_value; +}; + +using singleton_type = eosio::singleton<"testsingletona"_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: +```diff +// singleton contract constructor +singleton_example( name receiver, name code, datastream ds ) : + contract(receiver, code, ds), ++ singleton_instance(receiver, receiver.value) + { } +} +``` + +Now you have defined and instantiated a singleton. Below you can find a possible implementation for the full class singleton example contract. + +__singleton_example.hpp__ +```cpp +#include +#include +using namespace eosio; + +class [[eosio::contract]] singleton_example : public contract { + public: + using contract::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( ); + + struct [[eosio::table]] testtable { + name primary_value; + uint64_t secondary_value; + } tt; + + using singleton_type = eosio::singleton<"testsingletona"_n, testtable>; + singleton_type singleton_instance; + + using set_action = action_wrapper<"set"_n, &singleton_example::set>; + using get_action = action_wrapper<"get"_n, &singleton_example::get>; +}; +``` + +And below is a possible implementation for the two `get` and `set` actions defined above. It also demonstrates the usage of a couple of singleton methods. Note that the `set` action makes use of the singleton's `set` method, for which parameter is the account to pay for the new value stored. In this case, the same account name that is stored in the primary value is the payer. However, it can be a different account if so required. + +__singleton_example.cpp__ +```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(); + entry_stored.primary_value = user; + entry_stored.secondary_value = value; + singleton_instance.set(entry_stored, user); +} + +[[eosio::action]] void singleton_example::get( ) { + if (singleton_instance.exists()) + eosio::print( + "Value stored for: ", + name{singleton_instance.get().primary_value.value}, + " is ", + singleton_instance.get().secondary_value, + "\n"); + else + eosio::print("Singleton is empty\n"); +} +``` + + +[[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/02_multi-index/how-to-delete-data-from-a-multi-index-table.md new file mode 100644 index 0000000000..5ea7abf61c --- /dev/null +++ b/docs/06_how-to-guides/02_multi-index/how-to-delete-data-from-a-multi-index-table.md @@ -0,0 +1,31 @@ +## How to delete data from a multi index table + +### Preconditions +- It is assumed you already have a multi index table instance defined along with its mandatory primary index, otherwise take a look at the section [How to instantiate a multi index table](./how-to-instantiate-a-multi-index-table.md). + +To delete data from a multi index table follow the steps below: + +1. Make use of the multi index table iterator to find out if the data exists +```cpp +[[eosio::action]] void multi_index_example::del( name user ) { + // check if the user already exists + auto itr = testtab.find(user.value); +} +``` + +2. If the data exists use the `delete` method to delete the row from table +```diff +[[eosio::action]] void multi_index_example::del( name user ) { + // check if the user already exists + auto itr = testtab.find(user.value); ++ if ( itr == testtab.end() ) { ++ printf("user does not exist in table, nothing to delete" ); ++ return; ++ } + ++ testtab.erase( itr ); +} +``` + +[[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 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/02_multi-index/how-to-insert-data-into-a-multi-index-table.md new file mode 100644 index 0000000000..ac5510fbe2 --- /dev/null +++ b/docs/06_how-to-guides/02_multi-index/how-to-insert-data-into-a-multi-index-table.md @@ -0,0 +1,34 @@ +## How to insert data into a multi index table + +### Preconditions +- It is assumed you already have a multi index table instance defined along with its mandatory primary index, otherwise take a look at the section [How to instantiate a multi index table](./how-to-instantiate-a-multi-index-table.md). + +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 +```cpp +[[eosio::action]] void multi_index_example::set( name user ) { + // check if the user already exists + auto itr = testtab.find(user.value); + +} +``` + +2. Use the `emplace` method to make the insertion if the user is not already in table +```diff +[[eosio::action]] void multi_index_example::set( name user ) { + // check if the user already exists + auto itr = testtab.find(user.value); + ++ if ( itr == testtab.end() ) { ++ testtab.emplace( _self, [&]( auto& u ) { ++ u.test_primary = user; ++ u.secondary = "second"_n; ++ u.datum = 0; ++ }); ++ } +} +``` + +[[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 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/02_multi-index/how-to-instantiate-a-multi-index-table.md new file mode 100644 index 0000000000..c1c2d33877 --- /dev/null +++ b/docs/06_how-to-guides/02_multi-index/how-to-instantiate-a-multi-index-table.md @@ -0,0 +1,131 @@ +## How to instantiate a multi index table + +1. Include the `eosio.hpp` header and declare the `eosio` namespace usage +``` +#include +using namespace eosio; +``` +2. Define the data structure for the multi index table +```cpp + struct [[eosio::table]] test_table { + }; +``` +3. Add to the data structure the fields which define the multi index table +```diff + // the data structure which defines each row of the table + struct [[eosio::table]] test_table { ++ // this field stores a name for each row of the multi index table ++ name test_primary; ++ // additional data stored in table row, e.g. an uint64_t type data ++ 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: +```diff + // the data structure which defines each row of the table + struct [[eosio::table]] test_table { + // this field stores a name for each row of the multi index table + name test_primary; + // additional data stored in table row + uint64_t datum; ++ // mandatory definition for primary key getter ++ uint64_t primary_key( ) const { return test_primary.value; } + }; +``` + +[[Info | Additional indexes information]] +| Other, secondary, indexes if they will be defined can have duplicates. You can have up to 16 additional indexes and the field types can be uint64_t, uint128_t, uint256_t, double or long double. + +5. For ease of use, define a type alias `test_tables` based on the multi_index template type, parametarized with a random name `"testtaba"` and the `test_table` data structure defined above +```diff + // the data structure which defines each row of the table + struct [[eosio::table]] test_table { + // this field stores a name for each row of the multi index table + name test_primary; + // additional data stored in table row + uint64_t datum; + // mandatory definition for primary key getter + uint64_t primary_key( ) const { return test_primary.value; } + }; + ++ 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 +```diff + // the data structure which defines each row of the table + struct [[eosio::table]] test_table { + // this field stores a name for each row of the multi index table + name test_primary; + // additional data stored in table row + uint64_t datum; + // mandatory definition for primary key getter + uint64_t primary_key( ) const { return test_primary.value; } + }; + + typedef eosio::multi_index<"testtaba"_n, test_table> test_tables; ++ test_tables testtab; +``` + +7. Instantiate the data member `testtab` by passing to its constructor the `scope` (in this case `receiver`) and the `code` parameters, these two combined with table name `"testtaba"` provide access to the partition of the RAM cache used by this multi index table, in this example you will initialize the `testtab` data member in the smart contract constructor + +```diff +// contract class constructor +multi_index_example( name receiver, name code, datastream ds ) : + // contract base class contructor + contract(receiver, code, ds), + // instantiate multi index instance as data member (find it defined below) ++ testtab(receiver, receiver.value) + { } +``` +Now you have instantiated the `testtab` variable as a multi index table which has a primary index defined for its `test_primary` data member. + +Here is how the definition of a `multi_index_example` contract containing a multi index table could look like after following all the steps above. + +__multi_index_example.hpp__ +```cpp +#include +using namespace eosio; + +// multi index example contract class +class [[eosio::contract]] multi_index_example : public contract { + public: + using contract::contract; + + // contract class constructor + multi_index_example( name receiver, name code, datastream ds ) : + // contract base class contructor + contract(receiver, code, ds), + // instantiate multi index instance as data member (find it defined below) + testtab(receiver, receiver.value) + { } + + // the row structure of the multi index table, that is, each row of the table + // will contain an instance of this type of structure + struct [[eosio::table]] test_table { + // this field stores a name for each row of the multi index table + name test_primary; + // additional data stored in table row + uint64_t datum; + // mandatory definition for primary key getter + uint64_t primary_key( ) const { return test_primary.value; } + }; + + // the multi index type definition, for ease of use define a type alias `test_tables`, + // based on the multi_index template type, parametarized with a random name and + // the test_table data structure + typedef eosio::multi_index<"testtaba"_n, test_table> test_tables; + + // the multi index table instance declared as a data member of type test_tables + test_tables testtab; + + [[eosio::action]] void set( name user ); + [[eosio::action]] void print( name user ); + + using set_action = action_wrapper<"set"_n, &multi_index_example::set>; + using print_action = action_wrapper<"print"_n, &multi_index_example::print>; +}; +``` + +[[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 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/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table-based-on-secondary-index.md new file mode 100644 index 0000000000..400abb4156 --- /dev/null +++ b/docs/06_how-to-guides/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table-based-on-secondary-index.md @@ -0,0 +1,173 @@ +## How to iterate and retreive a multi index table based on secondary index + +### Preconditions +- It is assumed you already have a multi index table defined with a primary index and a secondary index, if not you can find an example [here](./how-to-define-a-secondary-index.md). + +You'll start with this example below which shows the definition of a `multi_index_example` contract class which has defined a multi index table with two indexes, a mandatory primary one and a secondary one: + +```cpp +#include +using namespace eosio; + +// multi index example contract class +class [[eosio::contract]] multi_index_example : public contract { + public: + using contract::contract; + + // contract class constructor + multi_index_example( name receiver, name code, datastream ds ) : + // contract base class contructor + contract(receiver, code, ds), + // instantiate multi index instance as data member (find it defined below) + testtab(receiver, receiver.value) + { } + + // the row structure of the multi index table, that is, each row of the table + // will contain an instance of this type of structure + struct [[eosio::table]] test_table { + // this field stores a name for each row of the multi index table + name test_primary; + name secondary; + // additional data stored in table row + uint64_t datum; + // mandatory definition for primary key getter + uint64_t primary_key( ) const { return test_primary.value; } + uint64_t by_secondary( ) const { return secondary.value; } + }; + + // the multi index type definition, for ease of use define a type alias `test_tables`, + // based on the multi_index template type, parametarized with a random name, the + // test_table data structure, and the secondary index + typedef eosio::multi_index<"testtaba"_n, test_table, eosio::indexed_by<"secid"_n, eosio::const_mem_fun>> test_tables; + + // the multi index table instance declared as a data member of type test_tables + test_tables testtab; + + [[eosio::action]] void set( name user ); + [[eosio::action]] void print( name user ); + + using set_action = action_wrapper<"set"_n, &multi_index_example::set>; + using print_action = action_wrapper<"print"_n, &multi_index_example::print>; +}; +``` + +To iterate and retreive the multi index table `testtab` defined in `multi_index_example` contract based on secondary index `by_secondary`, define a third action `bysec` which will do exactly that. + +1. In the contract definition, add the new action definition, using the `[[eosio::action]] void` and the `eosio::action_wrapper` template like this: + +```cpp + [[eosio::action]] void bysec( name secid ); + + using bysec_action = action_wrapper<"bysec"_n, &multi_index_example::bysec>; +``` + +2. In the contract implementation add the new action implementation like this: + +```cpp +// iterates the multi index table rows using the secondary index and prints the row's values +[[eosio::action]] void multi_index_example::bysec( name secid ) { + // access the secondary index + auto idx = testtab.get_index<"secid"_n>(); + // iterate through secondary index + for ( auto itr = idx.begin(); itr != idx.end(); itr++ ) { + // print each row's values + eosio::print_f("Test Table : {%, %, %}\n", itr->test_primary, itr->secondary, itr->datum); + } +} +``` + +3. The full code for both the contract definition and contract implementation follow: + +__multi_index_example.hpp__ +```cpp +#include +using namespace eosio; + +// multi index example contract class +class [[eosio::contract]] multi_index_example : public contract { + public: + using contract::contract; + + // contract class constructor + multi_index_example( name receiver, name code, datastream ds ) : + // contract base class contructor + contract(receiver, code, ds), + // instantiate multi index instance as data member (find it defined below) + testtab(receiver, receiver.value) + { } + + // the row structure of the multi index table, that is, each row of the table + // will contain an instance of this type of structure + struct [[eosio::table]] test_table { + // this field stores a name for each row of the multi index table + name test_primary; + name secondary; + // additional data stored in table row + uint64_t datum; + // mandatory definition for primary key getter + uint64_t primary_key( ) const { return test_primary.value; } + uint64_t by_secondary( ) const { return secondary.value; } + }; + + // the multi index type definition, for ease of use define a type alias `test_tables`, + // based on the multi_index template type, parametarized with a random name, the + // test_table data structure, and the secondary index + typedef eosio::multi_index<"testtaba"_n, test_table, eosio::indexed_by<"secid"_n, eosio::const_mem_fun>> test_tables; + + // the multi index table instance declared as a data member of type test_tables + test_tables testtab; + + [[eosio::action]] void set( name user ); + [[eosio::action]] void print( name user ); + [[eosio::action]] void bysec( name secid ); + + using set_action = action_wrapper<"set"_n, &multi_index_example::set>; + using print_action = action_wrapper<"print"_n, &multi_index_example::print>; + using bysec_action = action_wrapper<"bysec"_n, &multi_index_example::bysec>; +}; +``` + +__multi_index_example.cpp__ +```cpp +#include + +[[eosio::action]] void multi_index_example::set( name user ) { + // check if the user already exists + auto itr = testtab.find(user.value); + + if ( itr == testtab.end() ) { + // user is not found in table, use emplace to insert a new row data structure in table + testtab.emplace( _self, [&]( auto& u ) { + u.test_primary = user; + u.secondary = "second"_n; + u.datum = 0; + }); + } +} + +[[eosio::action]] void multi_index_example::print( name user ) { + // searches for the row that corresponds to the user parameter + auto itr = testtab.find(user.value); + + // asserts if the row was found for user parameter, if fails use the given message + check( itr != testtab.end(), "user does not exist in table" ); + + // prints the test_primary and datum fields stored for user parameter + eosio::print_f("Test Table : {%, %}\n", itr->test_primary, itr->datum); +} + +// iterates the multi index table rows using the secondary index and prints the row's values +[[eosio::action]] void multi_index_example::bysec( name secid ) { + // access the secondary index + auto idx = testtab.get_index<"secid"_n>(); + + // iterate through secondary index + for ( auto itr = idx.begin(); itr != idx.end(); itr++ ) { + // print each row's values + eosio::print_f("Test Table : {%, %, %}\n", itr->test_primary, itr->secondary, itr->datum); + } +} +``` + +[[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 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/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table.md new file mode 100644 index 0000000000..7aae513058 --- /dev/null +++ b/docs/06_how-to-guides/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table.md @@ -0,0 +1,155 @@ +## How to iterate and retrieve a multi index table + +### Preconditions +- It is assumed you already have a multi index table instance defined along with its mandatory primary index, otherwise take a look at the section [How to instantiate a multi index table](./how-to-instantiate-a-multi-index-table.md). + +For exemplification define the multi index contract definition like below: + +__multi_index_example.hpp__ +```cpp +#include +using namespace eosio; + +// multi index example contract class +class [[eosio::contract]] multi_index_example : public contract { + public: + using contract::contract; + + // contract class constructor + multi_index_example( name receiver, name code, datastream ds ) : + // contract base class contructor + contract(receiver, code, ds), + // instantiate multi index instance as data member (find it defined below) + testtab(receiver, receiver.value) + { } + + // the row structure of the multi index table, that is, each row of the table + // will contain an instance of this type of structure + struct [[eosio::table]] test_table { + // this field stores a name for each row of the multi index table + name test_primary; + // additional data stored in table row + uint64_t datum; + // mandatory definition for primary key getter + uint64_t primary_key( ) const { return test_primary.value; } + }; + + // the multi index type definition, for ease of use define a type alias `test_tables`, + // based on the multi_index template type, parametarized with a random name and + // the test_table data structure + typedef eosio::multi_index<"testtaba"_n, test_table> test_tables; + + // the multi index table instance declared as a data member of type test_tables + test_tables testtab; + + [[eosio::action]] void set( name user ); + + using set_action = action_wrapper<"set"_n, &multi_index_example::set>; +}; +``` + +The steps below show how to iterate and retrieve a multi index table. + +1. Add to the above multi index example contract an action `print` which gets as parameter an acount name + +```cpp +[[eosio::action]] void print( name user ); +``` +2. For ease of use add the action wrapper defition as well +```diff +[[eosio::action]] void print( name user ); + ++using print_action = action_wrapper<"print"_n, &multi_index_example::print>; +``` +3. Implement the action code, by searching for the `user` name in the multi index table using the primary index. If found, print out the value stored in that row for field `datum`. Otherwise asserts with a custom message. In the contract definition add the following implementation for `print` action: +```cpp + [[eosio::action]] void multi_index_example::print( name user ) { + // searches for the row that corresponds to the user parameter + auto itr = testtab.find(user.value); + + // asserts if the row was found for user parameter, if fails use the given message + check( itr != testtab.end(), "user does not exist in table" ); + + // prints the test_primary and datum fields stored for user parameter + eosio::print_f("Test Table : {%, %}\n", itr->test_primary, itr->datum); +} +``` +4. Finally the whole definition and implementation files for the contract should look like this: + +__multi_index_example.hpp__ +```cpp +#include +using namespace eosio; + +// multi index example contract class +class [[eosio::contract]] multi_index_example : public contract { + public: + using contract::contract; + + // contract class constructor + multi_index_example( name receiver, name code, datastream ds ) : + // contract base class contructor + contract(receiver, code, ds), + // instantiate multi index instance as data member (find it defined below) + testtab(receiver, receiver.value) + { } + + // the row structure of the multi index table, that is, each row of the table + // will contain an instance of this type of structure + struct [[eosio::table]] test_table { + // this field stores a name for each row of the multi index table + name test_primary; + // additional data stored in table row + uint64_t datum; + // mandatory definition for primary key getter + uint64_t primary_key( ) const { return test_primary.value; } + }; + + // the multi index type definition, for ease of use define a type alias `test_tables`, + // based on the multi_index template type, parametarized with a random name and + // the test_table data structure + typedef eosio::multi_index<"testtaba"_n, test_table> test_tables; + + // the multi index table instance declared as a data member of type test_tables + test_tables testtab; + + [[eosio::action]] void set( name user ); + [[eosio::action]] void print( name user ); + + using set_action = action_wrapper<"set"_n, &multi_index_example::set>; + using print_action = action_wrapper<"print"_n, &multi_index_example::print>; +}; +``` + +__multi_index_example.cpp__ +```cpp +#include + +[[eosio::action]] void multi_index_example::set( name user ) { + // check if the user already exists + auto itr = testtab.find(user.value); + + if ( itr == testtab.end() ) { + // user is not found in table, use emplace to insert a new row data structure in table + testtab.emplace( _self, [&]( auto& u ) { + u.test_primary = user; + u.secondary = "second"_n; + u.datum = 0; + }); + } +} + +[[eosio::action]] void multi_index_example::print( name user ) { + // searches for the row that corresponds to the user parameter + auto itr = testtab.find(user.value); + + // asserts if the row was found for user parameter, if fails use the given message + check( itr != testtab.end(), "user does not exist in table" ); + + // prints the test_primary and datum fields stored for user parameter + eosio::print_f("Test Table : {%, %}\n", itr->test_primary, itr->datum); +} +``` + +[[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 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/02_multi-index/how-to-modify-data-in-a-multi-index-table.md new file mode 100644 index 0000000000..af5f600c59 --- /dev/null +++ b/docs/06_how-to-guides/02_multi-index/how-to-modify-data-in-a-multi-index-table.md @@ -0,0 +1,39 @@ +## How to modify data in a multi index table + +### Preconditions +- It is assumed you already have a multi index table instance defined along with its mandatory primary index, otherwise take a look at the section [How to instantiate a multi index table](./how-to-instantiate-a-multi-index-table.md). + +To modify data in the multi index table defined in the above tutorial, you will implement an action `mod` which it will receive as parameter the `user` which is the key of the row you want to modify and the `value` param which is the value to update with the row. + +1. Make use of the multi index table iterator to find out if the data exists +```cpp +[[eosio::action]] void multi_index_example::mod( name user, uint32_t value ) { + auto itr = testtab.find(user.value); +} +``` + +2. If the row you want to update is not found, then assert by using the `check` method and yield an error message +```diff +[[eosio::action]] void multi_index_example::mod( name user, uint32_t value ) { + auto itr = testtab.find(user.value); ++ check( itr != testtab.end(), "user does not exist in table" ); +} +``` + +3. If the row you want to update is found, the `check` method will do nothing and the iterator `itr` will be pointing at the row which you want to update, so then use the multi index `modify` method to make the update like below + +```diff +[[eosio::action]] void multi_index_example::mod( name user, uint32_t value ) { + // check if the user already exists + auto itr = testtab.find(user.value); + check( itr != testtab.end(), "user does not exist in table" ); + ++ testtab.modify( itr, _self, [&]( auto& row ) { ++ row.secondary = user; ++ row.datum = value; ++ }); +} +``` + +[[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/04_how_to_create_and_use_action_wrappers.md b/docs/06_how-to-guides/04_how_to_create_and_use_action_wrappers.md new file mode 100644 index 0000000000..e6f354f770 --- /dev/null +++ b/docs/06_how-to-guides/04_how_to_create_and_use_action_wrappers.md @@ -0,0 +1,40 @@ +## 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 new file mode 100644 index 0000000000..a1a2754560 --- /dev/null +++ b/docs/06_how-to-guides/05_authorization/how_to_restrict_access_to_an_action_by_user.md @@ -0,0 +1,31 @@ +## 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/08_troubleshooting.md b/docs/08_troubleshooting.md new file mode 100644 index 0000000000..b3465e9ad2 --- /dev/null +++ b/docs/08_troubleshooting.md @@ -0,0 +1,132 @@ +## Troubleshooting + +### When sending an action to the blockchain you get the error below +```{ + "code":500, + "message":"Internal Service Error", + "error":{ + "code":3090003, + "name":"unsatisfied_authorization", + "what":"Provided keys, permissions, and delays do not satisfy declared authorizations", + "details":[ + { + "message":"transaction declares authority '{"actor":"account_name","permission":"permission_name"}', but does not have signatures for it under a provided delay of 0 ms, provided permissions [], provided keys ["EOS5ZcMvpgtDMdVtvCFewAQYTyfN6Vqhg4kdgauffx3jiaKaeWfY1"], and a delay max limit of 3888000000 ms", + "file":"authorization_manager.cpp", + "line_number":524, + "method":"check_authorization" + } + ] + } +} +``` +__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 +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: +```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 +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 +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 +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. + +__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 +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. + +__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. + +__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 +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. + +__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. + +The below code will print just the first line of the iteration. + +```cpp + auto index=0; + for (auto& item : testtab) + { + eosio::print_f("{item %}={%, %, %} \n", ++index, item.test_primary, item.secondary, item.datum); + } +``` + +The below code will print all lines of the iteration separated by `'|'` char. +```cpp + auto index=0; + for (auto& item : testtab) + { + eosio::print_f("{item %}={%, %, %} |", ++index, item.test_primary, item.secondary, item.datum); + } +``` + +### 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: + +```cpp +[[eosio::action]] void multi_index_example::mod( name user, uint64_t n ) { + + // `mod` action implementation code goes here... + + print_f("Output line before the inline send action.") + + singleton_set_action singleton_set("eostutorial1"_n, {get_self(), "active"_n}); + singleton_set.send(get_self(), n, get_self()); + + print_f("Output line after the inline send action.") +} +``` + +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 diff --git a/docs/09_tutorials/01_binary-extension.md b/docs/09_tutorials/01_binary-extension.md new file mode 100644 index 0000000000..b48f8cb80b --- /dev/null +++ b/docs/09_tutorials/01_binary-extension.md @@ -0,0 +1,787 @@ +# eosio::binary_extension + +You can find the implementation of `eosio::binary_extension` in the `eosio.cdt` repository in [binary_extension.hpp](https://github.com/EOSIO/eosio.cdt/blob/master/libraries/eosiolib/binary_extension.hpp). + +The primary concern when using this type is when you are adding a new field to a smart contract's data structure that is currently utilized in an `eosio::multi_index` type (AKA a _table_), or when adding a new parameter to an action declaration. + +By wrapping the new field in an `eosio::binary_extension`, you are enabling your contract to be backwards compatible for future use. Note that this new field/parameter **MUST** be appended at the end of a data structure (this is due to implementation details in `eosio::multi_index`, which relies on the `boost::multi_index` type), or at the end of the parameter list in an action declaration. + +If you don't wrap the new field in an `eosio::binary_extension`, the `eosio::multi_index` table will be reformatted in such a way that disallows reads to the former datum; or in an action's case, the function will be un-callable. + +
How the `eosio::binary_extension` type works + +Take a moment to study this smart contract and its corresponding `.abi`. + +This contract not only serves as a good example to the `eosio::binary_extension` type, but can also be used as a gateway for developing smart contracts on the eosio protocol. + +**binary_extension_contract.hpp** + +```c++ +#include // eosio::contract +#include // eosio::binary_extension +#include // eosio::datastream +#include // eosio::name +#include // eosio::indexed_by, eosio::multi_index +#include // eosio::print_f + +class [[eosio::contract]] binary_extension_contract : public eosio::contract { +public: + using contract::contract; + binary_extension_contract(eosio::name receiver, eosio::name code, eosio::datastream ds) + : contract{receiver, code, ds}, _table{receiver, receiver.value} + { } + + [[eosio::action]] void regpkey (eosio::name primary_key); ///< Register primary key. + [[eosio::action]] void printbyp(eosio::name primary_key); ///< Print by primary key. + [[eosio::action]] void printbys(eosio::name secondary_key); ///< Print by secondary key. + [[eosio::action]] void modifyp (eosio::name primary_key, eosio::name n); ///< Modify primary key by primary key. + [[eosio::action]] void modifys (eosio::name primary_key, eosio::name n); ///< Modify secondary key by primary key. + + struct [[eosio::table]] structure { + eosio::name _primary_key; + eosio::name _secondary_key; + + uint64_t primary_key() const { return _primary_key.value; } + uint64_t secondary_key() const { return _secondary_key.value; } + }; + + using index1 = eosio::indexed_by<"index1"_n, eosio::const_mem_fun>; + using index2 = eosio::indexed_by<"index2"_n, eosio::const_mem_fun>; + using table = eosio::multi_index<"table"_n, structure, index1, index2>; + +private: + table _table; +}; + +``` + +**binary_extension_contract.cpp** + +```c++ +#include "binary_extension_contract.hpp" + +using eosio::name; + +[[eosio::action]] void binary_extension_contract::regpkey(name primary_key) { + eosio::print_f("`regpkey` executing.\n"); + + auto index{_table.get_index<"index1"_n>()}; ///< `index` represents `_table` organized by `index1`. + auto iter {index.find(primary_key.value) }; ///< Note: the type returned by `index.find` is different than the type returned by `_table.find`. + + if (iter == _table.get_index<"index1"_n>().end()) { + eosio::print_f("`_primary_key`: % not found; registering.\n", primary_key.to_string()); + _table.emplace(_self, [&](auto& row) { + row._primary_key = primary_key; + row._secondary_key = "nothin"_n; + }); + } + else { + eosio::print_f("`_primary_key`: % found; not registering.\n", primary_key.to_string()); + } + + eosio::print_f("`regpkey` finished executing.\n"); +} + +[[eosio::action]] void binary_extension_contract::printbyp(eosio::name primary_key) { + eosio::print_f("`printbyp` executing.\n"); + + auto index{_table.get_index<"index1"_n>()}; + auto iter {index.find(primary_key.value) }; + + if (iter != _table.get_index<"index1"_n>().end()) { + eosio::print_f("`_primary_key`: % found; printing.\n", primary_key.to_string()); + eosio::print_f("{%, %}\n", iter->_primary_key, iter->_secondary_key); + } + else { + eosio::print_f("`_primary_key`: % not found; not printing.\n", primary_key.to_string()); + } + + eosio::print_f("`printbyp` finished executing.\n"); +} + +[[eosio::action]] void binary_extension_contract::printbys(eosio::name secondary_key) { + eosio::print_f("`printbys` executing.\n"); + + auto index{_table.get_index<"index2"_n>()}; + auto iter {index.find(secondary_key.value)}; + + if (iter != _table.get_index<"index2"_n>().end()) { + eosio::print_f("`_secondary_key`: % found; printing.\n", secondary_key.to_string()); + printbyp(iter->_primary_key); + } + else { + eosio::print_f("`_secondary_key`: % not found; not printing.\n", secondary_key.to_string()); + } + + eosio::print_f("`printbys` finished executing.\n"); +} + +[[eosio::action]] void binary_extension_contract::modifyp(eosio::name primary_key, name n) { + eosio::print_f("`modifyp` executing.\n"); + + auto index{_table.get_index<"index1"_n>()}; + auto iter {index.find(primary_key.value)}; + + if (iter != _table.get_index<"index1"_n>().end()) { + eosio::print_f("`_primary_key`: % found; modifying `_primary_key`.\n", primary_key.to_string()); + index.modify(iter, _self, [&](auto& row) { + row._primary_key = n; + }); + } + else { + eosio::print_f("`_primary_key`: % not found; not modifying `_primary_key`.\n", primary_key.to_string()); + } + + eosio::print_f("`modifyp` finished executing.\n"); +} + +[[eosio::action]] void binary_extension_contract::modifys(eosio::name primary_key, name n) { + eosio::print_f("`modifys` executing.\n"); + + auto index{_table.get_index<"index1"_n>()}; + auto iter {index.find(primary_key.value)}; + + if (iter != _table.get_index<"index1"_n>().end()) { + eosio::print_f("`_primary_key`: % found; modifying `_secondary_key`.\n", primary_key.to_string()); + index.modify(iter, _self, [&](auto& row) { + row._secondary_key = n; + }); + } + else { + eosio::print_f("`_primary_key`: % not found; not modifying `_secondary_key`.\n", primary_key.to_string()); + } + + eosio::print_f("`modifys` finished executing.\n"); +} +``` + +**binary_extension_contract.abi** + +```javascript +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.1", + "types": [], + "structs": [ + { + "name": "modifyp", + "base": "", + "fields": [ + { + "name": "primary_key", + "type": "name" + }, + { + "name": "n", + "type": "name" + } + ] + }, + { + "name": "modifys", + "base": "", + "fields": [ + { + "name": "primary_key", + "type": "name" + }, + { + "name": "n", + "type": "name" + } + ] + }, + { + "name": "printbyp", + "base": "", + "fields": [ + { + "name": "primary_key", + "type": "name" + } + ] + }, + { + "name": "printbys", + "base": "", + "fields": [ + { + "name": "secondary_key", + "type": "name" + } + ] + }, + { + "name": "regpkey", + "base": "", + "fields": [ + { + "name": "primary_key", + "type": "name" + } + ] + }, + { + "name": "structure", + "base": "", + "fields": [ + { + "name": "_primary_key", + "type": "name" + }, + { + "name": "_secondary_key", + "type": "name" + } + ] + } + ], + "actions": [ + { + "name": "modifyp", + "type": "modifyp", + "ricardian_contract": "" + }, + { + "name": "modifys", + "type": "modifys", + "ricardian_contract": "" + }, + { + "name": "printbyp", + "type": "printbyp", + "ricardian_contract": "" + }, + { + "name": "printbys", + "type": "printbys", + "ricardian_contract": "" + }, + { + "name": "regpkey", + "type": "regpkey", + "ricardian_contract": "" + } + ], + "tables": [ + { + "name": "table", + "type": "structure", + "index_type": "i64", + "key_names": [], + "key_types": [] + } + ], + "ricardian_clauses": [], + "variants": [] +} +``` + +
+ +Take note of the action `regpkey`, and the struct `structure` in `con.hpp` and `con.cpp`; the parts of the contract you will be upgrading. + +**binary_extension_contract.hpp** + +```c++ +[[eosio::action]] void regpkey (eosio::name primary_key); +``` + +```c++ +struct [[eosio::table]] structure { + eosio::name _primary_key; + eosio::name _secondary_key; + + uint64_t primary_key() const { return _primary_key.value; } + uint64_t secondary_key() const { return _secondary_key.value; } +}; +``` + +**binary_extension_contract.cpp** + +```c++ +[[eosio::action]] void binary_extension_contract::regpkey(name primary_key) { + eosio::print_f("`regpkey` executing.\n"); + + auto index{_table.get_index<"index1"_n>()}; ///< `index` represents `_table` organized by `index1`. + auto iter {index.find(primary_key.value) }; ///< Note: the type returned by `index.find` is different than the type returned by `_table.find`. + + if (iter == _table.get_index<"index1"_n>().end()) { + eosio::print_f("`_primary_key`: % not found; registering.\n", primary_key.to_string()); + _table.emplace(_self, [&](auto& row) { + row._primary_key = primary_key; + row._secondary_key = "nothin"_n; + }); + } + else { + eosio::print_f("`_primary_key`: % found; not registering.\n", primary_key.to_string()); + } + + eosio::print_f("`regpkey` finished executing.\n"); +} +``` + +And their corresponding sections in the `.abi` files: + +**binary_extension_contract.abi** + +```javascript +{ + "name": "regpkey", + "base": "", + "fields": [ + { + "name": "primary_key", + "type": "name" + } + ] +} +``` + +```javascript +{ + "name": "structure", + "base": "", + "fields": [ + { + "name": "_primary_key", + "type": "name" + }, + { + "name": "_secondary_key", + "type": "name" + } + ] +} +``` + +
Start up a blockchain instance, compile this smart contract, and test it out. + +``` +~/binary_extension_contract $ eosio-cpp binary_extension_contract.cpp -o binary_extension_contract.wasm +``` + +``` +~/binary_extension_contract $ cleos set contract eosio ./ +``` + +``` +Reading WASM from /Users/john.debord/binary_extension_contract/binary_extension_contract.wasm... +Publishing contract... +executed transaction: 6c5c7d869a5be67611869b5f300bc452bc57d258d11755f12ced99c7d7fe154c 4160 bytes 729 us +# eosio <= eosio::setcode "0000000000ea30550000d7600061736d01000000018f011760000060017f0060027f7f0060037f7f7f017f6000017e60067... +# eosio <= eosio::setabi "0000000000ea3055d1020e656f73696f3a3a6162692f312e310006076d6f646966797000020b7072696d6172795f6b65790... +warning: transaction executed locally, but may not be confirmed by the network yet +``` + +Next, push some data to the contract defined. + +``` +~/binary_extension_contract $ cleos push action eosio regpkey '{"primary_key":"eosio.name"}' -p eosio +``` + +``` +executed transaction: 3c708f10dcbf4412801d901eb82687e82287c2249a29a2f4e746d0116d6795f0 104 bytes 248 us +# eosio <= eosio::regpkey {"primary_key":"eosio.name"} +[(eosio,regpkey)->eosio]: CONSOLE OUTPUT BEGIN ===================== +`regpkey` executing. +`_primary_key`: eosio.name not found; registering. +`regpkey` finished executing. +[(eosio,regpkey)->eosio]: CONSOLE OUTPUT END ===================== +warning: transaction executed locally, but may not be confirmed by the network yet +``` + +Finally, read back the data you have just written. + +``` +~/binary_extension_contract $ cleos push action eosio printbyp '{"primary_key":"eosio.name"}' -p eosio +``` + +``` +executed transaction: e9b77d3cfba322a7a3a93970c0c883cb8b67e2072a26d714d46eef9d79b2f55e 104 bytes 227 us +# eosio <= eosio::printbyp {"primary_key":"eosio.name"} +[(eosio,printbyp)->eosio]: CONSOLE OUTPUT BEGIN ===================== +`printbyp` executing. +`_primary_key`: eosio.name found; printing. +{eosio.name, nothin} +`printbyp` finished executing. +[(eosio,printbyp)->eosio]: CONSOLE OUTPUT END ===================== +warning: transaction executed locally, but may not be confirmed by the network yet +``` + +
Upgrade the smart contract by adding a new field to the table and a new parameter to an action while **NOT** wrapping the new field/parameter in an `eosio::binary_extension` type and see what happens: + +**binary_extension_contract.hpp** + +```diff ++[[eosio::action]] void regpkey (eosio::name primary_key, eosio::name secondary_key); +-[[eosio::action]] void regpkey (eosio::name primary_key); +``` + +```diff +struct [[eosio::table]] structure { + eosio::name _primary_key; + eosio::name _secondary_key; ++ eosio::name _non_binary_extension_key; + + uint64_t primary_key() const { return _primary_key.value; } + uint64_t secondary_key() const { return _secondary_key.value; } +}; +``` + +**binary_extension_contract.cpp** + +```diff ++[[eosio::action]] void binary_extension_contract::regpkey(name primary_key, name secondary_key) { +-[[eosio::action]] void binary_extension_contract::regpkey(name primary_key) { + eosio::print_f("`regpkey` executing.\n"); + + auto index{_table.get_index<"index1"_n>()}; ///< `index` represents `_table` organized by `index1`. + auto iter {index.find(primary_key.value) }; ///< Note: the type returned by `index.find` is different than the type returned by `_table.find`. + + if (iter == _table.get_index<"index1"_n>().end()) { + eosio::print_f("`_primary_key`: % not found; registering.\n", primary_key.to_string()); + _table.emplace(_self, [&](auto& row) { + row._primary_key = primary_key; ++ if (secondary_key) { ++ row._secondary_key = secondary_key; ++ } ++ else { + row._secondary_key = "nothin"_n; ++ } + }); + } + else { + eosio::print_f("`_primary_key`: % found; not registering.\n", primary_key.to_string()); + } + + eosio::print_f("`regpkey` finished executing.\n"); +} +``` + +**binary_extension_contract.abi** +```diff +{ + "name": "regpkey", + "base": "", + "fields": [ + { + "name": "primary_key", + "type": "name" ++ }, ++ { ++ "name": "secondary_key", ++ "type": "name" + } + ] +} +``` + +```diff +{ + "name": "structure", + "base": "", + "fields": [ + { + "name": "_primary_key", + "type": "name" + }, + { + "name": "_secondary_key", + "type": "name" ++ }, ++ { ++ "name": "_non_binary_extension_key", ++ "type": "name" + } + ] +} +``` + +Next, upgrade the contract and try to read from table and write to table the original way: + +``` +~/binary_extension_contract $ eosio-cpp binary_extension_contract.cpp -o binary_extension_contract.wasm +``` + +``` +~/binary_extension_contract $ cleos set contract eosio ./ +``` + +``` +Reading WASM from /Users/john.debord/binary_extension_contract/binary_extension_contract.wasm... +Publishing contract... +executed transaction: b8ea485842fa5645e61d35edd97e78858e062409efcd0a4099d69385d9bc6b3e 4408 bytes 664 us +# eosio <= eosio::setcode "0000000000ea30550000a2660061736d01000000018f011760000060017f0060027f7f0060037f7f7f017f6000017e60067... +# eosio <= eosio::setabi "0000000000ea305583030e656f73696f3a3a6162692f312e310006076d6f646966797000020b7072696d6172795f6b65790... +warning: transaction executed locally, but may not be confirmed by the network yet +``` + +``` +~/binary_extension_contract $ cleos push action eosio printbyp '{"primary_key":"eosio.name"}' -p eosio +``` + +``` +Error 3050003: eosio_assert_message assertion failure +Error Details: +assertion failure with message: read +``` + +Whoops, you aren't able to read the data you've previously written to table. + +``` +~/binary_extension_contract $ cleos push action eosio regpkey '{"primary_key":"eosio.name2"}' -p eosio +``` + +``` +Error 3015014: Pack data exception +Error Details: +Missing field 'secondary_key' in input object while processing struct 'regpkey' +``` + +Whoops, you aren't able to write to table the original way with the upgraded action either. + +
Ok, back up and wrap the new field and the new action parameter in an `eosio::binary_extension` type: + +**binary_extension_contract.hpp** + +```diff ++[[eosio::action]] void regpkey (eosio::name primary_key. eosio::binary_extension secondary_key); +-[[eosio::action]] void regpkey (eosio::name primary_key, eosio::name secondary_key); +``` + +```diff +struct [[eosio::table]] structure { + eosio::name _primary_key; + eosio::name _secondary_key; ++ eosio::binary_extension _binary_extension_key; +- eosio::name _non_binary_extension_key; + + uint64_t primary_key() const { return _primary_key.value; } + uint64_t secondary_key() const { return _secondary_key.value; } +}; +``` + +**binary_extension_contract.cpp** + +```diff ++[[eosio::action]] void binary_extension_contract::regpkey(name primary_key, binary_extension secondary_key) { +-[[eosio::action]] void binary_extension_contract::regpkey(name primary_key, name secondary_key) { + eosio::print_f("`regpkey` executing.\n"); + + auto index{_table.get_index<"index1"_n>()}; ///< `index` represents `_table` organized by `index1`. + auto iter {index.find(primary_key.value) }; ///< Note: the type returned by `index.find` is different than the type returned by `_table.find`. + + if (iter == _table.get_index<"index1"_n>().end()) { + eosio::print_f("`_primary_key`: % not found; registering.\n", primary_key.to_string()); + _table.emplace(_self, [&](auto& row) { + row._primary_key = primary_key; + if (secondary_key) { ++ row._secondary_key = secondary_key.value(); +- row._secondary_key = secondary_key; + } + else { + row._secondary_key = "nothin"_n; + } + }); + } + else { + eosio::print_f("`_primary_key`: % found; not registering.\n", primary_key.to_string()); + } + + eosio::print_f("`regpkey` finished executing.\n"); +} +``` + +**binary_extension_contract.abi** +```diff +{ + "name": "regpkey", + "base": "", + "fields": [ + { + "name": "primary_key", + "type": "name" + }, + { + "name": "secondary_key", ++ "type": "name$" +- "type": "name" + } + ] +} +``` + +```diff +{ + "name": "structure", + "base": "", + "fields": [ + { + "name": "_primary_key", + "type": "name" + }, + { + "name": "_secondary_key", + "type": "name" + }, + { ++ "name": "_binary_extension_key", ++ "type": "name$" +- "name": "_non_binary_extension_key", +- "type": "name" + } + ] +} +``` + +Note the `$` after the types now; this indicates that this type is an `eosio::binary_extension` type field. +```diff +{ + "name": "secondary_key", ++ "type": "name$" +- "type": "name" +} +``` + +```diff +{ + "name": "_binary_extension_key", ++ "type": "name$" +- "type": "name" +} +``` + +Now, upgrade the contract again and try to read/write from/to table: + +``` +~/binary_extension_contract $ cleos set contract eosio ./ +``` + +``` +Reading WASM from /Users/john.debord/binary_extension_contract/binary_extension_contract.wasm... +Publishing contract... +executed transaction: 497584d4e43ec114dbef83c134570492893f49eacb555d0cd47d08ea4a3a72f7 4696 bytes 648 us +# eosio <= eosio::setcode "0000000000ea30550000cb6a0061736d01000000018f011760000060017f0060027f7f0060037f7f7f017f6000017e60017... +# eosio <= eosio::setabi "0000000000ea305581030e656f73696f3a3a6162692f312e310006076d6f646966797000020b7072696d6172795f6b65790... +warning: transaction executed locally, but may not be confirmed by the network yet +``` + +``` +~/binary_extension_contract $ cleos push action eosio printbyp '{"primary_key":"eosio.name"}' -p eosio +``` + +``` +executed transaction: 6108f3206e1824fe3a1fdcbc2fe733f38dc07ae3d411a1ccf777ecef56ddec97 104 bytes 224 us +# eosio <= eosio::printbyp {"primary_key":"eosio.name"} +[(eosio,printbyp)->eosio]: CONSOLE OUTPUT BEGIN ===================== +`printbyp` executing. +`_primary_key`: eosio.name found; printing. +{eosio.name, nothin} +`printbyp` finished executing. +[(eosio,printbyp)->eosio]: CONSOLE OUTPUT END ===================== +warning: transaction executed locally, but may not be confirmed by the network yet +``` + +``` +~/binary_extension_contract $ cleos push action eosio regpkey '{"primary_key":"eosio.name2"}' -p eosio +``` + +``` +executed transaction: 75a135d1279a9c967078b0ebe337dc0cd58e1ccd07e370a899d9769391509afc 104 bytes 227 us +# eosio <= eosio::regpkey {"primary_key":"eosio.name2"} +[(eosio,regpkey)->eosio]: CONSOLE OUTPUT BEGIN ===================== +`regpkey` executing. +`_primary_key`: eosio.name2 not found; registering. +`regpkey` finished executing. +[(eosio,regpkey)->eosio]: CONSOLE OUTPUT END ===================== +warning: transaction executed locally, but may not be confirmed by the network yet +``` + +Nice! The smart contract is now backwards compatible for the future use of its tables and/or actions. + +
+ +Just keep these simple rules in mind when upgrading a smart contract. +If you are adding a new field to a struct currently in use by a `eosio::multi_index` be **SURE** to: +- add the field at the end of the struct, +- and wrap the type using an `eosio::binary_extension` type. + +# There are a few restrictions you have to be aware of, and they are outlined below + +Binary extensions only operate correctly in certain locations. + +* ok: a non-embedded struct stored in a row may have binary extensions at its end +* ok: an action may use binary extensions to add additional arguments to its end +* ok: a struct with binary extensions may be used inside another struct, but only if the inner struct is the last field of the outer struct and the outer struct is allowed to contain binary extensions +* not ok: a struct with binary extensions may not be used inside an array +* not ok: a struct with binary extensions may not be used as a base of another struct +* not ok: fields with types which don't end in `$` following fields with types which do +* not ok: `$` used anywhere except in struct field types + +## ABI version string + +`eosio::abi/1.1` + +## ABI Text format + +Types may have a `$` suffix. During binary-to-json conversion, fields with a `$` type don't error out when end-of-data has been reached; instead they're omitted. During json-to-binary conversion, missing fields don't error out as long as no non-missing fields follow in the ABI. This omits the bytes from the output stream. + +e.g. + +```json + { + "name": "my_table_struct", + "base": "", + "fields": [ + { + "name": "required_field_1", + "type": "string" + }, + { + "name": "required_field_2", + "type": "float32[]" + }, + { + "name": "optional_field_3", + "type": "float32[]$" + }, + { + "name": "optional_field_4", + "type": "string$" + }, + ] + }, +``` + +## JSON representation + +Missing fields aren't included; null isn't used. E.g. all of these are valid JSON representations of `my_table_struct`: + +```json +{ + "required_field_1": "foo", + "required_field_2": [1,2,3,4] +} +``` + +```json +{ + "required_field_1": "foo", + "required_field_2": [1,2,3,4], + "optional_field_3": [5,6,7,8] +} +``` + +```json +{ + "required_field_1": "foo", + "required_field_2": [1,2,3,4], + "optional_field_3": [5,6,7,8], + "optional_field_4": "bar" +} +``` + +## ABI Binary format + +`$` can be included in type strings. No other changes. diff --git a/docs/09_tutorials/02_abi-variants.md b/docs/09_tutorials/02_abi-variants.md new file mode 100644 index 0000000000..9e99f1f912 --- /dev/null +++ b/docs/09_tutorials/02_abi-variants.md @@ -0,0 +1,157 @@ +## ABI variants + +ABI variants give the flexibility of using more than one type for a defined variable or data member. +In EOSIO, the variants make use of the standard template library `variant` which was introduced in C++ 17. An instance of `std::variant` at any given time either holds a value of one of its alternative types, or in the case of error - no value. Because of this trait, variants can be used to build the multi index table structure with flexibility. Used in conjunction with ABI extensions, it allows for modification of the structure of an exiting multi index table, a.k.a. table. + +### Use variant when building the multi index table the first time + +To define a `variant` for your table structure one example is shown below + +```cpp + std::variant variant_field; +``` + +This defines `variant` which can hold three different types, one at a time though. +So the contract interface could look like this: + +```diff +#include +using namespace eosio; + +class [[eosio::contract]] multi_index_example : public contract { + public: + using contract::contract; + multi_index_example( name receiver, name code, datastream ds ) + : contract(receiver, code, ds), testtab(receiver, receiver.value) + { } + + struct [[eosio::table]] test_table { + name test_primary; + name secondary; + uint64_t datum; ++ std::variant variant_field; + + uint64_t primary_key()const { return test_primary.value; } + uint64_t by_secondary()const { return secondary.value; } ++ std::variant get_variant_field()const { ++ return std::visit( ++ [](auto&& arg) -> std::variant { ++ return arg; ++ }, ++ variant_field); + } + }; + + typedef eosio::multi_index<"testtaba"_n, test_table, eosio::indexed_by<"secid"_n, eosio::const_mem_fun>> test_tables; + + test_tables testtab; + + [[eosio::action]] void set(name user); + [[eosio::action]] void print( name user ); + + using set_action = action_wrapper<"set"_n, &multi_index_example::set>; + using print_action = action_wrapper<"print"_n, &multi_index_example::print>; +}; +``` + +Notice above the declaration of the `variant_field` data memember and also the declaration and inline implementation for the `get_variant_field()` accessor for this data member. + +In the future, this allows you the flexibility to store in the `variant_field` three different types of data `int8_t`, `int16_t`, and `int32_t`, and also allows you to add more types in the list of supported types for this field. One important thing to keep in mind is that you can only append at the end of the supported types, you can not modify the existing supported types order nor drop one of them. That means if you want in the next version of your contract to add also type `int32_t` to the supported list types for this field, your contract implementation could look like this: + +```diff +#include +using namespace eosio; + +class [[eosio::contract]] multi_index_example : public contract { + public: + using contract::contract; + multi_index_example( name receiver, name code, datastream ds ) + : contract(receiver, code, ds), testtab(receiver, receiver.value) + { } + + struct [[eosio::table]] test_table { + name test_primary; + name secondary; + uint64_t datum; ++ std::variant variant_field; + + uint64_t primary_key()const { return test_primary.value; } + uint64_t by_secondary()const { return secondary.value; } ++ std::variant get_variant_field()const { ++ return std::visit( ++ [](auto&& arg) -> std::variant { ++ return arg; ++ }, ++ variant_field); + } + }; + + typedef eosio::multi_index<"testtaba"_n, test_table, eosio::indexed_by<"secid"_n, eosio::const_mem_fun>> test_tables; + + test_tables testtab; + + [[eosio::action]] void set(name user); + [[eosio::action]] void print( name user ); + + using set_action = action_wrapper<"set"_n, &multi_index_example::set>; + using print_action = action_wrapper<"print"_n, &multi_index_example::print>; +}; +``` + +Now you can deploy the contract and it will be backwards compatible with the previous existing multi index table. + +### 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. + +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: + +```cpp + eosio::binary_extension> binary_extension_variant_key; +``` + +Notice, the use of the `eosio::binary_extension` template which wraps the `std::variant` template parameterized with the types you want to support for the new data field. The full contract implementation can look like this: + +```diff +#include +#include +using namespace eosio; + +class [[eosio::contract]] multi_index_example : public contract { + public: + using contract::contract; + multi_index_example( name receiver, name code, datastream ds ) + : contract(receiver, code, ds), testtab(receiver, receiver.value) + { } + + struct [[eosio::table]] test_table { + name test_primary; + name secondary; + uint64_t datum; ++ eosio::binary_extension> binary_extension_variant_key; + + uint64_t primary_key()const { return test_primary.value; } + uint64_t by_secondary()const { return secondary.value; } ++ eosio::binary_extension> get_binary_extension_variant_field()const { ++ return binary_extension_variant_key; ++ } + }; + + typedef eosio::multi_index<"testtaba"_n, test_table, eosio::indexed_by<"secid"_n, eosio::const_mem_fun>> test_tables; + + test_tables testtab; + + [[eosio::action]] void set(name user); + [[eosio::action]] void print( name user ); + + using set_action = action_wrapper<"set"_n, &multi_index_example::set>; + using print_action = action_wrapper<"print"_n, &multi_index_example::print>; +}; +``` + +[[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! + +[[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 diff --git a/docs/guides/cmake.md b/docs/guides/cmake.md deleted file mode 100644 index 0ab8518474..0000000000 --- a/docs/guides/cmake.md +++ /dev/null @@ -1,39 +0,0 @@ -# CMake - -## CMake Configuration -To compile an EOSIO smart contract with CMake you'll need a CMake file. The new `eosio-init` tool can be used to generate the directory structure stub .hpp/.cpp files and subsequent cmake files. Or the template `CMakeLists.txt` in the examples folder is a good boilerplate for manual usage. - -For example: - -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 ) -``` - - -In `test.cpp`: - -``` -#include -using namespace eosio; - -CONTRACT test : public eosio::contract { -public: - using contract::contract; - - ACTION testact( name test ) { - } -}; - -EOSIO_DISPATCH( test, (testact) ) -``` - -## CMake Macros -- `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. -- (new for native tester) `add_native_library` and `add_native_executable` CMake macros have been added (these are a drop in replacement for add_library and add_executable). diff --git a/docs/guides/first-smart-contract.md b/docs/guides/first-smart-contract.md deleted file mode 100644 index 8be919d63e..0000000000 --- a/docs/guides/first-smart-contract.md +++ /dev/null @@ -1,32 +0,0 @@ -### Building your first smart contract -```c++ -#include -#include - -class [[eosio::contract]] hello : public eosio::contract { - public: - using eosio::contract::contract; - - [[eosio::action]] - void hi(eosio::name nm) { - eosio::print_f("Hello, %\n", nm); - } -}; -``` - -- Navigate to the hello folder in examples (./examples/hello). -- You should then see the hello.cpp file -- Now run the compiler -```sh -$ eosio-cpp -abigen hello.cpp -o hello.wasm -``` -- Or with CMake -```sh -$ mkdir build -$ cd build -$ cmake .. -$ make -``` -This will generate two files: -* The compiled binary wasm (hello.wasm) -* The generated ABI file (hello.abi) diff --git a/docs/guides/generator-attributes.md b/docs/guides/generator-attributes.md deleted file mode 100644 index 7b0bdca566..0000000000 --- a/docs/guides/generator-attributes.md +++ /dev/null @@ -1,98 +0,0 @@ -## ABI/Code generator attributes -Unlike the old ABI generator tool, the new 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. -Example (four ways to declare an action for ABI generation): -```c++ -// this is the C++11 and greater style attribute -[[eosio::action]] -void testa( name n ) { - // do something -} - -// this is the GNU style attribute, this can be used in C code and prior to C++ 11 -__attribute__((eosio_action)) -void testa( name n ){ - // do something -} - -struct [[eosio::action]] testa { - name n; - EOSLIB_SERIALIZE( testa, (n) ) -}; - -struct __attribute__((eosio_action)) testa { - name n; - EOSLIB_SERIALIZE( testa, (n) ) -}; -``` -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("")]]``` - -#### [[eosio::table]] -Example (two ways to declare a table for ABI generation): -``` -struct [[eosio::table]] testtable { - uint64_t owner; - /* all other fields */ -}; - -struct __attribute__((eosio_table)) testtable { - uint64_t owner; - /* all other fields */ -}; - -typedef eosio::multi_index<"tablename"_n, testtable> testtable_t; -``` -If you don't want to use the multi-index you can explicitly specify the name in the attribute ```c++ [[eosio::table("")]]```. - -#### [[eosio::contract("\")]] -``` -class [[eosio::contract("")]] test_contract : public eosio::contract { -}; -``` -This will mark this `class` as being an `EOSIO` contract, this allows for namespacing of contracts, i.e. you can include headers like `eosio::token` and not have `eosio::token`'s actions/tables wind up in you ABI or generated dispatcher. - -#### [[eosio::on_notify("\::\")]] -``` -[[eosio::on_notify("eosio.token::transfer")]] -void on_token_transfer(name from, name to, asset quantity, std::string memo) { - do something on transfer from eosio.token... -} - -[[eosio::on_notify("*::transfer")]] -void on_any_transfer(name from, name to, asset quantity, std::string memo) { - do something on transfer from any account... -} -``` - -#### [[eosio::wasm_entry]] -``` -[[eosio::wasm_entry]] -void some_function(...) { - do something... -} -``` - -This will mark an arbitrary function as an entry point, which will then wrap the function with global constructors (ctors) and global destructors (dtors). This will allow for the eosio.cdt toolchain to produce WASM binaries for other ecosystems. - -#### [[eosio::wasm_import]] -``` -extern "C" { - __attribute__((eosio_wasm_import)) - void some_intrinsic(...); -} -``` - -This will mark a function declaration as being a WebAssembly import. This allows for other compilation modes to specify which functions are import only (i.e. do not link) without having to maintain a secondary file with duplicate declarations. - -### Fixing an ABI or Writing an ABI Manually -- 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 generators 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. - -### 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. -- The Ricardian contracts should be housed in a file with the name .contracts.md and the clauses should be in a file named .clauses.md. - - For each Ricardian contract the header `

ActionName

` should be used, as this directs the ABI generator to attach this Ricardian contract to the specified action. - - For each Ricardian clause the header `

ClauseID

` should be used, as this directs the ABI generator to the clause id and the subsequent body. - - The option `-R` has been added to eosio-cpp and eosio-abigen to add "resource" paths to search from, so you can place these files in any directory structure you like and use `-R` in the same vein as `-I` for include paths. - - To see these in use please see ./examples/hello/hello.contracts.md and ./examples/hello/hello.clauses.md. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000000..3dc341ead1 --- /dev/null +++ b/docs/index.md @@ -0,0 +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 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 braking 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). + +## Contributing + +[Contributing Guide](../CONTRIBUTING.md) + +[Code of Conduct](../CONTRIBUTING.md#conduct) + +## License + +[MIT](../LICENSE) + +## Important + +See LICENSE for copyright and license terms. Block.one makes its contribution on a voluntary basis as a member of the EOSIO community and is not responsible for ensuring the overall performance of the software or any related applications. We make no representation, warranty, guarantee or undertaking in respect of the software or any related documentation, whether expressed or implied, including but not limited to the warranties or merchantability, fitness for a particular purpose and noninfringement. In no event shall we be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or documentation or the use or other dealings in the software or documentation. Any test results or performance figures are indicative and will not reflect performance under all conditions. Any reference to any third party or third-party product, service or other resource is not an endorsement or recommendation by Block.one. We are not responsible, and disclaim any and all responsibility and liability, for your use of or reliance on any of these resources. Third-party resources may be updated, changed or terminated at any time, so the information here may be out of date or inaccurate. diff --git a/examples/hello/README.txt b/examples/hello/README.txt index b200d54092..069019e07e 100644 --- a/examples/hello/README.txt +++ b/examples/hello/README.txt @@ -1,12 +1,22 @@ --- hello Project --- - - How to Build - - - cd to 'build' directory + -- How to Build with CMake and Make -- + - mkdir build + - cd into the 'build' directory - run the command 'cmake ..' - run the command 'make' - After build - - The built smart contract is under the 'hello' directory in the 'build' directory - - You can then do a 'set contract' action with 'cleos' and point in to the './build/hello' directory + - You can then do a 'set contract' action with 'cleos' and point to the './build/hello' directory - - Additions to CMake should be done to the CMakeLists.txt in the './src' directory and not in the top level CMakeLists.txt \ No newline at end of file +- 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/' + + - 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/hello/include/hello.hpp b/examples/hello/include/hello.hpp index a8abfc60b8..3019d18a30 100644 --- a/examples/hello/include/hello.hpp +++ b/examples/hello/include/hello.hpp @@ -1,12 +1,14 @@ #include using namespace eosio; -CONTRACT hello : public contract { +class [[eosio::contract]] hello : public contract { public: using contract::contract; - ACTION hi( name nm ); - ACTION check( name nm ); + [[eosio::action]] + void hi( name nm ); + [[eosio::action]] + void check( name nm ); using hi_action = action_wrapper<"hi"_n, &hello::hi>; using check_action = action_wrapper<"check"_n, &hello::check>; diff --git a/examples/hello/src/hello.cpp b/examples/hello/src/hello.cpp index 7b2c68387d..8c858132d7 100644 --- a/examples/hello/src/hello.cpp +++ b/examples/hello/src/hello.cpp @@ -1,9 +1,11 @@ #include -ACTION hello::hi( name nm ) { +[[eosio::action]] +void hello::hi( name nm ) { print_f("Name : %\n", nm); } -ACTION hello::check( name nm ) { +[[eosio::action]] +void hello::check( name nm ) { print_f("Name : %\n", nm); eosio::check(nm == "hello"_n, "check name not equal to `hello`"); } diff --git a/examples/multi_index_example/README.txt b/examples/multi_index_example/README.txt index 844225edaa..2f9e854dc8 100644 --- a/examples/multi_index_example/README.txt +++ b/examples/multi_index_example/README.txt @@ -1,12 +1,22 @@ --- multi_index_example Project --- - - How to Build - - - cd to 'build' directory + -- How to Build with CMake and Make -- + - mkdir build + - cd into the 'build' directory - run the command 'cmake ..' - run the command 'make' - After build - - The built smart contract is under the 'multi_index_example' directory in the 'build' directory - - You can then do a 'set contract' action with 'cleos' and point in to the './build/multi_index_example' directory + - You can then do a 'set contract' action with 'cleos' and point to the './build/multi_index_example' directory - - Additions to CMake should be done to the CMakeLists.txt in the './src' directory and not in the top level CMakeLists.txt \ No newline at end of file + - 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/' + + - 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/multi_index_example/include/multi_index_example.hpp b/examples/multi_index_example/include/multi_index_example.hpp index ef4905ffea..bd08eb9a7e 100644 --- a/examples/multi_index_example/include/multi_index_example.hpp +++ b/examples/multi_index_example/include/multi_index_example.hpp @@ -1,18 +1,13 @@ #include using namespace eosio; -CONTRACT multi_index_example : public contract { +class [[eosio::contract]] multi_index_example : public contract { public: using contract::contract; multi_index_example( name receiver, name code, datastream ds ) : contract(receiver, code, ds), testtab(receiver, receiver.value) {} - ACTION set(name user); - ACTION print( name user ); - ACTION bysec( name secid ); - ACTION mod( name user, uint32_t n ); - - TABLE test_table { + struct [[eosio::table]] test_table { name test_primary; name secondary; uint64_t datum; @@ -22,9 +17,22 @@ CONTRACT multi_index_example : public contract { typedef eosio::multi_index<"testtaba"_n, test_table, eosio::indexed_by<"secid"_n, eosio::const_mem_fun>> test_tables; + test_tables testtab; + + [[eosio::action]] + void set(name user); + [[eosio::action]] + void print( name user ); + [[eosio::action]] + void bysec( name secid ); + [[eosio::action]] + void mod( name user, uint32_t n ); + [[eosio::action]] + void del( name user ); + using set_action = action_wrapper<"set"_n, &multi_index_example::set>; using print_action = action_wrapper<"print"_n, &multi_index_example::print>; using bysec_action = action_wrapper<"bysec"_n, &multi_index_example::bysec>; using mod_action = action_wrapper<"mod"_n, &multi_index_example::mod>; - test_tables testtab; + using del_action = action_wrapper<"del"_n, &multi_index_example::del>; }; diff --git a/examples/multi_index_example/src/multi_index_example.cpp b/examples/multi_index_example/src/multi_index_example.cpp index c784f406ef..0072c3566f 100644 --- a/examples/multi_index_example/src/multi_index_example.cpp +++ b/examples/multi_index_example/src/multi_index_example.cpp @@ -1,5 +1,6 @@ #include -ACTION multi_index_example::set( name user ) { +[[eosio::action]] +void multi_index_example::set( name user ) { auto itr = testtab.find(user.value); if ( itr == testtab.end() ) { testtab.emplace( _self, [&]( auto& u ) { @@ -10,25 +11,41 @@ ACTION multi_index_example::set( name user ) { } } -ACTION multi_index_example::print( name user ) { +[[eosio::action]] +void multi_index_example::print( name user ) { auto itr = testtab.find(user.value); - check( itr != testtab.end(), "test table not set" ); + check( itr != testtab.end(), "user does not exist in table" ); eosio::print_f("Test Table : {%, %, %}\n", itr->test_primary, itr->secondary, itr->datum); } -ACTION multi_index_example::bysec( name secid ) { +[[eosio::action]] +void multi_index_example::bysec( name secid ) { auto idx = testtab.get_index<"secid"_n>(); for ( auto itr = idx.begin(); itr != idx.end(); itr++ ) { print( itr->test_primary ); } } - -ACTION multi_index_example::mod( name user, uint32_t n ) { +[[eosio::action]] +void multi_index_example::mod( name user, uint32_t n ) { auto itr = testtab.find(user.value); - check( itr != testtab.end(), "test table not set" ); + check( itr != testtab.end(), "user does not exist in table" ); testtab.modify( itr, _self, [&]( auto& row ) { row.secondary = user; row.datum = n; }); } + +[[eosio::action]] +void multi_index_example::del( name user ) { + // check if the user already exists + auto itr = testtab.find(user.value); + if ( itr == testtab.end() ) { + printf("user does not exist in table, nothing to delete" ); + return; + } + + // if we got so far it means user exists so we can delete it using + // the iterator found based on its primary key + testtab.erase( itr ); +} diff --git a/examples/send_inline/README.txt b/examples/send_inline/README.txt index cf339a7835..9f2183e211 100644 --- a/examples/send_inline/README.txt +++ b/examples/send_inline/README.txt @@ -1,12 +1,22 @@ --- send_inline Project --- - - How to Build - - - cd to 'build' directory + -- How to Build with CMake and Make -- + - mkdir build + - cd into the 'build' directory - run the command 'cmake ..' - run the command 'make' - After build - - The built smart contract is under the 'send_inline' directory in the 'build' directory - - You can then do a 'set contract' action with 'cleos' and point in to the './build/send_inline' directory + - You can then do a 'set contract' action with 'cleos' and point to the './build/send_inline' directory - - Additions to CMake should be done to the CMakeLists.txt in the './src' directory and not in the top level CMakeLists.txt \ No newline at end of file + - 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/' + + - 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/send_inline/include/send_inline.hpp b/examples/send_inline/include/send_inline.hpp index 7efde6e619..af99668590 100644 --- a/examples/send_inline/include/send_inline.hpp +++ b/examples/send_inline/include/send_inline.hpp @@ -1,11 +1,12 @@ #include using namespace eosio; -CONTRACT send_inline : public contract { +class [[eosio::contract]] send_inline : public contract { public: using contract::contract; - ACTION test( name user, name inline_code ); + [[eosio::action]] + void test( name user, name inline_code ); using test_action = action_wrapper<"test"_n, &send_inline::test>; }; diff --git a/examples/send_inline/src/send_inline.cpp b/examples/send_inline/src/send_inline.cpp index 058c613221..96647e0914 100644 --- a/examples/send_inline/src/send_inline.cpp +++ b/examples/send_inline/src/send_inline.cpp @@ -1,6 +1,7 @@ #include #include -ACTION send_inline::test( name user, name inline_code ) { +[[eosio::action]] +void send_inline::test( name user, name inline_code ) { print_f( "Hello % from send_inline", user ); // constructor takes two arguments (the code the contract is deployed on and the set of permissions) hello::hi_action hi(inline_code, {_self, "active"_n}); diff --git a/examples/singleton_example/CMakeLists.txt b/examples/singleton_example/CMakeLists.txt new file mode 100644 index 0000000000..cc78459a7b --- /dev/null +++ b/examples/singleton_example/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( + singleton_example_project + SOURCE_DIR ${CMAKE_SOURCE_DIR}/src + BINARY_DIR ${CMAKE_BINARY_DIR}/singleton_example + 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/singleton_example/README.txt b/examples/singleton_example/README.txt new file mode 100644 index 0000000000..8d948e46a0 --- /dev/null +++ b/examples/singleton_example/README.txt @@ -0,0 +1,20 @@ +--- singleton_example 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 'singleton_example' directory in the 'build' directory + - You can then do a 'set contract' action with 'cleos' and point to the './build/singleton_example' 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/singleton_example.cpp -o singleton_example.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/singleton_example/include/singleton_example.hpp b/examples/singleton_example/include/singleton_example.hpp new file mode 100644 index 0000000000..8b67639c62 --- /dev/null +++ b/examples/singleton_example/include/singleton_example.hpp @@ -0,0 +1,29 @@ +#include +#include +using namespace eosio; + +class [[eosio::contract]] singleton_example : public contract { + public: + using contract::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( ); + + struct [[eosio::table]] testtable { + name primary_value; + uint64_t secondary_value; + uint64_t primary_key() const { return primary_value.value; } + } tt; + + using singleton_type = eosio::singleton<"testtable"_n, testtable>; + singleton_type singleton_instance; + + using set_action = action_wrapper<"set"_n, &singleton_example::set>; + using get_action = action_wrapper<"get"_n, &singleton_example::get>; +}; diff --git a/examples/singleton_example/ricardian/singleton_example.contracts.md b/examples/singleton_example/ricardian/singleton_example.contracts.md new file mode 100644 index 0000000000..5c7d6556a7 --- /dev/null +++ b/examples/singleton_example/ricardian/singleton_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/singleton_example/src/CMakeLists.txt b/examples/singleton_example/src/CMakeLists.txt new file mode 100644 index 0000000000..8e5dbe960f --- /dev/null +++ b/examples/singleton_example/src/CMakeLists.txt @@ -0,0 +1,8 @@ +project(singleton_example) + +set(EOSIO_WASM_OLD_BEHAVIOR "Off") +find_package(eosio.cdt) + +add_contract( singleton_example singleton_example singleton_example.cpp ) +target_include_directories( singleton_example PUBLIC ${CMAKE_SOURCE_DIR}/../include ) +target_ricardian_directory( singleton_example ${CMAKE_SOURCE_DIR}/../ricardian ) \ No newline at end of file diff --git a/examples/singleton_example/src/singleton_example.cpp b/examples/singleton_example/src/singleton_example.cpp new file mode 100644 index 0000000000..4e896bb25a --- /dev/null +++ b/examples/singleton_example/src/singleton_example.cpp @@ -0,0 +1,26 @@ +#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(); + entry_stored.primary_value = user; + entry_stored.secondary_value = value; + singleton_instance.set(entry_stored, user); +} + +[[eosio::action]] +void singleton_example::get( ) { + if (singleton_instance.exists()) + eosio::print( + "Value stored for: ", + name{singleton_instance.get().primary_value.value}, + " is ", + singleton_instance.get().secondary_value, + "\n"); + else + eosio::print("Singleton is empty\n"); +} From 7df299c8ccc053de361a64ff1c6e5d453502d6ca Mon Sep 17 00:00:00 2001 From: ovi Date: Thu, 21 Nov 2019 13:37:18 +0200 Subject: [PATCH 85/99] Fix headers --- docs/02_installation.md | 23 +++++---- docs/03_command-reference/eosio-abidiff.md | 4 +- docs/03_command-reference/eosio-abigen.md | 6 ++- docs/03_command-reference/eosio-cc.md | 4 +- docs/03_command-reference/eosio-cpp.md | 4 +- docs/03_command-reference/eosio-init.md | 4 +- docs/03_command-reference/eosio-ld.md | 4 +- docs/04_upgrading/1.2-to-1.3.md | 50 ++++++++++--------- docs/04_upgrading/1.5-to-1.6.md | 18 ++++--- .../05_best-practices/03_resource-planning.md | 4 +- .../04_data-design-and-migration.md | 4 +- .../05_securing_your_contract.md | 5 +- docs/05_best-practices/07_error_handling.md | 4 +- ...abi-code-generator-attributes-explained.md | 17 ++++--- ...02_manually_write_an_ABI_file_explained.md | 7 ++- .../09_deferred_transactions.md | 4 +- .../10_native-tester-compilation.md | 11 ++-- .../11_debugging_a_smart_contract.md | 16 +++--- ...ry-extension.md => 12_binary-extension.md} | 4 +- .../01_compile-a-contract-via-cli.md | 6 ++- .../01_compile/02_how-to-configure-cmake.md | 10 ++-- .../03_compiling-contracts-with-cmake.md | 6 ++- .../how-to-define-a-primary-index.md | 4 +- .../how-to-define-a-secondary-index.md | 6 ++- .../how-to-define-a-singleton.md | 4 +- ...to-delete-data-from-a-multi-index-table.md | 6 ++- ...to-insert-data-into-a-multi-index-table.md | 6 ++- .../how-to-instantiate-a-multi-index-table.md | 4 +- ...ti_index-table-based-on-secondary-index.md | 6 ++- ...terate-and-retrieve-a-multi_index-table.md | 6 ++- ...w-to-modify-data-in-a-multi-index-table.md | 6 ++- ...4_how_to_create_and_use_action_wrappers.md | 4 +- ...to_restrict_access_to_an_action_by_user.md | 6 ++- docs/08_troubleshooting.md | 30 +++++------ docs/09_tutorials/01_binary-extension.md | 4 +- docs/09_tutorials/02_abi-variants.md | 10 ++-- docs/index.md | 1 + 37 files changed, 198 insertions(+), 120 deletions(-) rename docs/05_best-practices/{binary-extension.md => 12_binary-extension.md} (99%) diff --git a/docs/02_installation.md b/docs/02_installation.md index 193510a018..680d8addbe 100644 --- a/docs/02_installation.md +++ b/docs/02_installation.md @@ -1,42 +1,45 @@ -## Binary Releases +--- +content_title: Binary Releases +--- + EOSIO.CDT currently supports Mac OS X brew, Linux x86_64 Debian packages, and Linux x86_64 RPM packages. **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 +## Mac OS X Brew Install ```sh $ brew tap eosio/eosio.cdt $ brew install eosio.cdt ``` -### Mac OS X Brew Uninstall +## Mac OS X Brew Uninstall ```sh $ brew remove eosio.cdt ``` -### Debian Package Install +## 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 ``` -### Debian Package Uninstall +## Debian Package Uninstall ```sh $ sudo apt remove eosio.cdt ``` -### RPM Package Install +## 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 ``` -### RPM Package Uninstall +## RPM Package Uninstall ```sh $ sudo yum remove eosio.cdt ``` -## Guided Installation or Building from Scratch +# Guided Installation or Building from Scratch ```sh $ git clone --recursive https://github.com/eosio/eosio.cdt $ cd eosio.cdt @@ -53,7 +56,7 @@ Or you can install globally by running this command sudo make install ``` -### Uninstall after manual installation +## Uninstall after manual installation ```sh $ sudo rm -fr /usr/local/eosio.cdt @@ -62,7 +65,7 @@ $ sudo rm /usr/local/bin/eosio-* ``` -## Installed Tools +# Installed Tools --- * eosio-cpp * eosio-cc diff --git a/docs/03_command-reference/eosio-abidiff.md b/docs/03_command-reference/eosio-abidiff.md index adba0081f1..29dd8a34be 100644 --- a/docs/03_command-reference/eosio-abidiff.md +++ b/docs/03_command-reference/eosio-abidiff.md @@ -1,4 +1,6 @@ -## eosio-abidiff tool +--- +content_title: eosio-abidiff tool +--- The eosio-abidiff tool is used to diff two ABI files to flag and output differences. To report differences with ```eosio-abidiff```, you only need to pass the two ABI file names as command line arguments. diff --git a/docs/03_command-reference/eosio-abigen.md b/docs/03_command-reference/eosio-abigen.md index a2a563f902..35ea5849ba 100644 --- a/docs/03_command-reference/eosio-abigen.md +++ b/docs/03_command-reference/eosio-abigen.md @@ -1,6 +1,8 @@ -## eosio-abigen tool +--- +content_title: eosio-abigen tool +--- -### This tool is deprecated, use `eosio-cpp` for generation of your ABIs +## This tool is deprecated, use `eosio-cpp` for generation of your ABIs To generate an ABI with ```eosio-abigen```, only requires that you give the main '.cpp' file to compile and the output filename `--output` and generating against the contract name `--contract`. diff --git a/docs/03_command-reference/eosio-cc.md b/docs/03_command-reference/eosio-cc.md index 003e129210..11b08050d3 100644 --- a/docs/03_command-reference/eosio-cc.md +++ b/docs/03_command-reference/eosio-cc.md @@ -1,4 +1,6 @@ -## eosio-cc tool +--- +content_title: eosio-cc tool +--- To manually compile the source code, use `eosio-cc` and `eosio-ld` as if it were __clang__ and __lld__. All the includes and options specific to EOSIO and CDT are baked in. diff --git a/docs/03_command-reference/eosio-cpp.md b/docs/03_command-reference/eosio-cpp.md index c4cdc74e09..27b9053abd 100644 --- a/docs/03_command-reference/eosio-cpp.md +++ b/docs/03_command-reference/eosio-cpp.md @@ -1,4 +1,6 @@ -## eosio-cpp tool +--- +content_title: eosio-cpp tool +--- To manually compile the source code, use `eosio-cpp` and `eosio-ld` as if it were __clang__ and __lld__. All the includes and options specific to EOSIO and CDT are baked in. diff --git a/docs/03_command-reference/eosio-init.md b/docs/03_command-reference/eosio-init.md index 0702b09585..fb6dd99039 100644 --- a/docs/03_command-reference/eosio-init.md +++ b/docs/03_command-reference/eosio-init.md @@ -1,4 +1,6 @@ -## eosio-init tool +--- +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. diff --git a/docs/03_command-reference/eosio-ld.md b/docs/03_command-reference/eosio-ld.md index 07bad1e2af..0e4e8f74ae 100644 --- a/docs/03_command-reference/eosio-ld.md +++ b/docs/03_command-reference/eosio-ld.md @@ -1,4 +1,6 @@ -## eosio-ld tool +--- +content_title: eosio-ld tool +--- The eosio-ld tool is a the custom web assembly linker for EOSIO platform smart contracts. diff --git a/docs/04_upgrading/1.2-to-1.3.md b/docs/04_upgrading/1.2-to-1.3.md index c5d254380c..3d5b66a0ef 100644 --- a/docs/04_upgrading/1.2-to-1.3.md +++ b/docs/04_upgrading/1.2-to-1.3.md @@ -1,6 +1,8 @@ -## Version 1.3 +--- +content_title: Version 1.3 +--- -### eosiolib C API +## eosiolib C API - Removed the following typedefs to `uint64_t`: - `account_name` - `permission_name` @@ -22,7 +24,7 @@ - `signature` -> `capi_signature` - Removed the non-existent intrinsics declarations `require_write_lock` and `require_read_lock`. -### eosiolib C++ API +## eosiolib C++ API - Removed eosiolib/vector.hpp: - Removed alias `eosio::vector` and typedef `bytes`. - Going forward contract writers should include `` from the STL and use `std::vector` instead of bytes. @@ -31,14 +33,14 @@ - Removed eosiolib/core_symbol.hpp. The contract writer should explicitly specify the symbol. - Added eosiolib/name.hpp. -#### eosiolib/types.hpp +### eosiolib/types.hpp - Moved the typedef `eosio::extensions_types` to eosiolib/transaction.hpp. - Removed comparison functions for `checksum` structs. - Removal of `eosio::char_to_symbol`, `eosio::string_to_name`, `eosio::name_suffix` functions - Removal of the `N` macro. The `""_n` operator or the `name` constructor should be used as a type safe replacement. Example: `N(foo)` -> `"foo"_n`, or `N(foo)` -> `name("foo")`. - Moved `eosio::name` struct definition and `""_n` operator to eosiolib/name.hpp. -#### eosiolib/name.hpp +### eosiolib/name.hpp - Removed implicit and explicit conversions to `uint64_t`. - Added `enum class` `eosio::name::raw` which is implicitly converted from an `eosio::name` (used for template non-type parameters). - Added `bool` conversion operator for conditionally testing if a name is empty. @@ -46,7 +48,7 @@ - Added `constexpr` methods `eosio::name::length` and `eosio::name::suffix`. - Added equivalence, inverted equivalence and less than operators to `eosio::name`. -#### eosiolib/symbol.hpp +### eosiolib/symbol.hpp - Removed `eosio::symbol_type` struct and replaced with `eosio::symbol` class. - Added struct `eosio::symbol_code`: - Added two `constexpr` constructors that take either a raw `uint64_t` or an `std::string_view`. @@ -65,41 +67,41 @@ - Added `constexpr` methods `get_symbol` and `get_contract`. - Made existing comparison operators `constexpr`. -#### eosiolib/asset.hpp +### eosiolib/asset.hpp - The main constructor now requires a `int64_t` (quantity) and `eosio::symbol` explicitly. - The default constructor no longer initializes the instance to a valid zero quantity asset with a symbol equivalent to "core symbol". Instead the default constructed `eosio::asset` is a bit representation of all zeros (which will cause `is_valid` to fail) so that check is bypassed to allow for `multi_index` and `datastream` to work. - Old contracts that use `eosio::asset()` should be changed to either use the core symbol of the specific chain they are targeting i.e. `eosio::asset(0, symbol(symbol_code("SYS"),4))`. To reduce writing `symbol(symbol_code("SYS"),4)` over and over, a `constexpr` function to return the symbol or `constexpr` global variable should be used. -#### eosiolib/contract.hpp +### eosiolib/contract.hpp - The constructor for `eosio::contract` now takes an `eosio::name` for the receiver, an `eosio::name` for the code, and a `eosio::datastream` for the datastream used for the contract. The last argument is for manually unpacking an action, see the section on `eosio::ignore` for a more indepth usage. -#### eosiolib/dispatcher.hpp +### eosiolib/dispatcher.hpp - Renamed the macro `EOSIO_ABI` to `EOSIO_DISPATCH` as this is more descriptive of what this macro actually does. - Modified the definition of `EOSIO_DISPATCH` to work with the new constructor for `eosio::contract`. -#### eosiolib/multi_index.hpp +### eosiolib/multi_index.hpp - The first template parameter for `indexed_by` now requires the argument be convertible to `eosio::name::raw` (replacing `uint64_t`). - The first template parameter for `multi_index` now requires the argument be convertible to `eosio::name::raw` (replacing `uint64_t`). - The constructor now takes an `eosio::name` type for the code (replacing `uint64_t`). Scope is still `uint64_t`. - Various other replacements of `uint64_t` to `eosio::name`. -#### eosiolib/singleton.hpp +### eosiolib/singleton.hpp - The first template parameter for `eosio::singleton` now requires the argument be convertible to `eosio::name::raw` (replacing `uint64_t`). - The constructor now takes an `eosio::name` type for the code. - In the methods `get_or_create` and `set`, the argument `bill_to_account` is now of type `eosio::name` (replacing `uint64_t`). -#### eosiolib/action.hpp +### eosiolib/action.hpp - Added C++ function `eosio::require_auth`. - Added C++ function `eosio::has_auth`. - Added C++ function `eosio::is_account`. - Redefined `eosio::permission_level` to use `eosio::name` in place of `uint64_t`. - Removed the macro `ACTION`. (The identifier `ACTION` has been reused for another macro described below in the Macros section.) -#### eosiolib/permission.hpp +### eosiolib/permission.hpp - The optional provided_keys argument of the function `eosio::check_transaction_authorization` is now of the type `std::set` rather than the type `std::set`. C++ contract code should most likely be using the `eosio::public_key` struct (defined in "eosiolib/public_key.hpp") if they need to deal with EOSIO-compatible public keys rather than the `capi_public_key` struct (now renamed from its original name of `::public_key`) from the eosiolib C API. Note that existing contract code that just referred to the type `public_key` without namespace qualification may have accidentally been using the `capi_public_key` struct and therefore should ideally be modified to use the `eosio::public_key` C++ type. - The `account` and `permission` arguments of `eosio::check_permission_authorization` are both `eosio::name` now instead of `uint64_t`. -#### eosiolib/ignore.hpp +### eosiolib/ignore.hpp - Added new type `ignore`: - This type acts as a placeholder for actions that don't want to deserialize their fields but want the types to be reflected in the ABI. ``` @@ -108,25 +110,25 @@ - Added new type `ignore_wrapper`: - This allows for calling `SEND_INLINE_ACTION` with `ignore_wrapper(some_value)` against an action with an `ignore` of matching types. -### Macros +## Macros - Added `ACTION` macro which is simply a shortcut for `[[eosio::action]] void`. - Added `TABLE` macro which is simply a shortcut for `struct [[eosio::table]]`. - Added `CONTRACT` macro which is simply a shortcut for `class [[eosio::contract]]`. -### CMake +## CMake - Added `eosio.cdt-config.cmake` to allow for `find_package(eosio.cdt)`. See eosio.cdt/examples/hello or eosio.cdt/examples/template for an example. - Added new macro `add_contract`. This new contract takes a contract name, cmake target, then any normal arguments you would give to `add_executable`. See eosio.cdt/examples/hello or eosio.cdt/examples/template. - New version checking mechanism is included. See eosio.contracts/CMakeLists.txt to see this in use. -### libc +## libc - Replaced `printf`, `sprintf`, and `snprintf` with new minimal variants. This allows contracts to use these functions without causing stack overflow issues. -### libcxx +## libcxx - Removed `sstream` with the intent to return this after more has been done. - Added `__cxa_pure_virtual` to allow for pure virtual methods in contract classes. - `std::to_string` now works without the issues of stack overflows. -### attributes +## attributes - Added `[[eosio::ignore]]` attribute to flag a type as being ignored by the deserializer. This attribute is primarily only used for internal use within eosiolib. - Added `[[eosio::contract]]` attribute. This new attribute is used to mark a contract class as "contract" with the name being either the C++ name of the class or a user specified name (i.e. `[[eosio::contract("somecontract")]]`). This attribute can also be used in conjunction with the `eosio::action` and `eosio::table` attributes for tables that you would like to define outside of the `eosio::contract` class. This is used in conjunction with either the raw `eosio-cpp` option `--contract `, `-o .wasm` or with CMake `add_contract`. It acts as a filter enabling contract developers to include a header file with attributes from another contract (e.g. eosio.token) while generating an ABI devoid of those actions and tables. ```c++ @@ -153,13 +155,13 @@ ``` The above code will produce the tables `testtaba` and `testtabb` in your ABI. Example: `eosio-cpp -abigen test.cpp -o test.wasm` will mark this compilation and ABI generation for the `eosio::contract` `test`. The same thing can be done with `eosio-cpp -abigen test.cpp -o test_contract.wasm --contract test` or with the CMake command `add_contract( test, test_contract, test.cpp )`. Either of the previous two approaches will produce a test_contract.wasm and test_contract.abi generated under the context of the contract name of `test`. -### Boost +## Boost - Boost is now part of the library. No more external dependence on Boost and all system inclusion are within it's `sysroot`. (Boost will be removed in a future release.) -## ABI generator attributes +# ABI generator attributes Unlike the old ABI generator tool, the new tool uses C++11 or GNU style attributes to mark ```actions``` and ```tables```. -#### [[eosio::action]] +### [[eosio::action]] This attribute marks either a struct or a method as an action. Example (four ways to declare an action for ABI generation): ```c++ @@ -205,12 +207,12 @@ If you don't want to use the multi-index you can explicitly specify the name in For an example contract of ABI generation see the file ./examples/abigen_test/test.cpp. You can generate the ABI for this file with `eosio-abigen test.cpp --output=test.abi`. -### Fixing an ABI or Writing an ABI Manually +## 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. -### Adding Ricardian Contracts and Clauses to 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. - The Ricardian contract should be housed in a file with the name .contracts.md and the clauses should be in a file named .clauses.md. - For each Ricardian contract, the header `

ActionName

` should be used, as this directs the ABI generator to attach this Ricardian contract to the specified action. diff --git a/docs/04_upgrading/1.5-to-1.6.md b/docs/04_upgrading/1.5-to-1.6.md index a870b8bec0..4181b6d877 100644 --- a/docs/04_upgrading/1.5-to-1.6.md +++ b/docs/04_upgrading/1.5-to-1.6.md @@ -1,7 +1,9 @@ -## Version 1.6 +--- +content_title: Version 1.6 +--- -### eosiolib -#### Partitioning +## eosiolib +### Partitioning In `eosio.cdt` v1.6.0, `eosiolib` will now be partitioned into 3 groups. These allow for finer grained allowance for particular modes of compilation. - CAPI - Contracts @@ -13,7 +15,7 @@ In `eosio.cdt` v1.6.0, `eosiolib` will now be partitioned into 3 groups. These a To access these new partitioned header files, use `` instead of ``. Please note that all the old header files are still available at the old `eosiolib` directory, but these are deprecated and will be removed in v1.7.0. Also, once you change one header file from `eosiolib` to `eosio` you will need to do so for all other occurrences at that point, because of some conflicts with the auto generated dispatcher. -### eosiolib C API +## eosiolib C API - `action.h` - `chain.h` - `crypto.h` @@ -27,7 +29,7 @@ To access these new partitioned header files, use `` inst This entire API is now only available to C developers (i.e. using `eosio-cc`), and all internal uses in `Core` and `Contracts` have been guarded behind the namespace `eosio::internal_use_do_not_use`. -### eosiolib Contracts API +## eosiolib Contracts API - `action.hpp` - added C++ wrappers for - `publication_time` -> `time_point publication_time()` @@ -80,7 +82,7 @@ This entire API is now only available to C developers (i.e. using `eosio-cc`), a - `expiration` - `get_context_free_data` -#### eosiolib Core API +### eosiolib Core API - `asset.hpp` - no changes - `binary_extension.hpp` @@ -119,10 +121,10 @@ This entire API is now only available to C developers (i.e. using `eosio-cc`), a - `varint.hpp` - no changes -### Auto Code Generation +## Auto Code Generation 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 +### 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. In addition to actions and notification handlers, two new "hooks" are available. diff --git a/docs/05_best-practices/03_resource-planning.md b/docs/05_best-practices/03_resource-planning.md index 1ba7fb6f62..4b3b70e44f 100644 --- a/docs/05_best-practices/03_resource-planning.md +++ b/docs/05_best-practices/03_resource-planning.md @@ -1,4 +1,6 @@ -## Resource planning +--- +content_title: Resource planning +--- How much RAM do I need? This is not an easy question to answer, and there's really no perfect answer for it. You need to find out by measuring your contracts' actions and by planning accordingly based on your predictions on how fast and how much your blockchain application will grow. If your blockchain application growth is requiring more storage capacity you'll need to buy more RAM. If it requires more actions to be executed in the 3 day window (the staking time) you need to stake more tokens for CPU bandwidth. If your blockchain application growth means more actions will be stored on the blockchain then you also will need to expand your NET bandwidth maximum limit by staking more tokens for NET bandwidth. diff --git a/docs/05_best-practices/04_data-design-and-migration.md b/docs/05_best-practices/04_data-design-and-migration.md index ec126b4250..5d847064a9 100644 --- a/docs/05_best-practices/04_data-design-and-migration.md +++ b/docs/05_best-practices/04_data-design-and-migration.md @@ -1,4 +1,6 @@ -# Data design and migration +--- +content_title: Data design and migration +--- EOSIO based blockchains allow developers to easily update their smart contract code. However, a few things need to be considered when it comes to data update and/or migration. The main structure for storing data in EOSIO based blockchains is the multi index table. Once a multi index table has been created with a first version of a smart contract, it has some limitations when it comes to changing its structure. Below you will find a few possible approaches which you can consider when you design your smart contract data and its migration. diff --git a/docs/05_best-practices/05_securing_your_contract.md b/docs/05_best-practices/05_securing_your_contract.md index 2bcf2f39b8..122e5c52d7 100644 --- a/docs/05_best-practices/05_securing_your_contract.md +++ b/docs/05_best-practices/05_securing_your_contract.md @@ -1,4 +1,7 @@ -## Securing your contract +--- +content_title: Securing your contract +--- + These are basic recommendations that should be the foundation of securing your smart contract: 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). diff --git a/docs/05_best-practices/07_error_handling.md b/docs/05_best-practices/07_error_handling.md index 162977af7d..8f323e6e48 100644 --- a/docs/05_best-practices/07_error_handling.md +++ b/docs/05_best-practices/07_error_handling.md @@ -1,4 +1,6 @@ -## Error handling +--- +content_title: Error handling +--- Contracts are able to use `uint64_t` error codes as an alternative (and cheaper) means of signaling error conditions as opposed to string error messages. However, EOSIO and EOSIO.CDT reserve certain ranges of the `uint64_t` value space for their own purposes. They assume that the contract develop respects the following restrictions: 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 937f18cab4..cf2864d8dc 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,7 +1,10 @@ -## ABI/Code generator attributes explained +--- +content_title: ABI/Code generator attributes explained +--- + The new ABI generator tool uses C++11 or GNU style attributes to mark `actions` and `tables`. -### [[eosio::action]] +## [[eosio::action]] This attribute marks either a struct or a method as an action. Example (four ways to declare an action for ABI generation): ```cpp @@ -30,7 +33,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("")]]``` -### [[eosio::table]] +## [[eosio::table]] Example (two ways to declare a table for ABI generation): ```cpp struct [[eosio::table]] testtable { @@ -48,7 +51,7 @@ typedef eosio::multi_index<"tablename"_n, testtable> testtable_t; If you don't want to use the multi-index you can explicitly specify the name in the attribute ```c++ [[eosio::table("")]]```. -### [[eosio::contract("ANY_NAME_YOU_LIKE")]] +## [[eosio::contract("ANY_NAME_YOU_LIKE")]] ```cpp class [[eosio::contract("ANY_NAME_YOU_LIKE")]] test_contract : public eosio::contract { }; @@ -56,7 +59,7 @@ class [[eosio::contract("ANY_NAME_YOU_LIKE")]] test_contract : public eosio::con The code above will mark this `class` as being an `EOSIO` contract, this allows for namespacing of contracts, i.e. you can include headers like `eosio::token` and not have `eosio::token`'s actions/tables wind up in you ABI or generated dispatcher. -### [[eosio::on_notify("VALID_EOSIO_ACCOUNT_NAME::VALID_EOSIO_ACTION_NAME")]] +## [[eosio::on_notify("VALID_EOSIO_ACCOUNT_NAME::VALID_EOSIO_ACTION_NAME")]] ```cpp [[eosio::on_notify("eosio.token::transfer")]] void on_token_transfer(name from, name to, assert quantity, std::string memo) { @@ -69,7 +72,7 @@ void on_any_transfer(name from, name to, assert quantity, std::string memo) { } ``` -### [[eosio::wasm_entry]] +## [[eosio::wasm_entry]] ```cpp [[eosio::wasm_entry]] void some_function(...) { @@ -79,7 +82,7 @@ void some_function(...) { The code above will mark an arbitrary function as an entry point, which will then wrap the function with global constructors (ctors) and global destructors (dtors). This will allow for the eosio.cdt toolchain to produce WASM binaries for other ecosystems. -### [[eosio::wasm_import]] +## [[eosio::wasm_import]] ```cpp extern "C" { __attribute__((eosio_wasm_import)) 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 e2c10c7396..c97a45726a 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,8 +1,11 @@ -## Manually write, or fix, an ABI file +--- +content_title: Manually write, or fix, 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. -### Adding Ricardian Contracts and Clauses to 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. - The Ricardian contracts should be housed in a file with the name `.contracts.md` and the clauses should be in a file named `.clauses.md`. - For each Ricardian contract the header `

ActionName

` should be used, as this directs the ABI generator to attach this Ricardian contract to the specified action. diff --git a/docs/05_best-practices/09_deferred_transactions.md b/docs/05_best-practices/09_deferred_transactions.md index 0989b6372c..63ca63c2aa 100644 --- a/docs/05_best-practices/09_deferred_transactions.md +++ b/docs/05_best-practices/09_deferred_transactions.md @@ -1,4 +1,6 @@ -## Deferred transactions +--- +content_title: Deferred transactions +--- Deferred communication conceptually takes the form of action notifications sent to a peer transaction. Deferred actions get scheduled to run, at best, at a later time, at the producer's discretion. There is no guarantee that a deferred action will be executed. diff --git a/docs/05_best-practices/10_native-tester-compilation.md b/docs/05_best-practices/10_native-tester-compilation.md index e677980021..c1dc48e348 100644 --- a/docs/05_best-practices/10_native-tester-compilation.md +++ b/docs/05_best-practices/10_native-tester-compilation.md @@ -1,7 +1,10 @@ -## How to use native tester/compilation +--- +content_title: How to use native tester/compilation +--- + As of v1.5.0 native compilation can be performed and a new set of libraries to facilitate native testing and native "scratch pad" compilation. [`eosio-cc`](../03_command-reference/eosio-cc.md), [`eosio-cpp`](../03_command-reference/eosio-cpp.md) and [`eosio-ld`](../03_command-reference/eosio-ld.md) now support building "smart contracts" and unit tests natively for quick tests to help facilitate faster development \(note the default implementations of eosio `intrinsics` are currently asserts that state they are unavailable, these are user definable.\) -### Getting Started +## Getting Started Once you have your smart contract written then a test source file can be written. `hello.hpp` @@ -90,12 +93,12 @@ int main(int argc, char** argv) { Every `intrinsic` that is defined for eosio (prints, require_auth, etc.) is re-definable given the `intrinsics::set_intrinsics()` functions. These take a lambda whose arguments and return type should match that of the intrinsic you are trying to define. This gives the contract writer the flexibility to modify behavior to suit the unit test being written. A sister function `intrinsics::get_intrinsics()` will return the function object that currently defines the behavior for said intrinsic. This pattern can be used to mock functionality and allow for easier testing of smart contracts. For more information see, either the [tests](https://github.com/EOSIO/eosio.cdt/blob/master/examples/hello/tests/) directory or [hello_test.cpp](https://github.com/EOSIO/eosio.cdt/blob/master/examples/hello/tests/hello_test.cpp) for working examples. -### Compiling Native Code +## Compiling Native Code - Raw `eosio-cpp` to compile the test or program the only addition needed to the command line is to add the flag `-fnative` this will then generate native code instead of `wasm` code. - Via CMake - `add_native_library` and `add_native_executable` CMake macros have been added (these are a drop in replacement for add_library and add_executable). -### Eosio.CDT Native Tester API +## Eosio.CDT Native Tester API - CHECK_ASSERT(...) : This macro will check whether a particular assert has occured and flag the tests as failed but allow the rest of the tests to run. - This is called either by - `CHECK_ASSERT("", [](){ whatever_function(); })` diff --git a/docs/05_best-practices/11_debugging_a_smart_contract.md b/docs/05_best-practices/11_debugging_a_smart_contract.md index 7c03da1f91..2debfd3f4b 100644 --- a/docs/05_best-practices/11_debugging_a_smart_contract.md +++ b/docs/05_best-practices/11_debugging_a_smart_contract.md @@ -1,4 +1,6 @@ -## Debugging a smart contract +--- +content_title: Debugging a smart contract +--- In order to be able to debug your smart contract, you will need to setup a local nodeos node. This local nodeos node can be run as separate private testnet or as an extension of a public testnet. This local node also needs to be run with the contracts-console option on, either `--contracts-console` via the command line or `contracts-console = true` via the config.ini and/or by setting up logging on your running nodeos node and checking the output logs. See below for details on logging. @@ -8,10 +10,10 @@ The concept is the same, so for the following guide, debugging on the private te If you haven't set up your own local nodeos, follow the [setup guide](https://developers.eos.io/eosio-home/docs/getting-the-software). By default, your local nodeos will just run in a private testnet unless you modify the config.ini file to connect with public testnet (or official testnet) nodes. -## Method +# Method The main method used to debug smart contract is **Caveman Debugging**. Printing is utilized to inspect the value of a variable and check the flow of the contract. Printing in smart contracts can be done through the Print API. The C++ API is a wrapper for C API and is the recommended API. -## Print +# Print Print C API supports the following data type that you can print: - prints - a null terminated char array (string) - prints_l - any char array (string) with given size @@ -31,10 +33,10 @@ The Print C++ API wraps some of the above C API by overriding the print() functi - base32 string encoded as 64-bit unsigned integer - struct that has print() method -## Example +# Example Here's an example contract for debugging -### debug.hpp +## debug.hpp ```cpp namespace debug { @@ -48,7 +50,7 @@ namespace debug { }; } ``` -### debug.cpp +## debug.cpp ```cpp #include @@ -74,7 +76,7 @@ extern "C" { } } // extern "C" ``` -### debug.abi +## debug.abi ```json { diff --git a/docs/05_best-practices/binary-extension.md b/docs/05_best-practices/12_binary-extension.md similarity index 99% rename from docs/05_best-practices/binary-extension.md rename to docs/05_best-practices/12_binary-extension.md index 3f3d09d674..c7783641ac 100644 --- a/docs/05_best-practices/binary-extension.md +++ b/docs/05_best-practices/12_binary-extension.md @@ -1,4 +1,6 @@ -## eosio::binary_extension +--- +content_title: The eosio::binary_extension type +--- Let's fully explain what the `eosio::binary_extension` type is, what it does, and why we need it for contract upgrades in certain situations. 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 index c50c6155e6..060efe3869 100644 --- 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 @@ -1,6 +1,8 @@ -## How to compile a contract via CLI +--- +content_title: How to compile a contract via CLI +--- -### Preconditions +## 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) 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 index 8811abf5d1..22549b473c 100644 --- 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 @@ -1,8 +1,10 @@ -## How to configure CMake +--- +content_title: How to configure CMake +--- -### CMake Configuration +## CMake Configuration -#### Automatic 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: @@ -16,7 +18,7 @@ To compile an EOSIO smart contract with CMake, you'll need a CMake file. To use 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 +### 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. 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 index 70afbb5fd0..54bd0814d6 100644 --- 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 @@ -1,6 +1,8 @@ -## How to compile a smart contract with CMake +--- +content_title: How to compile a smart contract with CMake +--- -### Preconditions +## 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) diff --git a/docs/06_how-to-guides/02_multi-index/how-to-define-a-primary-index.md b/docs/06_how-to-guides/02_multi-index/how-to-define-a-primary-index.md index dc1eb6265d..edebe2c726 100644 --- a/docs/06_how-to-guides/02_multi-index/how-to-define-a-primary-index.md +++ b/docs/06_how-to-guides/02_multi-index/how-to-define-a-primary-index.md @@ -1,4 +1,6 @@ -## How to define a primary index +--- +content_title: How to define a primary index +--- A primary key is required when defining a multi index table structure. See the following example: diff --git a/docs/06_how-to-guides/02_multi-index/how-to-define-a-secondary-index.md b/docs/06_how-to-guides/02_multi-index/how-to-define-a-secondary-index.md index 3036e20b45..a0aaae5329 100644 --- a/docs/06_how-to-guides/02_multi-index/how-to-define-a-secondary-index.md +++ b/docs/06_how-to-guides/02_multi-index/how-to-define-a-secondary-index.md @@ -1,6 +1,8 @@ -## How to define a secondary index +--- +content_title: How to define a secondary index +--- -### Preconditions +## Preconditions - It is assumed you already have a multi index table instance defined along with its mandatory primary index, otherwise take a look at the section [How to instantiate a multi index table](./how-to-instantiate-a-multi-index-table.md). The steps below show how to add a secondary index to the existing multi index table. diff --git a/docs/06_how-to-guides/02_multi-index/how-to-define-a-singleton.md b/docs/06_how-to-guides/02_multi-index/how-to-define-a-singleton.md index ce861a6103..4729372b53 100644 --- a/docs/06_how-to-guides/02_multi-index/how-to-define-a-singleton.md +++ b/docs/06_how-to-guides/02_multi-index/how-to-define-a-singleton.md @@ -1,4 +1,6 @@ -## How to define a singleton +--- +content_title: How to define a singleton +--- To define a simple singleton, which is storing an account name as primary value and a uint64_t as secondary value in structure `testtable`, follow the steps below: 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/02_multi-index/how-to-delete-data-from-a-multi-index-table.md index 5ea7abf61c..f1079c7aec 100644 --- 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/02_multi-index/how-to-delete-data-from-a-multi-index-table.md @@ -1,6 +1,8 @@ -## How to delete data from a multi index table +--- +content_title: How to delete data from a multi index table +--- -### Preconditions +## Preconditions - It is assumed you already have a multi index table instance defined along with its mandatory primary index, otherwise take a look at the section [How to instantiate a multi index table](./how-to-instantiate-a-multi-index-table.md). To delete data from a multi index table follow the steps below: 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/02_multi-index/how-to-insert-data-into-a-multi-index-table.md index ac5510fbe2..81df3702d9 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/02_multi-index/how-to-insert-data-into-a-multi-index-table.md @@ -1,6 +1,8 @@ -## How to insert data into a multi index table +--- +content_title: How to insert data into a multi index table +--- -### Preconditions +## Preconditions - It is assumed you already have a multi index table instance defined along with its mandatory primary index, otherwise take a look at the section [How to instantiate a multi index table](./how-to-instantiate-a-multi-index-table.md). To insert data into a multi index table follow the following steps 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/02_multi-index/how-to-instantiate-a-multi-index-table.md index c1c2d33877..9a33cf0abb 100644 --- a/docs/06_how-to-guides/02_multi-index/how-to-instantiate-a-multi-index-table.md +++ b/docs/06_how-to-guides/02_multi-index/how-to-instantiate-a-multi-index-table.md @@ -1,4 +1,6 @@ -## How to instantiate a multi index table +--- +content_title: How to instantiate a multi index table +--- 1. Include the `eosio.hpp` header and declare the `eosio` namespace usage ``` 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/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table-based-on-secondary-index.md index 400abb4156..f2363f4477 100644 --- 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/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table-based-on-secondary-index.md @@ -1,6 +1,8 @@ -## How to iterate and retreive a multi index table based on secondary index +--- +content_title: How to iterate and retrieve a multi index table based on secondary index +--- -### Preconditions +## Preconditions - It is assumed you already have a multi index table defined with a primary index and a secondary index, if not you can find an example [here](./how-to-define-a-secondary-index.md). You'll start with this example below which shows the definition of a `multi_index_example` contract class which has defined a multi index table with two indexes, a mandatory primary one and a secondary one: 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/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table.md index 7aae513058..188f50e1f7 100644 --- 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/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table.md @@ -1,6 +1,8 @@ -## How to iterate and retrieve a multi index table +--- +content_title: How to iterate and retrieve a multi index table +--- -### Preconditions +## Preconditions - It is assumed you already have a multi index table instance defined along with its mandatory primary index, otherwise take a look at the section [How to instantiate a multi index table](./how-to-instantiate-a-multi-index-table.md). For exemplification define the multi index contract definition like below: 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/02_multi-index/how-to-modify-data-in-a-multi-index-table.md index af5f600c59..c3be46ea60 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/02_multi-index/how-to-modify-data-in-a-multi-index-table.md @@ -1,6 +1,8 @@ -## How to modify data in a multi index table +--- +content_title: How to modify data in a multi index table +--- -### Preconditions +## Preconditions - It is assumed you already have a multi index table instance defined along with its mandatory primary index, otherwise take a look at the section [How to instantiate a multi index table](./how-to-instantiate-a-multi-index-table.md). To modify data in the multi index table defined in the above tutorial, you will implement an action `mod` which it will receive as parameter the `user` which is the key of the row you want to modify and the `value` param which is the value to update with the row. 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 index e6f354f770..f717d9a960 100644 --- 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 @@ -1,4 +1,6 @@ -## How to create and use action wrappers +--- +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 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 index a1a2754560..4ec1229724 100644 --- 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 @@ -1,6 +1,8 @@ -## How to restrict access to an action by a user +--- +content_title: How to restrict access to an action by a user +--- -### Preconditions +## 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: diff --git a/docs/08_troubleshooting.md b/docs/08_troubleshooting.md index b3465e9ad2..b41d979018 100644 --- a/docs/08_troubleshooting.md +++ b/docs/08_troubleshooting.md @@ -1,6 +1,8 @@ -## Troubleshooting +--- +content_title: Troubleshooting +--- -### When sending an action to the blockchain you get the error below +## When sending an action to the blockchain you get the error below ```{ "code":500, "message":"Internal Service Error", @@ -21,7 +23,7 @@ ``` __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: +## When sending an action to the blockchain an error similar to the one below is encountered: ```sh Error 3015014: Pack data exception Error Details: @@ -33,14 +35,14 @@ 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: +## When sending an action to the blockchain an error similar to the one below is encountered: ```sh 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: +## When deploying a contract code to the blockchain a similar error with the ones below is encountered: ```sh Error 3160010: No abi file found or @@ -48,11 +50,11 @@ 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. +## 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: +## 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 Error 3050003: eosio_assert_message assertion failure Error Details: @@ -61,26 +63,26 @@ 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. +## 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 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: +## 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 Error 3050004: eosio_assert_code assertion failure Error Details: @@ -88,7 +90,7 @@ 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. @@ -111,7 +113,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: diff --git a/docs/09_tutorials/01_binary-extension.md b/docs/09_tutorials/01_binary-extension.md index b48f8cb80b..c149089790 100644 --- a/docs/09_tutorials/01_binary-extension.md +++ b/docs/09_tutorials/01_binary-extension.md @@ -1,4 +1,6 @@ -# eosio::binary_extension +--- +content_title: eosio::binary_extension +--- You can find the implementation of `eosio::binary_extension` in the `eosio.cdt` repository in [binary_extension.hpp](https://github.com/EOSIO/eosio.cdt/blob/master/libraries/eosiolib/binary_extension.hpp). diff --git a/docs/09_tutorials/02_abi-variants.md b/docs/09_tutorials/02_abi-variants.md index 9e99f1f912..51430b4b08 100644 --- a/docs/09_tutorials/02_abi-variants.md +++ b/docs/09_tutorials/02_abi-variants.md @@ -1,9 +1,11 @@ -## ABI variants +--- +content_title: ABI variants +--- ABI variants give the flexibility of using more than one type for a defined variable or data member. In EOSIO, the variants make use of the standard template library `variant` which was introduced in C++ 17. An instance of `std::variant` at any given time either holds a value of one of its alternative types, or in the case of error - no value. Because of this trait, variants can be used to build the multi index table structure with flexibility. Used in conjunction with ABI extensions, it allows for modification of the structure of an exiting multi index table, a.k.a. table. -### Use variant when building the multi index table the first time +## Use variant when building the multi index table the first time To define a `variant` for your table structure one example is shown below @@ -100,9 +102,9 @@ class [[eosio::contract]] multi_index_example : public contract { Now you can deploy the contract and it will be backwards compatible with the previous existing multi index table. -### Use variant when changing an already deployed multi index table +## Use variant when changing an already deployed multi index table -#### Preconditions +### 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. 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: diff --git a/docs/index.md b/docs/index.md index 3dc341ead1..ba66b91f59 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,5 @@ # 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. From 11bb8a640db5af0d8071f793e377052717a7fd57 Mon Sep 17 00:00:00 2001 From: iamveritas Date: Tue, 3 Dec 2019 14:14:51 +0200 Subject: [PATCH 86/99] Get rid of the improper use of "----" and "---" --- README.md | 2 +- docs/02_installation.md | 4 ++-- docs/03_command-reference/eosio-abidiff.md | 2 +- docs/03_command-reference/eosio-abigen.md | 2 +- docs/03_command-reference/eosio-init.md | 2 +- docs/03_command-reference/eosio-ld.md | 2 +- docs/04_upgrading/1.2-to-1.3.md | 2 +- docs/04_upgrading/1.5-to-1.6.md | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 950e2ce10f..9ff4c63570 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ sudo rm /usr/local/bin/eosio-* ``` ## Installed Tools ---- + * eosio-cpp * eosio-cc * eosio-ld diff --git a/docs/02_installation.md b/docs/02_installation.md index 680d8addbe..88952be309 100644 --- a/docs/02_installation.md +++ b/docs/02_installation.md @@ -66,7 +66,7 @@ $ sudo rm /usr/local/bin/eosio-* # Installed Tools ---- + * eosio-cpp * eosio-cc * eosio-ld @@ -88,5 +88,5 @@ eosio-readelf License ----- + [MIT](../LICENSE) diff --git a/docs/03_command-reference/eosio-abidiff.md b/docs/03_command-reference/eosio-abidiff.md index 29dd8a34be..eb41f9ad45 100644 --- a/docs/03_command-reference/eosio-abidiff.md +++ b/docs/03_command-reference/eosio-abidiff.md @@ -11,7 +11,7 @@ $ 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 35ea5849ba..50b3a3a692 100644 --- a/docs/03_command-reference/eosio-abigen.md +++ b/docs/03_command-reference/eosio-abigen.md @@ -13,7 +13,7 @@ $ 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 fb6dd99039..2f83eb4333 100644 --- a/docs/03_command-reference/eosio-init.md +++ b/docs/03_command-reference/eosio-init.md @@ -11,7 +11,7 @@ $ eosio-abigen hello.cpp --contract=hello --output=hello.abi This will generate one file: * The generated ABI file (hello.abi) ---- + ``` USAGE: eosio-init [options] diff --git a/docs/03_command-reference/eosio-ld.md b/docs/03_command-reference/eosio-ld.md index 0e4e8f74ae..21b28fe974 100644 --- a/docs/03_command-reference/eosio-ld.md +++ b/docs/03_command-reference/eosio-ld.md @@ -4,7 +4,7 @@ content_title: eosio-ld tool The eosio-ld tool is a the custom web assembly linker for EOSIO platform smart contracts. ---- + ``` USAGE: eosio-ld [options] ... diff --git a/docs/04_upgrading/1.2-to-1.3.md b/docs/04_upgrading/1.2-to-1.3.md index 3d5b66a0ef..840c22b6b0 100644 --- a/docs/04_upgrading/1.2-to-1.3.md +++ b/docs/04_upgrading/1.2-to-1.3.md @@ -223,5 +223,5 @@ 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 4181b6d877..83d438fec6 100644 --- a/docs/04_upgrading/1.5-to-1.6.md +++ b/docs/04_upgrading/1.5-to-1.6.md @@ -142,5 +142,5 @@ 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) From d70ecd93a22653066ffe31210d7865902c6488d9 Mon Sep 17 00:00:00 2001 From: dskvr Date: Mon, 9 Dec 2019 23:24:28 +0100 Subject: [PATCH 87/99] Fix callouts: Info -> info --- .../02_multi-index/how-to-define-a-primary-index.md | 4 ++-- .../02_multi-index/how-to-define-a-secondary-index.md | 2 +- .../02_multi-index/how-to-define-a-singleton.md | 2 +- .../how-to-delete-data-from-a-multi-index-table.md | 2 +- .../how-to-insert-data-into-a-multi-index-table.md | 6 +++--- .../how-to-instantiate-a-multi-index-table.md | 4 ++-- ...retrieve-a-multi_index-table-based-on-secondary-index.md | 2 +- .../how-to-iterate-and-retrieve-a-multi_index-table.md | 2 +- .../how-to-modify-data-in-a-multi-index-table.md | 2 +- docs/09_tutorials/02_abi-variants.md | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/06_how-to-guides/02_multi-index/how-to-define-a-primary-index.md b/docs/06_how-to-guides/02_multi-index/how-to-define-a-primary-index.md index edebe2c726..7bf18d8491 100644 --- a/docs/06_how-to-guides/02_multi-index/how-to-define-a-primary-index.md +++ b/docs/06_how-to-guides/02_multi-index/how-to-define-a-primary-index.md @@ -37,7 +37,7 @@ using namespace eosio; }; ``` -[[Info | Secondary indexes information]] +[[info | Secondary indexes information]] | Other, secondary, indexes if they will be defined can have duplicates. You can have up to 16 additional indexes and the field types can be uint64_t, uint128_t, uint256_t, double or long double. 5. For ease of use, define a type alias `test_tables` based on the `eosio::multi_index` template type, parametarized with a random name `"testtaba"` and the `test_table` data structure defined above @@ -73,5 +73,5 @@ Declare the multi index table as a data member of type `test_tables`, as defined Now you have instantiated the `testtab` as a multi index table which has a primary index defined for its `test_primary` data member. -[[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). \ No newline at end of file diff --git a/docs/06_how-to-guides/02_multi-index/how-to-define-a-secondary-index.md b/docs/06_how-to-guides/02_multi-index/how-to-define-a-secondary-index.md index a0aaae5329..c6a36082d5 100644 --- a/docs/06_how-to-guides/02_multi-index/how-to-define-a-secondary-index.md +++ b/docs/06_how-to-guides/02_multi-index/how-to-define-a-secondary-index.md @@ -88,5 +88,5 @@ class [[eosio::contract]] multi_index_example : public contract { }; ``` -[[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). \ No newline at end of file diff --git a/docs/06_how-to-guides/02_multi-index/how-to-define-a-singleton.md b/docs/06_how-to-guides/02_multi-index/how-to-define-a-singleton.md index 4729372b53..b20846a9bd 100644 --- a/docs/06_how-to-guides/02_multi-index/how-to-define-a-singleton.md +++ b/docs/06_how-to-guides/02_multi-index/how-to-define-a-singleton.md @@ -112,5 +112,5 @@ __singleton_example.cpp__ ``` -[[Info | Full example location]] +[[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/02_multi-index/how-to-delete-data-from-a-multi-index-table.md index f1079c7aec..3f2fbc5050 100644 --- 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/02_multi-index/how-to-delete-data-from-a-multi-index-table.md @@ -29,5 +29,5 @@ To delete data from a multi index table follow the steps below: } ``` -[[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). \ No newline at end of file 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/02_multi-index/how-to-insert-data-into-a-multi-index-table.md index 81df3702d9..4a747e643e 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/02_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 @@ -32,5 +32,5 @@ 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). \ No newline at end of file +[[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/02_multi-index/how-to-instantiate-a-multi-index-table.md b/docs/06_how-to-guides/02_multi-index/how-to-instantiate-a-multi-index-table.md index 9a33cf0abb..3a2120a6e0 100644 --- a/docs/06_how-to-guides/02_multi-index/how-to-instantiate-a-multi-index-table.md +++ b/docs/06_how-to-guides/02_multi-index/how-to-instantiate-a-multi-index-table.md @@ -35,7 +35,7 @@ using namespace eosio; }; ``` -[[Info | Additional indexes information]] +[[info | Additional indexes information]] | Other, secondary, indexes if they will be defined can have duplicates. You can have up to 16 additional indexes and the field types can be uint64_t, uint128_t, uint256_t, double or long double. 5. For ease of use, define a type alias `test_tables` based on the multi_index template type, parametarized with a random name `"testtaba"` and the `test_table` data structure defined above @@ -129,5 +129,5 @@ class [[eosio::contract]] multi_index_example : public contract { }; ``` -[[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). \ No newline at end of file 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/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table-based-on-secondary-index.md index f2363f4477..939abbd7e8 100644 --- 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/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table-based-on-secondary-index.md @@ -171,5 +171,5 @@ __multi_index_example.cpp__ } ``` -[[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). \ No newline at end of file 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/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table.md index 188f50e1f7..3654701d8a 100644 --- 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/02_multi-index/how-to-iterate-and-retrieve-a-multi_index-table.md @@ -153,5 +153,5 @@ __multi_index_example.cpp__ } ``` -[[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). \ No newline at end of file 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/02_multi-index/how-to-modify-data-in-a-multi-index-table.md index c3be46ea60..8450f39cfd 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/02_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/09_tutorials/02_abi-variants.md b/docs/09_tutorials/02_abi-variants.md index 51430b4b08..e20f4bb461 100644 --- a/docs/09_tutorials/02_abi-variants.md +++ b/docs/09_tutorials/02_abi-variants.md @@ -155,5 +155,5 @@ 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! -[[Info | Implemenatation location]] +[[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 From 4eb3d9694cc68fe894bb7bbb401e6bf6407d9035 Mon Sep 17 00:00:00 2001 From: dskvr Date: Tue, 10 Dec 2019 02:08:35 +0100 Subject: [PATCH 88/99] Fix typos and broken sentence --- docs/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index ba66b91f59..87b023e2e3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,10 +5,10 @@ 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 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. +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. ## Upgrading -There's been a round of braking 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 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). ## Contributing From ce3877a4acbb9de9757f60fdc1c43915cd690313 Mon Sep 17 00:00:00 2001 From: Luis Paris Date: Fri, 13 Dec 2019 16:00:27 -0500 Subject: [PATCH 89/99] update uint64_t error code ranges --- docs/05_best-practices/07_error_handling.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/05_best-practices/07_error_handling.md b/docs/05_best-practices/07_error_handling.md index 8f323e6e48..cc54a013f6 100644 --- a/docs/05_best-practices/07_error_handling.md +++ b/docs/05_best-practices/07_error_handling.md @@ -2,12 +2,14 @@ content_title: Error handling --- -Contracts are able to use `uint64_t` error codes as an alternative (and cheaper) means of signaling error conditions as opposed to string error messages. However, EOSIO and EOSIO.CDT reserve certain ranges of the `uint64_t` value space for their own purposes. They assume that the contract develop respects the following restrictions: +Contracts can use `uint64_t` error codes as an alternative (and shorter) means of signaling error conditions, as opposed to string error messages. However, EOSIO and EOSIO.CDT reserve certain ranges of the `uint64_t` value space for their own purposes. The contract developer must be aware of the following ranges and restrictions: -1. 0 (inclusive) to 5,000,000,000,000,000,000 (exclusive): Available for contract developers to use to signal errors specific to their contracts. +1. 0 to 4,999,999,999,999,999,999: Available for contract developers to assign error codes specific to their contracts. -2. 5,000,000,000,000,000,000 (inclusive) to 8,000,000,000,000,000,000 (exclusive): Reserved for the EOSIO.CDT compiler to allocate as appropriate. Although the WASM code generated by the EOSIO.CDT compiler may use error code values that were automatically generated from within this range, the error codes in this range are meant to have meaning specific to the particular compiled contract (the meaning would typically be conveyed through the mapping between the error code value and strings in the associated generated ABI file). +2. 5,000,000,000,000,000,000 to 7,999,999,999,999,999,999: Reserved for the EOSIO.CDT compiler to allocate as appropriate. Although the WASM code generated by the EOSIO.CDT compiler may use error code values that were automatically generated from within this range, the error codes in this range are meant to have meaning specific to the particular compiled contract (the meaning would typically be conveyed through the mapping between the error code value and strings in the associated generated ABI file). -3. 8,000,000,000,000,000,000 (inclusive) to 10,000,000,000,000,000,000 (exclusive): Reserved for the EOSIO.CDT compiler to allocate as appropriate. The error codes in this range are not specific to any contract but rather are used to convey general runtime error conditions associated with the generated code by EOSIO.CDT. +3. 8,000,000,000,000,000,000 to 9,999,999,999,999,999,999: Reserved for the EOSIO.CDT compiler to allocate as appropriate. The error codes in this range are not specific to any contract but rather are used to convey general runtime error conditions associated with the generated code by EOSIO.CDT. -4. 10,000,000,000,000,000,000 (inclusive) to 18,446,744,073,709,551,615 (inclusive): Reserved for EOSIO to represent system-level error conditions. EOSIO will actually enforce this by restricting the ability for `eosio_assert_code` to be used to fail with error code values used within this range. \ No newline at end of file +4. 10,000,000,000,000,000,000 to 18,446,744,073,709,551,615: Reserved for EOSIO to represent system-level error conditions. EOSIO will actually enforce this by restricting the ability for `eosio_assert_code` to be used to fail with error code values used within this range. + +Therefore, contract developers should only reserve error codes from the first range above (0 to 4,999,999,999,999,999,999) to use in their contracts. From 4e4a84f188dc05172820812505e8276c8d92a06b Mon Sep 17 00:00:00 2001 From: Luis Paris Date: Sat, 14 Dec 2019 22:35:02 -0500 Subject: [PATCH 90/99] wrap error code ranges in latex --- docs/05_best-practices/07_error_handling.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/05_best-practices/07_error_handling.md b/docs/05_best-practices/07_error_handling.md index cc54a013f6..549a21ce59 100644 --- a/docs/05_best-practices/07_error_handling.md +++ b/docs/05_best-practices/07_error_handling.md @@ -2,14 +2,18 @@ content_title: Error handling --- -Contracts can use `uint64_t` error codes as an alternative (and shorter) means of signaling error conditions, as opposed to string error messages. However, EOSIO and EOSIO.CDT reserve certain ranges of the `uint64_t` value space for their own purposes. The contract developer must be aware of the following ranges and restrictions: +Contracts can use `uint64_t` error codes as an alternative (and shorter) means of signaling error conditions, as opposed to string error messages. However, EOSIO and EOSIO.CDT reserve certain ranges of the `uint64_t` value space for their own purposes. Contract developers must be aware of the following ranges and restrictions: -1. 0 to 4,999,999,999,999,999,999: Available for contract developers to assign error codes specific to their contracts. +1. $0 - 4,999,999,999,999,999,999$: +Available for contract developers to assign error codes specific to their contracts. -2. 5,000,000,000,000,000,000 to 7,999,999,999,999,999,999: Reserved for the EOSIO.CDT compiler to allocate as appropriate. Although the WASM code generated by the EOSIO.CDT compiler may use error code values that were automatically generated from within this range, the error codes in this range are meant to have meaning specific to the particular compiled contract (the meaning would typically be conveyed through the mapping between the error code value and strings in the associated generated ABI file). +2. $5,000,000,000,000,000,000 - 7,999,999,999,999,999,999$: +Reserved for the EOSIO.CDT compiler to allocate as appropriate. Although the WASM code generated by the EOSIO.CDT compiler may use error code values that were automatically generated from within this range, the error codes in this range are meant to have meaning specific to the particular compiled contract (the meaning would typically be conveyed through the mapping between the error code value and strings in the associated generated ABI file). -3. 8,000,000,000,000,000,000 to 9,999,999,999,999,999,999: Reserved for the EOSIO.CDT compiler to allocate as appropriate. The error codes in this range are not specific to any contract but rather are used to convey general runtime error conditions associated with the generated code by EOSIO.CDT. +3. $8,000,000,000,000,000,000 - 9,999,999,999,999,999,999$: +Reserved for the EOSIO.CDT compiler to allocate as appropriate. The error codes in this range are not specific to any contract but rather are used to convey general runtime error conditions associated with the generated code by EOSIO.CDT. -4. 10,000,000,000,000,000,000 to 18,446,744,073,709,551,615: Reserved for EOSIO to represent system-level error conditions. EOSIO will actually enforce this by restricting the ability for `eosio_assert_code` to be used to fail with error code values used within this range. +4. $10,000,000,000,000,000,000 - 18,446,744,073,709,551,615$: +Reserved for EOSIO to represent system-level error conditions. EOSIO will actually enforce this by restricting the ability for `eosio_assert_code` to be used to fail with error code values used within this range. -Therefore, contract developers should only reserve error codes from the first range above (0 to 4,999,999,999,999,999,999) to use in their contracts. +Therefore, contract developers should only reserve error codes from the first range above to use in their contracts. From 0ea6ba5ec819310fe42abc2a1fbcc2acf304a579 Mon Sep 17 00:00:00 2001 From: dskvr Date: Mon, 16 Dec 2019 10:19:35 +0100 Subject: [PATCH 91/99] Add docs.jsonm --- docs.json | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 docs.json diff --git a/docs.json b/docs.json new file mode 100644 index 0000000000..0e5dfaece1 --- /dev/null +++ b/docs.json @@ -0,0 +1,32 @@ +{ + "name": "eosio.cdt", + "generators": [ + { + "name": "collate_markdown", + "options": { + "docs_dir": "docs", + "disable_default_filters": true, + "filters": [ + { "name": "sort" }, + { "name": "remove_extension" }, + { "name": "sanitize", "exclude": ["command-reference/*"] }, + { "name": "capitalize" } + ] + } + }, + { + "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": [] + }, + { + "name": "doxybook", + "options": {} + } + ] +} From 88d08c9c86788388d1fa2b321c250949f6fcdbce Mon Sep 17 00:00:00 2001 From: dskvr Date: Mon, 16 Dec 2019 23:29:03 +0100 Subject: [PATCH 92/99] Slight modifications to docs.json --- docs.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs.json b/docs.json index 0e5dfaece1..28259a4389 100644 --- a/docs.json +++ b/docs.json @@ -9,8 +9,8 @@ "filters": [ { "name": "sort" }, { "name": "remove_extension" }, - { "name": "sanitize", "exclude": ["command-reference/*"] }, - { "name": "capitalize" } + { "name": "sanitize", "options": { "exclude": ["command-reference/eosio-*.md"] } }, + { "name": "capitalize", "options": { "exclude": ["command-reference/eosio-*.md"] } } ] } }, From 33d464ed05eef832b1994663e27e2231409f3f4c Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Wed, 18 Dec 2019 17:33:46 -0500 Subject: [PATCH 93/99] fix for extended_symbol 1.7.x --- libraries/eosiolib/core/eosio/symbol.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/eosiolib/core/eosio/symbol.hpp b/libraries/eosiolib/core/eosio/symbol.hpp index 631a9272f1..6dff81320d 100644 --- a/libraries/eosiolib/core/eosio/symbol.hpp +++ b/libraries/eosiolib/core/eosio/symbol.hpp @@ -393,14 +393,14 @@ namespace eosio { * @param sym - The symbol * @param con - The name of the contract */ - constexpr extended_symbol( symbol sym, name con ) : symbol(sym), contract(con) {} + constexpr extended_symbol( symbol s, name con ) : sym(s), contract(con) {} /** * Returns the symbol in the extended_contract * * @return symbol */ - constexpr symbol get_symbol() const { return symbol; } + constexpr symbol get_symbol() const { return sym; } /** * Returns the name of the contract in the extended_symbol @@ -415,7 +415,7 @@ namespace eosio { * @brief %Print the extended symbol */ void print( bool show_precision = true )const { - symbol.print( show_precision ); + sym.print( show_precision ); ::eosio::print("@", contract); } @@ -425,7 +425,7 @@ namespace eosio { * @return boolean - true if both provided extended_symbols are the same */ friend constexpr bool operator == ( const extended_symbol& a, const extended_symbol& b ) { - return std::tie( a.symbol, a.contract ) == std::tie( b.symbol, b.contract ); + return std::tie( a.sym, a.contract ) == std::tie( b.sym, b.contract ); } /** @@ -434,7 +434,7 @@ namespace eosio { * @return boolean - true if both provided extended_symbols are not the same */ friend constexpr bool operator != ( const extended_symbol& a, const extended_symbol& b ) { - return std::tie( a.symbol, a.contract ) != std::tie( b.symbol, b.contract ); + return std::tie( a.sym, a.contract ) != std::tie( b.sym, b.contract ); } /** @@ -443,13 +443,13 @@ namespace eosio { * @return boolean - true if extended_symbol `a` is less than `b` */ friend constexpr bool operator < ( const extended_symbol& a, const extended_symbol& b ) { - return std::tie( a.symbol, a.contract ) < std::tie( b.symbol, b.contract ); + return std::tie( a.sym, a.contract ) < std::tie( b.sym, b.contract ); } private: symbol symbol; ///< the symbol name contract; ///< the token contract hosting the symbol - EOSLIB_SERIALIZE( extended_symbol, (symbol)(contract) ) + EOSLIB_SERIALIZE( extended_symbol, (sym)(contract) ) }; } From 1ba675ef4fe6dedc9f57a9982d1227a098bcaba9 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Thu, 19 Dec 2019 11:26:21 -0500 Subject: [PATCH 94/99] Update symbol.hpp --- libraries/eosiolib/core/eosio/symbol.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/eosiolib/core/eosio/symbol.hpp b/libraries/eosiolib/core/eosio/symbol.hpp index 6dff81320d..e02c86ad07 100644 --- a/libraries/eosiolib/core/eosio/symbol.hpp +++ b/libraries/eosiolib/core/eosio/symbol.hpp @@ -447,7 +447,7 @@ namespace eosio { } private: - symbol symbol; ///< the symbol + symbol sym; ///< the symbol name contract; ///< the token contract hosting the symbol EOSLIB_SERIALIZE( extended_symbol, (sym)(contract) ) From cc79ad46a589059fe308b3345c753914e2322f1e Mon Sep 17 00:00:00 2001 From: johndebord Date: Thu, 9 Jan 2020 13:54:17 -0500 Subject: [PATCH 95/99] Allow interoperability between `binary_extension` and types with only an explicit default constructor --- libraries/eosiolib/core/eosio/binary_extension.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/eosiolib/core/eosio/binary_extension.hpp b/libraries/eosiolib/core/eosio/binary_extension.hpp index d44ed3276a..2cb2813952 100644 --- a/libraries/eosiolib/core/eosio/binary_extension.hpp +++ b/libraries/eosiolib/core/eosio/binary_extension.hpp @@ -103,12 +103,12 @@ namespace eosio { } constexpr T value_or()& { if (!_has_value) - return {}; + return T{}; return _get(); } constexpr T value_or()const& { if (!_has_value) - return {}; + return T{}; return _get(); } From a5d8f768f0f33cb5e6dbf69c12bb01402a0140aa Mon Sep 17 00:00:00 2001 From: John DeBord Date: Thu, 9 Jan 2020 15:41:53 -0500 Subject: [PATCH 96/99] Switch brackets to parenthesis --- libraries/eosiolib/core/eosio/binary_extension.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libraries/eosiolib/core/eosio/binary_extension.hpp b/libraries/eosiolib/core/eosio/binary_extension.hpp index 2cb2813952..628f0d7027 100644 --- a/libraries/eosiolib/core/eosio/binary_extension.hpp +++ b/libraries/eosiolib/core/eosio/binary_extension.hpp @@ -13,7 +13,6 @@ namespace eosio { /** * Container to hold a binary payload for an extension * - * @ingroup binary_extension * @tparam T - Contained typed */ template @@ -103,12 +102,12 @@ namespace eosio { } constexpr T value_or()& { if (!_has_value) - return T{}; + return T(); return _get(); } constexpr T value_or()const& { if (!_has_value) - return T{}; + return T(); return _get(); } From d73789e52e567d628c15bfbe3ab7b038b97adbc9 Mon Sep 17 00:00:00 2001 From: John DeBord Date: Thu, 9 Jan 2020 15:43:25 -0500 Subject: [PATCH 97/99] Fix comment --- libraries/eosiolib/core/eosio/binary_extension.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/eosiolib/core/eosio/binary_extension.hpp b/libraries/eosiolib/core/eosio/binary_extension.hpp index 628f0d7027..ac59569e43 100644 --- a/libraries/eosiolib/core/eosio/binary_extension.hpp +++ b/libraries/eosiolib/core/eosio/binary_extension.hpp @@ -13,6 +13,7 @@ namespace eosio { /** * Container to hold a binary payload for an extension * + * @ingroup binary_extension * @tparam T - Contained typed */ template From 3ddaa979e3af4efc48ae6b9c4ce2b60adc0bf5e9 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Thu, 9 Jan 2020 19:16:21 -0500 Subject: [PATCH 98/99] added macros --- modules/ToolsExternalProject.txt | 2 +- tools/include/compiler_options.hpp.in | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/ToolsExternalProject.txt b/modules/ToolsExternalProject.txt index 7bb6ccab41..de3bcfdf2f 100644 --- a/modules/ToolsExternalProject.txt +++ b/modules/ToolsExternalProject.txt @@ -5,7 +5,7 @@ include(GNUInstallDirs) set(LLVM_BINDIR ${CMAKE_BINARY_DIR}/eosio_llvm) ExternalProject_Add( EosioTools - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DCMAKE_BUILD_TYPE=Release -DVERSION_FULL=${VERSION_FULL} -DLLVM_SRCDIR=${CMAKE_SOURCE_DIR}/eosio_llvm -DLLVM_BINDIR=${LLVM_BINDIR} -DLLVM_DIR=${LLVM_BINDIR}/lib/cmake/llvm -DCMAKE_INSTALL_BINDIR=${CMAKE_INSTALL_BINDIR} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DCMAKE_BUILD_TYPE=Release -DVERSION_FULL=${VERSION_FULL} -DLLVM_SRCDIR=${CMAKE_SOURCE_DIR}/eosio_llvm -DLLVM_BINDIR=${LLVM_BINDIR} -DLLVM_DIR=${LLVM_BINDIR}/lib/cmake/llvm -DCMAKE_INSTALL_BINDIR=${CMAKE_INSTALL_BINDIR} -DVERSION_MAJOR=${VERSION_MAJOR} -DVERSION_MINOR=${VERSION_MINOR} -DVERSION_PATCH=${VERSION_PATCH} SOURCE_DIR "${CMAKE_SOURCE_DIR}/tools" BINARY_DIR "${CMAKE_BINARY_DIR}/tools" diff --git a/tools/include/compiler_options.hpp.in b/tools/include/compiler_options.hpp.in index 2792ee4248..bbbdddc52b 100644 --- a/tools/include/compiler_options.hpp.in +++ b/tools/include/compiler_options.hpp.in @@ -362,6 +362,11 @@ struct Options { static void GetCompDefaults(std::vector& copts) { const char* eosio_apply_suff = "${CMAKE_SHARED_LIBRARY_SUFFIX}"; std::string apply_lib; + // add the define for whether this is compiling with CDT and version macros + copts.emplace_back("-D__eosio_cdt__"); + copts.emplace_back(std::string("-D__eosio_cdt_major__=")+"${VERSION_MAJOR}"); + copts.emplace_back(std::string("-D__eosio_cdt_minor__=")+"${VERSION_MINOR}"); + copts.emplace_back(std::string("-D__eosio_cdt_patchlevel__=")+"${VERSION_PATCH}"); if (!fnative_opt) { copts.emplace_back("--target=wasm32"); copts.emplace_back("-ffreestanding"); @@ -380,6 +385,7 @@ static void GetCompDefaults(std::vector& copts) { if (!fasm_opt) { copts.emplace_back("-fno-builtin"); copts.emplace_back("-mstackrealign"); + copts.emplace_back("-D__eosio_cdt_native__"); copts.emplace_back("-DEOSIO_NATIVE"); copts.emplace_back("-DLLP64"); } From 52d109378e4bf796ce21b8c6991cec79127a7ef0 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Fri, 10 Jan 2020 13:18:11 -0500 Subject: [PATCH 99/99] bump version from rc --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 02e4207cd1..81e9583759 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ endif() set(VERSION_MAJOR 1) set(VERSION_MINOR 7) set(VERSION_PATCH 0) -set(VERSION_SUFFIX rc1) +#set(VERSION_SUFFIX rc1) if (VERSION_SUFFIX) set(VERSION_FULL "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}-${VERSION_SUFFIX}")