Skip to content

SC: Implement call wrapper to simplify making sync calls #351

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 30 commits into
base: sync_call_entry_func
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d3cac6a
Implement call wrapper to simplify making sync calls
linh2931 May 12, 2025
1c9cd27
tests/integration/call_tests.cpp
linh2931 May 12, 2025
f45496e
Use execution_mode and on_call_not_supported_mode enums instead of bo…
linh2931 May 12, 2025
7454073
Change parameter names of test function sum
linh2931 May 12, 2025
67b646c
Add const back to members
linh2931 May 12, 2025
de050b2
Automatically derive return type of sync call functions in wrapper
linh2931 May 13, 2025
676233a
Use set_call_return_value from C linkage
linh2931 May 13, 2025
b358459
Add tests for updating and reading from tables using sync calls and r…
linh2931 May 13, 2025
755ddc5
Refactor call_warpper
linh2931 May 14, 2025
797d5aa
Merge branch 'sync_call_entry_func' into call_wrapper
linh2931 May 14, 2025
e798246
Implement data header and validate it
linh2931 May 16, 2025
ca6f0a1
Bump llvm submodule to call_wrapper
linh2931 May 16, 2025
7cab68c
Bump cdt-llvm
linh2931 May 16, 2025
de416fd
Point antelope-spring-dev to return_status branch temporarily
linh2931 May 16, 2025
79d466e
Add type checks for arguments
linh2931 May 17, 2025
4848fb2
Rename execution_mode to access_mode, and on_call_not_supported_mode …
linh2931 May 20, 2025
8af4875
Return std::optional for support_mode::no_op calls; add comprehensive…
linh2931 May 20, 2025
58bd4c0
Add toolchain tests for the validation of arguments types and numbers…
linh2931 May 21, 2025
d7fa594
Bring llvm to latest
linh2931 May 22, 2025
aea6188
Bump cdt-llvm version to pick up entry point return status constants
linh2931 May 23, 2025
061efae
Update unknown function and invalid header tests to accommodate new e…
linh2931 May 23, 2025
aa16dcd
Use enum class instead of plain enum to define enums
linh2931 May 23, 2025
090c99f
Use std::forward_as_tuple instead of std::make_tuple to avoid making …
linh2931 May 23, 2025
0acd966
Clarify the comment about why free() is not called
linh2931 May 23, 2025
91b4b00
Use orig_ret_type explicitly to make sure return value optimization i…
linh2931 May 23, 2025
d51ad33
Use eosio::name instead of uint64_t for receiver in host function cal…
linh2931 May 23, 2025
a3ad5f6
Change spring-dev branch back to sync_call from the temporary return_…
linh2931 May 23, 2025
e1227b7
Add a comment why function name type in call_data_header is uint64_t,…
linh2931 May 23, 2025
6abb8db
Update cdt-llvm to pickup generate sync call entry point function onl…
linh2931 May 26, 2025
bc5974f
Add tests for complex parameter passing (a mix of structs and integer)
linh2931 May 31, 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
80 changes: 1 addition & 79 deletions libraries/eosiolib/contracts/eosio/action.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
#include <cstdlib>
#include <type_traits>

#include "detail.hpp"
#include "../../core/eosio/serialize.hpp"
#include "../../core/eosio/datastream.hpp"
#include "../../core/eosio/name.hpp"
#include "../../core/eosio/fixed_bytes.hpp"
#include "../../core/eosio/ignore.hpp"
#include "../../core/eosio/time.hpp"

