Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b9f47e0
Add is_sync_call() to eosio::contract class
linh2931 Jul 18, 2025
09b234b
Update sync_call_addr_book_callee for new eosio::contract constructor
linh2931 Jul 18, 2025
b086797
Add tests for is_sync_call()
linh2931 Jul 18, 2025
db42ab4
Use set_exec_type() to explicitly set a contract's execution type (ac…
linh2931 Jul 21, 2025
bad7b81
Do not use unnecessary and expensivestd::optional for _exec_type
linh2931 Jul 21, 2025
a294a08
Make separate_cpp_hpp derive publicly from eosio::contract such that …
linh2931 Jul 21, 2025
a53fc34
Fix action_wrapper fails to compile if it contains bool as the first …
linh2931 Jul 21, 2025
00cb315
Allow a contract derives from eosio::contract privately
linh2931 Jul 22, 2025
425fc6c
Add a missing ()
linh2931 Jul 22, 2025
d607a92
Revert back the change for allowing private deriving from eosio::cont…
linh2931 Jul 22, 2025
62f04bd
Merge pull request #369 from AntelopeIO/is_sync_call
linh2931 Jul 22, 2025
4153ba4
Merge pull request #372 from AntelopeIO/fix_bool_in_action_wrapper
linh2931 Jul 23, 2025
d244ead
Merge remote-tracking branch 'origin/release/4.1' into merge_from_4_1
linh2931 Jul 23, 2025
a762ebd
Resolve merging conflicts
linh2931 Jul 23, 2025
8b76fa3
Merge pull request #373 from AntelopeIO/merge_from_4_1
linh2931 Jul 24, 2025
c853568
Update to use latest cdt-llvm (including latest cdt-clang and cdt-lld)
linh2931 Jul 25, 2025
c32f4e4
Merge pull request #377 from AntelopeIO/update_latest_llvm
linh2931 Jul 25, 2025
92945e4
Add tests for customized sync call entry function
linh2931 Jul 27, 2025
fef5731
Update to use cdt-llvm unhide_cust_entry branch for missing export of…
linh2931 Jul 27, 2025
36f2da8
Update to use main of cdt-llvm which includes cdt-lld which has custo…
linh2931 Jul 28, 2025
2ae2708
Merge pull request #379 from AntelopeIO/unhide_cust_entry
linh2931 Jul 28, 2025
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
2 changes: 1 addition & 1 deletion cdt-llvm
Submodule cdt-llvm updated 2 files
+1 −1 tools/clang
+1 −1 tools/lld
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
44 changes: 41 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,43 @@ 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() }

// Verify customized sync call entry function, without sync call tag
BOOST_AUTO_TEST_CASE(customized_call_entry_func_test1) { try {
call_tester t({{"receiver"_n, contracts::cust_entry_wasm(), contracts::cust_entry_abi().data()}});

BOOST_REQUIRE_NO_THROW(t.push_action("receiver"_n, "cusentrytst1"_n, "receiver"_n, {}));
} FC_LOG_AND_RETHROW() }

