Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions libraries/eosiolib/contracts/eosio/contract.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -75,7 +81,27 @@ class contract {
*/
inline const datastream<const char*>& 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.
*/
Expand All @@ -90,5 +116,10 @@ class contract {
* The datastream for this contract
*/
datastream<const char*> _ds = datastream<const char*>(nullptr, 0);

/**
* The execution type: action or sync call
*/
exec_type_t _exec_type = exec_type_t::unknown;
};
}
5 changes: 5 additions & 0 deletions libraries/eosiolib/contracts/eosio/detail.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ namespace eosio { namespace detail {
template <typename T>
struct is_same<T,bool> { static constexpr bool value = std::is_integral<T>::value; };

// Full specialization to resolve ambiguity introduced by partial specializations
// of is_same<bool,U> and is_same<T,bool>
template <>
struct is_same<bool, bool> { static constexpr bool value = true; };

template <size_t N, size_t I, auto Arg, auto... Args>
struct get_nth_impl { static constexpr auto value = get_nth_impl<N,I+1,Args...>::value; };

Expand Down
30 changes: 27 additions & 3 deletions tests/integration/call_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<bool>(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<bool>(action_trace1.return_value);
BOOST_REQUIRE(return_value == false);
} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_SUITE_END()
2 changes: 1 addition & 1 deletion tests/toolchain/build-pass/separate_cpp_hpp.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <eosio/eosio.hpp>

class [[eosio::contract]] separate_cpp_hpp : eosio::contract {
class [[eosio::contract]] separate_cpp_hpp : public eosio::contract {
public:
using contract::contract;

Expand Down
3 changes: 3 additions & 0 deletions tests/unit/test_contracts/sync_call_callee.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
5 changes: 5 additions & 0 deletions tests/unit/test_contracts/sync_call_callee.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down
7 changes: 7 additions & 0 deletions tests/unit/test_contracts/sync_call_caller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
};
49 changes: 47 additions & 2 deletions tools/include/eosio/codegen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename F>
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 <eosio/datastream.hpp>\n";
ss << "#include <eosio/name.hpp>\n";
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down