namespace eosio {
Expand Down Expand Up @@ -409,84 +409,6 @@ namespace eosio {

};



namespace detail {

/// @cond INTERNAL

template <typename T>
struct unwrap { typedef T type; };

template <typename T>
struct unwrap<ignore<T>> { typedef T type; };

template <typename R, typename Act, typename... Args>
auto get_args(R(Act::*p)(Args...)) {
return std::tuple<std::decay_t<typename unwrap<Args>::type>...>{};
}

template <typename R, typename Act, typename... Args>
auto get_args_nounwrap(R(Act::*p)(Args...)) {
return std::tuple<std::decay_t<Args>...>{};
}

template <auto Action>
using deduced = decltype(get_args(Action));

template <auto Action>
using deduced_nounwrap = decltype(get_args_nounwrap(Action));

template <typename T>
struct convert { typedef T type; };

template <>
struct convert<const char*> { typedef std::string type; };

template <>
struct convert<char*> { typedef std::string type; };

template <typename T, typename U>
struct is_same { static constexpr bool value = std::is_convertible<T,U>::value; };

template <typename U>
struct is_same<bool,U> { static constexpr bool value = std::is_integral<U>::value; };

template <typename T>
struct is_same<T,bool> { static constexpr bool value = std::is_integral<T>::value; };

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; };

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

template <size_t N, auto... Args>
struct get_nth { static constexpr auto value = get_nth_impl<N,0,Args...>::value; };

template <auto Action, size_t I, typename T, typename... Rest>
struct check_types {
static_assert(detail::is_same<typename convert<T>::type, typename convert<typename std::tuple_element<I, deduced<Action>>::type>::type>::value);
using type = check_types<Action, I+1, Rest...>;
static constexpr bool value = true;
};
template <auto Action, size_t I, typename T>
struct check_types<Action, I, T> {
static_assert(detail::is_same<typename convert<T>::type, typename convert<typename std::tuple_element<I, deduced<Action>>::type>::type>::value);
static constexpr bool value = true;
};

template <auto Action, typename... Ts>
constexpr bool type_check() {
static_assert(sizeof...(Ts) == std::tuple_size<deduced<Action>>::value);
if constexpr (sizeof...(Ts) != 0)
return check_types<Action, 0, Ts...>::value;
return true;
}

/// @endcond
}

/**
* Wrapper for an action object.
*
Expand Down
80 changes: 69 additions & 11 deletions libraries/eosiolib/contracts/eosio/call.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <cstdlib>
#include <type_traits>

#include "detail.hpp"
#include "../../core/eosio/serialize.hpp"
#include "../../core/eosio/datastream.hpp"
#include "../../core/eosio/name.hpp"
Expand Down Expand Up @@ -46,6 +47,13 @@ namespace eosio {
internal_use_do_not_use::set_call_return_value(mem, len);
}

// Request a sync call is read_write or read_only. Default is read_write
enum execution_mode { read_write = 0, read_only = 1 };

// Behaviour of a sync call if the receiver does not support sync calls
// Default is abort
enum on_call_not_supported_mode { abort = 0, no_op = 1 };

/**
* This is the packed representation of a call
*
Expand All @@ -55,24 +63,24 @@ namespace eosio {
/**
* Name of the account the call is intended for
*/
const name receiver{};
name receiver{};

/**
* indicating if the call is read only or not
*/
const bool read_only = false;
execution_mode exec_mode = execution_mode::read_write;

/**
* if the receiver contract does not have sync_call entry point or its signature
* is invalid, when no_op_if_receiver_not_support_sync_call is set to true,
* is invalid, when on_call_not_supported_mode is set to no_op,
* the sync call is no op, otherwise the call is aborted and an exception is raised.
*/
const bool no_op_if_receiver_not_support_sync_call = false;
on_call_not_supported_mode not_supported_mode = on_call_not_supported_mode::abort;

/**
* Payload data
*/
const std::vector<char> data{};
std::vector<char> data{};

/**
* Construct a new call object with receiver, name, and payload data
Expand All @@ -83,28 +91,78 @@ namespace eosio {
* @param payload - The call data that will be serialized via pack into data
*/
template<typename T>
call( struct name receiver, T&& payload, bool read_only = false, bool no_op = false )
call( struct name receiver, T&& payload, execution_mode exec_mode = execution_mode::read_write, on_call_not_supported_mode not_supported_mode = on_call_not_supported_mode::abort)
: receiver(receiver)
, read_only(read_only)
, no_op_if_receiver_not_support_sync_call(no_op)
, exec_mode(exec_mode)
, not_supported_mode(not_supported_mode)
, data(pack(std::forward<T>(payload))) {}

/// @cond INTERNAL
EOSLIB_SERIALIZE( call, (receiver)(read_only)(no_op_if_receiver_not_support_sync_call)(data) )
EOSLIB_SERIALIZE( call, (receiver)(exec_mode)(not_supported_mode)(data) )
/// @endcond

/**
* Make a call using the functor operator
*/
int64_t operator()() const {
uint64_t flags = read_only ? 0x01 : 0x00; // last bit indicating read only
uint64_t flags = (exec_mode == execution_mode::read_only) ? 0x01 : 0x00; // last bit indicating read only
auto retval = internal_use_do_not_use::call(receiver.value, flags, data.data(), data.size());

if (retval == -1) { // sync call is not supported by the receiver contract
check(no_op_if_receiver_not_support_sync_call, "receiver does not support sync call but no_op_if_receiver_not_support_sync_call flag is not set");
check(not_supported_mode == on_call_not_supported_mode::no_op, "receiver does not support sync call but on_call_not_supported_mode is set to abort");
}
return retval;
}
};