// Verify customized sync call entry function, with sync call tag
BOOST_AUTO_TEST_CASE(customized_call_entry_func_test2) { try {
call_tester t({{"receiver"_n, contracts::cust_entry_wasm(), contracts::cust_entry_abi().data()}});

BOOST_REQUIRE_NO_THROW(t.push_action("receiver"_n, "cusentrytst2"_n, "receiver"_n, {}));
} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_SUITE_END()
2 changes: 2 additions & 0 deletions tests/integration/contracts.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,7 @@ namespace eosio::testing {
static std::vector<char> addr_book_callee_abi() { return read_abi("${CMAKE_BINARY_DIR}/../unit/test_contracts/sync_call_addr_book_callee.abi"); }
static std::vector<uint8_t> addr_book_caller_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/../unit/test_contracts/sync_call_addr_book_caller.wasm"); }
static std::vector<char> addr_book_caller_abi() { return read_abi("${CMAKE_BINARY_DIR}/../unit/test_contracts/sync_call_addr_book_caller.abi"); }
static std::vector<uint8_t> cust_entry_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/../unit/test_contracts/sync_call_cust_entry.wasm"); }
static std::vector<char> cust_entry_abi() { return read_abi("${CMAKE_BINARY_DIR}/../unit/test_contracts/sync_call_cust_entry.abi"); }
};
} //ns eosio::testing
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
1 change: 1 addition & 0 deletions tests/unit/test_contracts/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ add_contract(sync_call_not_supported sync_call_not_supported sync_call_not_suppo
add_contract(sync_call_single_func sync_call_single_func sync_call_single_func.cpp)
add_contract(sync_call_addr_book_callee sync_call_addr_book_callee sync_call_addr_book_callee.cpp)
add_contract(sync_call_addr_book_caller sync_call_addr_book_caller sync_call_addr_book_caller.cpp)
add_contract(sync_call_cust_entry sync_call_cust_entry sync_call_cust_entry.cpp)
add_contract(capi_tests capi_tests capi/capi.c capi/action.c capi/chain.c capi/crypto.c capi/db.c capi/permission.c
capi/print.c capi/privileged.c capi/system.c capi/transaction.c capi/call.c)

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();
}
};
83 changes: 83 additions & 0 deletions tests/unit/test_contracts/sync_call_cust_entry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* This contract is used to verify customized sync_call entry function works.
* It verifies customized sync_call entry is exported, and `call()` host function
* can reach it.
*/

#include <eosio/eosio.hpp>
#include <eosio/call.hpp>

namespace eosio {

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

[[eosio::action]]
void cusentrytst1() {
// Make a sync call to "receiver"_n account, pass function ID in `data`,
// go to `sync_call()` entry function, and dispatch the call to `get_10()`
// based on the function ID.
const std::vector<char> data{ eosio::pack(1) }; // function ID 1
eosio::call("receiver"_n, 0, data.data(), data.size());

// Retrieve return value and unpack it
std::vector<char> return_value(sizeof(uint32_t));
eosio::get_call_return_value(return_value.data(), return_value.size());

// Verify it
eosio::check(eosio::unpack<uint32_t>(return_value) == 10, "return value is not 10");
}

[[eosio::action]]
void cusentrytst2() {
// Make a sync call to "receiver"_n account, pass function ID in `data`,
// go to `sync_call()` entry function, and dispatch the call to `get_20()`
// based on the function ID.
const std::vector<char> data{ eosio::pack(2) }; // function ID 2
eosio::call("receiver"_n, 0, data.data(), data.size());

// Retrieve return value and unpack it
std::vector<char> return_value(sizeof(uint32_t));
eosio::get_call_return_value(return_value.data(), return_value.size());

// Verify it
eosio::check(eosio::unpack<uint32_t>(return_value) == 20, "return value is not 20");
}

// `eosio::call` tag is optional
uint32_t get_10() {
return 10;
}

[[eosio::call]]
uint32_t get_20() {
return 20;
}
};
} /// namespace eosio

extern "C" {
[[eosio::wasm_entry]]
int64_t sync_call(uint64_t sender, uint64_t receiver, uint32_t data_size) {
eosio::datastream<const char*> ds(nullptr, 0); // for testing, not used
eosio::sync_call_cust_entry obj(eosio::name{receiver}, eosio::name{receiver}, ds);

std::vector<char> data(sizeof(uint32_t));
eosio::get_call_data(data.data(), data.size());
auto func_id = eosio::unpack<uint32_t>(data);

// Dispatch sync calls
uint32_t rv = 0;
switch (func_id) {
case 1: rv = obj.get_10(); break;
case 2: rv = obj.get_20(); break;
default: eosio::check(false, "wrong function ID");
}

// set return value
eosio::set_call_return_value(&rv, sizeof(uint32_t));

return 0; // return 0 to indicate success
}
}
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