diff --git a/libraries/eosiolib/contracts/eosio/contract.hpp b/libraries/eosiolib/contracts/eosio/contract.hpp index d60d47241c..f0ef6e0568 100644 --- a/libraries/eosiolib/contracts/eosio/contract.hpp +++ b/libraries/eosiolib/contracts/eosio/contract.hpp @@ -29,6 +29,12 @@ namespace eosio { */ class contract { public: + enum class exec_type_t : uint8_t { + action, + call, + unknown + }; + /** * Construct a new contract given the contract name * @@ -75,7 +81,27 @@ class contract { */ inline const datastream& get_datastream()const { return _ds; } + /** + * Whether this contract is for a sync call + * + * @return bool - Whether this contract is for a sync call + */ + inline bool is_sync_call()const { + check(_exec_type != exec_type_t::unknown, "too early to call is_sync_call(). _exec_type has not been set yet"); + return (_exec_type == exec_type_t::call); + } + + /** + * Set the exectution type. + * + * @param type - The exectution type to be set. + */ + inline void set_exec_type(exec_type_t type) { + _exec_type = type; + } + protected: + /** * The name of the account this contract is deployed on. */ @@ -90,5 +116,10 @@ class contract { * The datastream for this contract */ datastream _ds = datastream(nullptr, 0); + + /** + * The execution type: action or sync call + */ + exec_type_t _exec_type = exec_type_t::unknown; }; } diff --git a/libraries/eosiolib/contracts/eosio/detail.hpp b/libraries/eosiolib/contracts/eosio/detail.hpp index 889bf496be..6a88af66b0 100644 --- a/libraries/eosiolib/contracts/eosio/detail.hpp +++ b/libraries/eosiolib/contracts/eosio/detail.hpp @@ -46,6 +46,11 @@ namespace eosio { namespace detail { template struct is_same { static constexpr bool value = std::is_integral::value; }; + // Full specialization to resolve ambiguity introduced by partial specializations + // of is_same and is_same + template <> + struct is_same { static constexpr bool value = true; }; + template struct get_nth_impl { static constexpr auto value = get_nth_impl::value; }; diff --git a/tests/integration/call_tests.cpp b/tests/integration/call_tests.cpp index be7562ed9c..feb102ef62 100644 --- a/tests/integration/call_tests.cpp +++ b/tests/integration/call_tests.cpp @@ -140,12 +140,11 @@ BOOST_AUTO_TEST_CASE(mixed_action_call_tags_test) { try { BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "hstmulprmtst"_n, "caller"_n, {})); // Make sure we can push an action using `sum`. - //BOOST_REQUIRE_NO_THROW(t.push_action("callee"_n, "sum"_n, "callee"_n, - t.push_action("callee"_n, "sum"_n, "callee"_n, + BOOST_REQUIRE_NO_THROW(t.push_action("callee"_n, "sum"_n, "callee"_n, mvo() ("a", 1) ("b", 2) - ("c", 3)); //); + ("c", 3))); } FC_LOG_AND_RETHROW() } // Verify the receiver contract with only one sync call function works @@ -234,4 +233,29 @@ BOOST_AUTO_TEST_CASE(addr_book_tests) { try { BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "get"_n, "caller"_n, mvo() ("user", "alice"))); } FC_LOG_AND_RETHROW() } +// For a function tagged as both `action` and `call`, verify is_sync_call() +// returns true if tagged as `call` and false if tagged as `action`. +BOOST_AUTO_TEST_CASE(is_sync_call_test) { try { + call_tester t({ + {"caller"_n, contracts::caller_wasm(), contracts::caller_abi().data()}, + {"callee"_n, contracts::callee_wasm(), contracts::callee_abi().data()} + }); + + // issynccall() is tagged as both `action` and `call`, and returns + // is_sync_call(). makesynccall() calls issynccall() as a sync call + // and returns its result. So the current action return value must be + // true. + auto trx_trace = t.push_action("caller"_n, "makesynccall"_n, "caller"_n, {}); + auto& action_trace = trx_trace->action_traces[0]; + bool return_value = fc::raw::unpack(action_trace.return_value); + BOOST_REQUIRE(return_value == true); + + // Call issynccall() directly as an action. Since issynccall() + // returns is_sync_call(), the current action return value must be false. + trx_trace = t.push_action("callee"_n, "issynccall"_n, "callee"_n, {}); + auto& action_trace1 = trx_trace->action_traces[0]; + return_value = fc::raw::unpack(action_trace1.return_value); + BOOST_REQUIRE(return_value == false); +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/toolchain/build-pass/separate_cpp_hpp.hpp b/tests/toolchain/build-pass/separate_cpp_hpp.hpp index 2002834021..d1685a2858 100644 --- a/tests/toolchain/build-pass/separate_cpp_hpp.hpp +++ b/tests/toolchain/build-pass/separate_cpp_hpp.hpp @@ -1,6 +1,6 @@ #include -class [[eosio::contract]] separate_cpp_hpp : eosio::contract { +class [[eosio::contract]] separate_cpp_hpp : public eosio::contract { public: using contract::contract; diff --git a/tests/unit/test_contracts/sync_call_callee.cpp b/tests/unit/test_contracts/sync_call_callee.cpp index 867f67dd33..883d8a4e72 100644 --- a/tests/unit/test_contracts/sync_call_callee.cpp +++ b/tests/unit/test_contracts/sync_call_callee.cpp @@ -31,3 +31,6 @@ struct1_t sync_call_callee::pass_multi_structs(struct1_t s1, int32_t m, struct2_ return { .a = s1.a * m + s2.c, .b = s1.b * m + s2.d }; } +bool sync_call_callee::issynccall() { + return is_sync_call(); +} diff --git a/tests/unit/test_contracts/sync_call_callee.hpp b/tests/unit/test_contracts/sync_call_callee.hpp index fc867da405..2f5d5d414b 100644 --- a/tests/unit/test_contracts/sync_call_callee.hpp +++ b/tests/unit/test_contracts/sync_call_callee.hpp @@ -38,6 +38,11 @@ class [[eosio::contract]] sync_call_callee : public eosio::contract{ [[eosio::call]] struct1_t pass_multi_structs(struct1_t s1, int32_t m, struct2_t s2); + // return is_sync_call() + [[eosio::action, eosio::call]] + bool issynccall(); + using issynccall_func = eosio::call_wrapper<"issynccall"_i, &sync_call_callee::issynccall>; + using return_ten_func = eosio::call_wrapper<"return_ten"_i, &sync_call_callee::return_ten>; using echo_input_func = eosio::call_wrapper<"echo_input"_i, &sync_call_callee::echo_input>; using void_func_func = eosio::call_wrapper<"void_func"_i, &sync_call_callee::void_func>; diff --git a/tests/unit/test_contracts/sync_call_caller.cpp b/tests/unit/test_contracts/sync_call_caller.cpp index ad7c8d2d33..a97a78b74a 100644 --- a/tests/unit/test_contracts/sync_call_caller.cpp +++ b/tests/unit/test_contracts/sync_call_caller.cpp @@ -172,4 +172,11 @@ class [[eosio::contract]] sync_call_caller : public eosio::contract{ status = eosio::call("callee"_n, 0, bad_version_data.data(), bad_version_data.size()); eosio::check(status == -10000, "call did not return -10000 for invalid version"); } + + // Call issynccall as a sync call and return its return value + [[eosio::action]] + bool makesynccall() { + sync_call_callee::issynccall_func is_sync_call_func{ "callee"_n }; + return is_sync_call_func(); + } }; diff --git a/tools/include/eosio/codegen.hpp b/tools/include/eosio/codegen.hpp index 44ed8e0a1f..123ea3c631 100644 --- a/tools/include/eosio/codegen.hpp +++ b/tools/include/eosio/codegen.hpp @@ -146,11 +146,28 @@ namespace eosio { namespace cdt { return ""; } + // Return `true` if the method `decl`'s base class if `eosio::contract` + bool base_is_eosio_contract_class(const clang::CXXMethodDecl* decl) { + auto cxx_decl = decl->getParent(); + // on this point it could be just an attribute so let's check base classes + for (const auto& base : cxx_decl->bases()) { + if (const clang::Type *base_type = base.getType().getTypePtrOrNull()) { + if (const auto* cur_cxx_decl = base_type->getAsCXXRecordDecl()) { + if (cur_cxx_decl->getQualifiedNameAsString() == "eosio::contract") { + return true;; + } + } + } + } + return false; + } + template void create_dispatch(const std::string& attr, const std::string& func_name, F&& get_str, CXXMethodDecl* decl) { constexpr static uint32_t max_stack_size = 512; codegen& cg = codegen::get(); std::string nm = decl->getNameAsString()+"_"+decl->getParent()->getNameAsString(); + if (cg.is_eosio_contract(decl, cg.contract_name)) { ss << "\n\n#include \n"; ss << "#include \n"; @@ -190,8 +207,22 @@ namespace eosio { namespace cdt { ss << tn << " arg" << i << "; ds >> arg" << i << ";\n"; i++; } + + // Create contract object + ss << decl->getParent()->getQualifiedNameAsString() + << " obj {eosio::name{r},eosio::name{c},ds};\n"; + + // Call `set_exec_type()` only for contracts dervied from `eosio::contract`. + // A contract class can have only `eosio::contract` attribute + // but does not inherit from the `eosio::contract` class; + // it may not have `set_exec_type()`. We need to make sure the class derives + // from `eosio::contract` before calling set_exec_type(). + if (base_is_eosio_contract_class(decl)) { + ss << "obj.set_exec_type(eosio::contract::exec_type_t::action);\n"; + } + const auto& call_action = [&]() { - ss << decl->getParent()->getQualifiedNameAsString() << "{eosio::name{r},eosio::name{c},ds}." << decl->getNameAsString() << "("; + ss << "obj." << decl->getNameAsString() << "("; for (int i=0; i < decl->parameters().size(); i++) { ss << "arg" << i; if (i < decl->parameters().size()-1) @@ -260,8 +291,22 @@ namespace eosio { namespace cdt { ss << tn << " arg" << i << "; ds >> arg" << i << ";\n"; i++; } + + // Create contract object + ss << decl->getParent()->getQualifiedNameAsString() + << " obj {eosio::name{receiver},eosio::name{receiver},ds};\n"; + + // Call `set_exec_type()` only for contracts dervied from `eosio::contract`. + // A contract class can have only `eosio::contract` attribute + // but does not inherit from the `eosio::contract` class; + // it may not have `set_exec_type()`. We need to make sure the class derives + // from `eosio::contract` before calling set_exec_type(). + if (base_is_eosio_contract_class(decl)) { + ss << "obj.set_exec_type(eosio::contract::exec_type_t::call);\n"; + } + const auto& call_function = [&]() { - ss << decl->getParent()->getQualifiedNameAsString() << "{eosio::name{receiver},eosio::name{receiver},ds}." << decl->getNameAsString() << "("; + ss << "obj." << decl->getNameAsString() << "("; for (int i=0; i < decl->parameters().size(); i++) { ss << "arg" << i; if (i < decl->parameters().size()-1)