/**
* Wrapper for a call object.
*
* @brief Used to wrap an a particular sync call to simplify the process of other contracts making sync calls to the "wrapped" call.
* Example:
* @code
* // defined by contract writer of the sync call functions
* using get_func = call_wrapper<"get"_n, &callee::get, uint32_t>;
* // usage by different contract writer
* get_func{"callee"_n}();
* // or
* get_func get{"callee"_n};
* get();
* @endcode
*/
template <eosio::name::raw Func_Name, auto Func_Ref, typename Return_Type=void>
struct call_wrapper {
template <typename Receiver>
constexpr call_wrapper(Receiver&& receiver, execution_mode exec_mode = execution_mode::read_write, on_call_not_supported_mode not_supported_mode = on_call_not_supported_mode::abort)
: receiver(std::forward<Receiver>(receiver))
, exec_mode(exec_mode)
, not_supported_mode(not_supported_mode)
{}

static constexpr eosio::name func_name = eosio::name(Func_Name);
eosio::name receiver {};
execution_mode exec_mode = execution_mode::read_write;
on_call_not_supported_mode not_supported_mode = on_call_not_supported_mode::abort;

template <typename... Args>
call to_call(Args&&... args)const {
static_assert(detail::type_check<Func_Ref, Args...>());
return call(receiver, std::make_tuple(func_name, detail::deduced<Func_Ref>{std::forward<Args>(args)...}), exec_mode, not_supported_mode);
}

template <typename... Args>
Return_Type operator()(Args&&... args)const {
auto size = to_call(std::forward<Args>(args)...)();

if constexpr (std::is_void<Return_Type>::value) {
return;
} else {
constexpr size_t max_stack_buffer_size = 512;
char* buffer = (char*)(max_stack_buffer_size < size ? malloc(size) : alloca(size));
internal_use_do_not_use::get_call_return_value(buffer, size);
return unpack<Return_Type>(buffer, size);
}
}

};
} // namespace eosio
79 changes: 79 additions & 0 deletions libraries/eosiolib/contracts/eosio/detail.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#pragma once

#include "../../core/eosio/ignore.hpp"

namespace eosio { namespace detail {

/// @cond INTERNAL

template <typename T>
struct unwrap { typedef T type; };

template <typename T>
struct unwrap<ignore<T>> { typedef T type; };

template <typename R, typename Act, typename... Args>
auto get_args(R(Act::*p)(Args...)) {
return std::tuple<std::decay_t<typename unwrap<Args>::type>...>{};
}

template <typename R, typename Act, typename... Args>
auto get_args_nounwrap(R(Act::*p)(Args...)) {
return std::tuple<std::decay_t<Args>...>{};
}

template <auto Function>
using deduced = decltype(get_args(Function));

template <auto Function>
using deduced_nounwrap = decltype(get_args_nounwrap(Function));

template <typename T>
struct convert { typedef T type; };

template <>
struct convert<const char*> { typedef std::string type; };

template <>
struct convert<char*> { typedef std::string type; };

template <typename T, typename U>
struct is_same { static constexpr bool value = std::is_convertible<T,U>::value; };

template <typename U>
struct is_same<bool,U> { static constexpr bool value = std::is_integral<U>::value; };

template <typename T>
struct is_same<T,bool> { static constexpr bool value = std::is_integral<T>::value; };

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; };

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

template <size_t N, auto... Args>
struct get_nth { static constexpr auto value = get_nth_impl<N,0,Args...>::value; };

template <auto Function, size_t I, typename T, typename... Rest>
struct check_types {
static_assert(detail::is_same<typename convert<T>::type, typename convert<typename std::tuple_element<I, deduced<Function>>::type>::type>::value);
using type = check_types<Function, I+1, Rest...>;
static constexpr bool value = true;
};
template <auto Function, size_t I, typename T>
struct check_types<Function, I, T> {
static_assert(detail::is_same<typename convert<T>::type, typename convert<typename std::tuple_element<I, deduced<Function>>::type>::type>::value);
static constexpr bool value = true;
};

template <auto Function, typename... Ts>
constexpr bool type_check() {
static_assert(sizeof...(Ts) == std::tuple_size<deduced<Function>>::value);
if constexpr (sizeof...(Ts) != 0)
return check_types<Function, 0, Ts...>::value;
return true;
}

/// @endcond
}} // eosio detail
Loading
Loading