Merge pull request #351 from AntelopeIO/call_wrapper
SC: Implement call wrapper to simplify making sync calls
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"antelope-spring-dev":{
|
||||
"target":"new_entry_point_validation",
|
||||
"target":"return_status",
|
||||
"prerelease":false
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
Submodule cdt-llvm updated: 859a288611...ff3f15b471
@@ -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 {
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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"
|
||||
@@ -34,6 +35,10 @@ namespace eosio {
|
||||
* @note There are some methods from the @ref call that can be used directly from C++
|
||||
*/
|
||||
|
||||
inline int64_t call(eosio::name receiver, uint64_t flags, const char* data, size_t data_size) {
|
||||
return internal_use_do_not_use::call(receiver.value, flags, data, data_size);
|
||||
}
|
||||
|
||||
inline uint32_t get_call_return_value( void* mem, uint32_t len ) {
|
||||
return internal_use_do_not_use::get_call_return_value(mem, len);
|
||||
}
|
||||
@@ -46,65 +51,107 @@ namespace eosio {
|
||||
internal_use_do_not_use::set_call_return_value(mem, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the packed representation of a call
|
||||
*
|
||||
* @ingroup call
|
||||
*/
|
||||
struct call {
|
||||
/**
|
||||
* Name of the account the call is intended for
|
||||
*/
|
||||
const name receiver{};
|
||||
// Indicate whether a sync call is read_write or read_only. Default is read_write
|
||||
enum class access_mode { read_write = 0, read_only = 1 };
|
||||
|
||||
/**
|
||||
* indicating if the call is read only or not
|
||||
*/
|
||||
const bool read_only = false;
|
||||
// Indicate the action to take if the receiver does not support sync calls.
|
||||
// Default is abort_op
|
||||
enum class support_mode { abort_op = 0, no_op = 1 };
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Payload data
|
||||
*/
|
||||
const std::vector<char> data{};
|
||||
|
||||
/**
|
||||
* Construct a new call object with receiver, name, and payload data
|
||||
*
|
||||
* @tparam T - Type of call data, must be serializable by `pack(...)`
|
||||
* @param receiver - The name of the account this call is intended for
|
||||
* @param flags - The flags
|
||||
* @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 )
|
||||
: receiver(receiver)
|
||||
, read_only(read_only)
|
||||
, no_op_if_receiver_not_support_sync_call(no_op)
|
||||
, data(pack(std::forward<T>(payload))) {}
|
||||
|
||||
/// @cond INTERNAL
|
||||
EOSLIB_SERIALIZE( call, (receiver)(read_only)(no_op_if_receiver_not_support_sync_call)(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
|
||||
auto retval = internal_use_do_not_use::call(receiver.value, flags, data.data(), data.size());
|
||||
|
||||
if (retval == -2) { // 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");
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
// For a void function, when support_mode is set to no_op, the call_wrapper.
|
||||
// returns `std::optional<void_call>`. If the optional has no value, it indicates
|
||||
// the call was op-op; if the optional has a value of `void_call`, the call
|
||||
// executed successfully.
|
||||
struct void_call {
|
||||
};
|
||||
|
||||
struct call_data_header {
|
||||
uint32_t version = 0;
|
||||
uint64_t func_name = 0; // At WASM level, function name is an uint64_t. We do not use eosio::name here to make the decoding function name simpler in sync_call entry point function.
|
||||
|
||||
EOSLIB_SERIALIZE(call_data_header, (version)(func_name))
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper for simplifying making a sync call
|
||||
*
|
||||
* @brief Used to wrap 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, access_mode Access_Mode=access_mode::read_write, support_mode Support_Mode = support_mode::abort_op>
|
||||
struct call_wrapper {
|
||||
template <typename Receiver>
|
||||
constexpr call_wrapper(Receiver&& receiver)
|
||||
: receiver(std::forward<Receiver>(receiver))
|
||||
{}
|
||||
|
||||
static constexpr eosio::name function_name = eosio::name(Func_Name);
|
||||
eosio::name receiver {};
|
||||
|
||||
using orig_ret_type = typename detail::function_traits<decltype(Func_Ref)>::return_type;
|
||||
|
||||
using return_type = std::conditional_t<
|
||||
Support_Mode == support_mode::abort_op,// if Support_Mode is abort_op
|
||||
orig_ret_type, // use the original return type
|
||||
std::conditional_t< // else
|
||||
std::is_void<orig_ret_type>::value, // original return type is void
|
||||
std::optional<void_call>, // use optional of empty struct
|
||||
std::optional<orig_ret_type> // use optional of original return type
|
||||
>
|
||||
>;
|
||||
|
||||
template <typename... Args>
|
||||
return_type operator()(Args&&... args)const {
|
||||
static_assert(detail::type_check<Func_Ref, Args...>());
|
||||
|
||||
uint64_t flags = 0x00;
|
||||
if constexpr (Access_Mode == access_mode::read_only) {
|
||||
flags = 0x01;
|
||||
}
|
||||
|
||||
call_data_header header{ .version = 0,
|
||||
.func_name = function_name.value };
|
||||
|
||||
const std::vector<char> data{ pack(std::forward_as_tuple(header, detail::deduced<Func_Ref>{std::forward<Args>(args)...})) };
|
||||
|
||||
auto ret_val_size = internal_use_do_not_use::call(receiver.value, flags, data.data(), data.size());
|
||||
|
||||
if (ret_val_size < 0) { // the receiver does not support sync calls
|
||||
if constexpr (Support_Mode == support_mode::abort_op) {
|
||||
check(false, "receiver does not support sync call but support_mode is set to abort_op");
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// The sync call has been executed by the receiver
|
||||
if constexpr (std::is_void<return_type>::value) {
|
||||
return;
|
||||
} else {
|
||||
if constexpr (Support_Mode == support_mode::no_op && std::is_void<orig_ret_type>::value) {
|
||||
return void_call{};
|
||||
} else {
|
||||
constexpr size_t max_stack_buffer_size = 512;
|
||||
char* buffer = (char*)(max_stack_buffer_size < ret_val_size ? malloc(ret_val_size) : alloca(ret_val_size)); // intentionally no `free()` is called. the memory will be freed at the end of callers wasm execution.
|
||||
internal_use_do_not_use::get_call_return_value(buffer, ret_val_size);
|
||||
orig_ret_type ret_val = unpack<orig_ret_type>(buffer, ret_val_size);
|
||||
|
||||
if constexpr (Support_Mode == support_mode::no_op) {
|
||||
return std::make_optional(ret_val);
|
||||
} else {
|
||||
return ret_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace eosio
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
// For non-function-pointers (function_traits is undefined)
|
||||
template <typename T>
|
||||
struct function_traits;
|
||||
|
||||
// For non-const member function
|
||||
template <typename Class, typename Ret, typename... Args>
|
||||
struct function_traits<Ret (Class::*)(Args...)> {
|
||||
using return_type = Ret;
|
||||
};
|
||||
|
||||
// For const member function
|
||||
template <typename Class, typename Ret, typename... Args>
|
||||
struct function_traits<Ret (Class::*)(Args...) const> {
|
||||
using return_type = Ret;
|
||||
};
|
||||
/// @endcond
|
||||
}} // eosio detail
|
||||
@@ -41,7 +41,11 @@ BOOST_AUTO_TEST_CASE(return_value_test) { try {
|
||||
{"callee"_n, contracts::callee_wasm(), contracts::callee_abi().data()}
|
||||
});
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "retvaltest"_n, "caller"_n, {}));
|
||||
// Using host function directly
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "hstretvaltst"_n, "caller"_n, {}));
|
||||
|
||||
// Using call_wrapper
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "wrpretvaltst"_n, "caller"_n, {}));
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
// Verify one parameter passing works correctly
|
||||
@@ -51,7 +55,11 @@ BOOST_AUTO_TEST_CASE(param_basic_test) { try {
|
||||
{"callee"_n, contracts::callee_wasm(), contracts::callee_abi().data()}
|
||||
});
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "paramtest"_n, "caller"_n, {}));
|
||||
// Using host function directly
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "hstoneprmtst"_n, "caller"_n, {}));
|
||||
|
||||
// Using call_wrapper
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "wrponeprmtst"_n, "caller"_n, {}));
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
// Verify multiple parameters passing works correctly
|
||||
@@ -61,7 +69,31 @@ BOOST_AUTO_TEST_CASE(multiple_params_test) { try {
|
||||
{"callee"_n, contracts::callee_wasm(), contracts::callee_abi().data()}
|
||||
});
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "mulparamtest"_n, "caller"_n, {}));
|
||||
// Using host function directly
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "hstmulprmtst"_n, "caller"_n, {}));
|
||||
|
||||
// Using call_wrapper
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "wrpmulprmtst"_n, "caller"_n, {}));
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
// Verify passing a struct parameter works correctly
|
||||
BOOST_AUTO_TEST_CASE(struct_param_test) { try {
|
||||
call_tester t({
|
||||
{"caller"_n, contracts::caller_wasm(), contracts::caller_abi().data()},
|
||||
{"callee"_n, contracts::callee_wasm(), contracts::callee_abi().data()}
|
||||
});
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "structtest"_n, "caller"_n, {}));
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
// Verify passing a mix of structs and integer works correctly
|
||||
BOOST_AUTO_TEST_CASE(mix_struct_int_params_test) { try {
|
||||
call_tester t({
|
||||
{"caller"_n, contracts::caller_wasm(), contracts::caller_abi().data()},
|
||||
{"callee"_n, contracts::callee_wasm(), contracts::callee_abi().data()}
|
||||
});
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "structinttst"_n, "caller"_n, {}));
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
// Verify a sync call to a void function works properly.
|
||||
@@ -71,18 +103,27 @@ BOOST_AUTO_TEST_CASE(void_func_test) { try {
|
||||
{"callee"_n, contracts::callee_wasm(), contracts::callee_abi().data()}
|
||||
});
|
||||
|
||||
auto trx_trace = t.push_action("caller"_n, "voidfunctest"_n, "caller"_n, {});
|
||||
auto& atrace = trx_trace->action_traces;
|
||||
auto check = [] (const transaction_trace_ptr& trx_trace) {
|
||||
auto& atrace = trx_trace->action_traces;
|
||||
|
||||
auto& call_traces = atrace[0].call_traces;
|
||||
BOOST_REQUIRE_EQUAL(call_traces.size(), 1u);
|
||||
auto& call_traces = atrace[0].call_traces;
|
||||
BOOST_REQUIRE_EQUAL(call_traces.size(), 1u);
|
||||
|
||||
// Verify the print from the void function is correct.
|
||||
// The test contract checks the return value size is 0.
|
||||
auto& call_trace = call_traces[0];
|
||||
BOOST_REQUIRE_EQUAL(call_trace.call_ordinal, 1u);
|
||||
BOOST_REQUIRE_EQUAL(call_trace.sender_ordinal, 0u);
|
||||
BOOST_REQUIRE_EQUAL(call_trace.console, "I am a void function");
|
||||
// Verify the print from the void function is correct.
|
||||
// The test contract checks the return value size is 0.
|
||||
auto& call_trace = call_traces[0];
|
||||
BOOST_REQUIRE_EQUAL(call_trace.call_ordinal, 1u);
|
||||
BOOST_REQUIRE_EQUAL(call_trace.sender_ordinal, 0u);
|
||||
BOOST_REQUIRE_EQUAL(call_trace.console, "I am a void function");
|
||||
};
|
||||
|
||||
// Using host function directly
|
||||
auto trx_trace = t.push_action("caller"_n, "hstvodfuntst"_n, "caller"_n, {});
|
||||
check(trx_trace);
|
||||
|
||||
// Using call_wrapper
|
||||
trx_trace = t.push_action("caller"_n, "wrpvodfuntst"_n, "caller"_n, {});
|
||||
check(trx_trace);
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
// Verify a function tagged as both `action` and `call` works
|
||||
@@ -96,14 +137,14 @@ BOOST_AUTO_TEST_CASE(mixed_action_call_tags_test) { try {
|
||||
|
||||
// Make sure we can make a sync call to `sum` (`mulparamtest` in `caller` does
|
||||
// a sync call to `sum`)
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "mulparamtest"_n, "caller"_n, {}));
|
||||
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,
|
||||
mvo()
|
||||
("in1", 1)
|
||||
("in2", 2)
|
||||
("in3", 3)));
|
||||
("a", 1)
|
||||
("b", 2)
|
||||
("c", 3)));
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
// Verify the receiver contract with only one sync call function works
|
||||
@@ -116,31 +157,80 @@ BOOST_AUTO_TEST_CASE(single_function_test) { try {
|
||||
|
||||
// The single_func_wasm contains only one function and the caller contract
|
||||
// hooks up with it
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "retvaltest"_n, "caller"_n, {}));
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "hstretvaltst"_n, "caller"_n, {}));
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
// Verify no_op_if_receiver_not_support_sync_call flag works
|
||||
BOOST_AUTO_TEST_CASE(sync_call_not_supported_test) { try {
|
||||
// Verify support_mode for void and non-void sync calls if calls are a failure
|
||||
BOOST_AUTO_TEST_CASE(sync_call_support_mode_failure_test) { try {
|
||||
call_tester t({
|
||||
{"caller"_n, contracts::not_supported_wasm(), contracts::not_supported_abi().data()}
|
||||
{"caller"_n, contracts::caller_wasm(), contracts::caller_abi().data()},
|
||||
{"callee"_n, contracts::not_supported_wasm(), contracts::not_supported_abi().data()}
|
||||
});
|
||||
|
||||
// * sync_call_not_supported contract only has actions
|
||||
// * no_op_if_receiver_not_support_sync_call is set
|
||||
// so the call is just a no-op
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "noopset"_n, "caller"_n, {}));
|
||||
// voidfncnoop uses support_mode::no_op
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "voidfncnoop"_n, "caller"_n, {}));
|
||||
|
||||
// voidfncabort uses default support_mode::abort
|
||||
BOOST_CHECK_EXCEPTION(t.push_action("caller"_n, "voidfncabort"_n, "caller"_n, {}),
|
||||
eosio_assert_message_exception,
|
||||
fc_exception_message_contains("receiver does not support sync call but support_mode is set to abort"));
|
||||
|
||||
// intfuncnoop uses support_mode::no_op
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "intfuncnoop"_n, "caller"_n, {}));
|
||||
|
||||
// intfuncabort uses default support_mode::abort
|
||||
BOOST_CHECK_EXCEPTION(t.push_action("caller"_n, "intfuncabort"_n, "caller"_n, {}),
|
||||
eosio_assert_message_exception,
|
||||
fc_exception_message_contains("receiver does not support sync call but support_mode is set to abort"));
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
// Verify calling an unknown function will result in an eosio_assert
|
||||
// Verify support_mode for void and non-void sync calls if call is successful
|
||||
BOOST_AUTO_TEST_CASE(sync_call_support_mode_success_test) { try {
|
||||
call_tester t({
|
||||
{"caller"_n, contracts::caller_wasm(), contracts::caller_abi().data()},
|
||||
{"callee"_n, contracts::callee_wasm(), contracts::callee_abi().data()}
|
||||
});
|
||||
|
||||
// voidnoopsucc uses support_mode::no_op
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "voidnoopsucc"_n, "caller"_n, {}));
|
||||
|
||||
// sumnoopsucc uses support_mode::no_op
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "sumnoopsucc"_n, "caller"_n, {}));
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
// Verify header validation
|
||||
BOOST_AUTO_TEST_CASE(unknown_function_test) { try {
|
||||
call_tester t({
|
||||
{"caller"_n, contracts::caller_wasm(), contracts::caller_abi().data()},
|
||||
{"callee"_n, contracts::callee_wasm(), contracts::callee_abi().data()}
|
||||
});
|
||||
|
||||
BOOST_CHECK_EXCEPTION(t.push_action("caller"_n, "unknwnfuntst"_n, "caller"_n, {}),
|
||||
eosio_assert_code_exception,
|
||||
eosio_assert_code_is(8000000000000000003));
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "hdrvaltest"_n, "caller"_n, {}));
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
// Verify adding/reading entries to/from a table, and read-only enforcement work
|
||||
BOOST_AUTO_TEST_CASE(addr_book_tests) { try {
|
||||
call_tester t({
|
||||
{"caller"_n, contracts::addr_book_caller_wasm(), contracts::addr_book_caller_abi().data()},
|
||||
{"callee"_n, contracts::addr_book_callee_wasm(), contracts::addr_book_callee_abi().data()}
|
||||
});
|
||||
|
||||
// Try to add an entry using a read-only sync call
|
||||
BOOST_CHECK_EXCEPTION(t.push_action("caller"_n, "upsertrdonly"_n, "caller"_n, mvo()
|
||||
("user", "alice")
|
||||
("first_name", "alice")
|
||||
("street", "123 Main St.")),
|
||||
unaccessible_api,
|
||||
fc_exception_message_contains("this API is not allowed in read only action/call"));
|
||||
|
||||
// Add an entry using a read-write sync call
|
||||
t.push_action("caller"_n, "upsert"_n, "caller"_n, mvo()
|
||||
("user", "alice")
|
||||
("first_name", "alice")
|
||||
("street", "123 Main St."));
|
||||
|
||||
// Read the inserted entry. "get"_n action will check the return value from the sync call
|
||||
BOOST_REQUIRE_NO_THROW(t.push_action("caller"_n, "get"_n, "caller"_n, mvo() ("user", "alice")));
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
@@ -48,5 +48,9 @@ namespace eosio::testing {
|
||||
static std::vector<char> not_supported_abi() { return read_abi("${CMAKE_BINARY_DIR}/../unit/test_contracts/sync_call_not_supported.abi"); }
|
||||
static std::vector<uint8_t> single_func_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/../unit/test_contracts/sync_call_single_func.wasm"); }
|
||||
static std::vector<char> single_func_abi() { return read_abi("${CMAKE_BINARY_DIR}/../unit/test_contracts/sync_call_single_func.abi"); }
|
||||
static std::vector<uint8_t> addr_book_callee_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/../unit/test_contracts/sync_call_addr_book_callee.wasm"); }
|
||||
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"); }
|
||||
};
|
||||
} //ns eosio::testing
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#include <eosio/call.hpp>
|
||||
#include <eosio/eosio.hpp>
|
||||
|
||||
// Test the validation of the number of arguments passed in call_wrapper.
|
||||
// Expected error:
|
||||
// .../build/bin/../include/eosiolib/contracts/eosio/detail.hpp:72:7: error: static_assert failed due to requirement 'sizeof...(Ts) == std::tuple_size<std::__1::tuple<unsigned int, unsigned int, unsigned int>>::value'
|
||||
// static_assert(sizeof...(Ts) == std::tuple_size<deduced<Function>>::value);
|
||||
|
||||
class [[eosio::contract]] sync_call_invalid_arg_nums : public eosio::contract{
|
||||
public:
|
||||
using contract::contract;
|
||||
|
||||
[[eosio::call]]
|
||||
uint32_t sum(uint32_t a, uint32_t b, uint32_t c) {
|
||||
return a + b + c;
|
||||
}
|
||||
|
||||
using sum_func = eosio::call_wrapper<"sum"_n, &sync_call_invalid_arg_nums::sum>;
|
||||
|
||||
// Fewer number of arguments
|
||||
[[eosio::action]]
|
||||
void fewerargs() {
|
||||
sum_func{"callee"_n}(1, 2);
|
||||
}
|
||||
|
||||
// More number of arguments
|
||||
[[eosio::action]]
|
||||
void moreargs() {
|
||||
sum_func{"callee"_n}(1, 2, 3, 4);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"tests" : [
|
||||
{
|
||||
"compile_flags": [],
|
||||
"expected" : {
|
||||
"exit-code": 255,
|
||||
"stderr": "error: static_assert failed due to requirement 'sizeof"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#include <eosio/call.hpp>
|
||||
#include <eosio/eosio.hpp>
|
||||
|
||||
// Test the validation of the types of arguments passed in call_wrapper.
|
||||
// Expected error:
|
||||
// build/bin/../include/eosiolib/contracts/eosio/detail.hpp:60:7: error: static_assert failed due to requirement 'detail::is_same<sync_call_invalid_arg_nums::empty &, unsigned int>::value'
|
||||
// static_assert(detail::is_same<typename convert<T>::type, typename convert<typename std::tuple_element<I, deduced<Function>>::type>::type>::value);
|
||||
class [[eosio::contract]] sync_call_invalid_arg_nums : public eosio::contract{
|
||||
public:
|
||||
using contract::contract;
|
||||
|
||||
[[eosio::call]]
|
||||
uint32_t sum(uint32_t a, uint32_t b, uint32_t c) {
|
||||
return a + b + c;
|
||||
}
|
||||
|
||||
using sum_func = eosio::call_wrapper<"sum"_n, &sync_call_invalid_arg_nums::sum>;
|
||||
|
||||
struct empty {
|
||||
};
|
||||
|
||||
// Invalid first argument
|
||||
[[eosio::action]]
|
||||
void wrongrettype() {
|
||||
empty bad;
|
||||
sum_func{"callee"_n}(bad, 2, 3);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"tests" : [
|
||||
{
|
||||
"compile_flags": [],
|
||||
"expected" : {
|
||||
"exit-code": 255,
|
||||
"stderr": "error: static_assert failed due to requirement 'detail::is_same"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -17,6 +17,8 @@ add_contract(sync_call_caller sync_call_caller sync_call_caller.cpp)
|
||||
add_contract(sync_call_callee sync_call_callee sync_call_callee.cpp)
|
||||
add_contract(sync_call_not_supported sync_call_not_supported sync_call_not_supported.cpp)
|
||||
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(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)
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "sync_call_addr_book_callee.hpp"
|
||||
#include <eosio/eosio.hpp>
|
||||
|
||||
using namespace eosio;
|
||||
|
||||
void sync_call_addr_book_callee::upsert(name user, std::string first_name, std::string street) {
|
||||
// Intentionally leave out require_auth(user) to test upsert cannot be called as a read_only
|
||||
// sync call
|
||||
|
||||
address_index addresses(get_first_receiver(), get_first_receiver().value);
|
||||
auto iterator = addresses.find(user.value);
|
||||
if( iterator == addresses.end() )
|
||||
{
|
||||
addresses.emplace(user, [&]( auto& row ) {
|
||||
row.key = user;
|
||||
row.first_name = first_name;
|
||||
row.street = street;
|
||||
});
|
||||
}
|
||||
else {
|
||||
addresses.modify(iterator, user, [&]( auto& row ) {
|
||||
row.key = user;
|
||||
row.first_name = first_name;
|
||||
row.street = street;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
person_info sync_call_addr_book_callee::get(name user) {
|
||||
address_index addresses(get_first_receiver(), get_first_receiver().value);
|
||||
|
||||
auto iterator = addresses.find(user.value);
|
||||
check(iterator != addresses.end(), "Record does not exist");
|
||||
|
||||
return person_info{ .first_name = iterator->first_name,
|
||||
.street = iterator->street };
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <eosio/call.hpp>
|
||||
#include <eosio/eosio.hpp>
|
||||
|
||||
struct person_info {
|
||||
std::string first_name;
|
||||
std::string street;
|
||||
};
|
||||
|
||||
class [[eosio::contract]] sync_call_addr_book_callee : public eosio::contract {
|
||||
public:
|
||||
sync_call_addr_book_callee(eosio::name receiver, eosio::name code, eosio::datastream<const char*> ds): contract(receiver, code, ds) {}
|
||||
|
||||
[[eosio::call]]
|
||||
void upsert(eosio::name user, std::string first_name, std::string street);
|
||||
|
||||
[[eosio::call]]
|
||||
person_info get(eosio::name user);
|
||||
|
||||
using upsert_read_only_func = eosio::call_wrapper<"upsert"_n, &sync_call_addr_book_callee::upsert, eosio::access_mode::read_only>;
|
||||
using upsert_func = eosio::call_wrapper<"upsert"_n, &sync_call_addr_book_callee::upsert>;
|
||||
using get_func = eosio::call_wrapper<"get"_n, &sync_call_addr_book_callee::get>;
|
||||
|
||||
private:
|
||||
struct [[eosio::table]] person {
|
||||
eosio::name key;
|
||||
std::string first_name;
|
||||
std::string street;
|
||||
|
||||
uint64_t primary_key() const { return key.value; }
|
||||
};
|
||||
|
||||
using address_index = eosio::multi_index<"people"_n, person>;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "sync_call_addr_book_callee.hpp"
|
||||
|
||||
#include <eosio/eosio.hpp>
|
||||
#include <eosio/call.hpp>
|
||||
|
||||
class [[eosio::contract]] sync_call_addr_book_caller : public eosio::contract{
|
||||
public:
|
||||
using contract::contract;
|
||||
|
||||
// Insert an entry using read_only, which will fail
|
||||
[[eosio::action]]
|
||||
void upsertrdonly(eosio::name user, std::string first_name, std::string street) {
|
||||
sync_call_addr_book_callee::upsert_read_only_func{"callee"_n}(user, first_name, street);
|
||||
}
|
||||
|
||||
// Insert an entry
|
||||
[[eosio::action]]
|
||||
void upsert(eosio::name user, std::string first_name, std::string street) {
|
||||
sync_call_addr_book_callee::upsert_func{"callee"_n}(user, first_name, street);
|
||||
}
|
||||
|
||||
// Read an entry
|
||||
[[eosio::action]]
|
||||
person_info get(eosio::name user) {
|
||||
auto user_info = sync_call_addr_book_callee::get_func{"callee"_n}(user);
|
||||
eosio::check(user_info.first_name == "alice", "first name not alice");
|
||||
eosio::check(user_info.street == "123 Main St.", "street not 123 Main St.");
|
||||
return user_info;
|
||||
}
|
||||
};
|
||||
@@ -1,28 +1,33 @@
|
||||
#include <eosio/call.hpp>
|
||||
#include "sync_call_callee.hpp"
|
||||
#include <eosio/print.hpp>
|
||||
#include <eosio/eosio.hpp>
|
||||
|
||||
class [[eosio::contract]] sync_call_callee : public eosio::contract{
|
||||
public:
|
||||
using contract::contract;
|
||||
[[eosio::call]]
|
||||
uint32_t sync_call_callee::getten() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
[[eosio::call]]
|
||||
uint32_t getten() {
|
||||
return 10;
|
||||
}
|
||||
[[eosio::call]]
|
||||
uint32_t sync_call_callee::getback(uint32_t in) {
|
||||
return in;
|
||||
}
|
||||
|
||||
[[eosio::call]]
|
||||
uint32_t getback(uint32_t in) {
|
||||
return in;
|
||||
}
|
||||
[[eosio::call]]
|
||||
void sync_call_callee::voidfunc() {
|
||||
eosio::print("I am a void function");
|
||||
}
|
||||
|
||||
[[eosio::call]]
|
||||
void voidfunc() {
|
||||
eosio::print("I am a void function");
|
||||
}
|
||||
[[eosio::action, eosio::call]]
|
||||
uint32_t sync_call_callee::sum(uint32_t a, uint32_t b, uint32_t c) {
|
||||
return a + b + c;
|
||||
}
|
||||
|
||||
[[eosio::call]]
|
||||
struct1_t sync_call_callee::structonly(struct1_t s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
[[eosio::call]]
|
||||
struct1_t sync_call_callee::structmix(struct1_t s1, int32_t m, struct2_t s2) {
|
||||
return { .a = s1.a * m + s2.c, .b = s1.b * m + s2.d };
|
||||
}
|
||||
|
||||
[[eosio::action, eosio::call]]
|
||||
uint32_t sum(uint32_t in1, uint32_t in2, uint32_t in3) {
|
||||
return in1 + in2 + in3;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#include <eosio/call.hpp>
|
||||
#include <eosio/eosio.hpp>
|
||||
|
||||
struct struct1_t {
|
||||
int64_t a;
|
||||
uint64_t b;
|
||||
};
|
||||
|
||||
struct struct2_t {
|
||||
char a;
|
||||
bool b;
|
||||
int64_t c;
|
||||
uint64_t d;
|
||||
};
|
||||
|
||||
class [[eosio::contract]] sync_call_callee : public eosio::contract{
|
||||
public:
|
||||
using contract::contract;
|
||||
|
||||
[[eosio::call]]
|
||||
uint32_t getten();
|
||||
|
||||
[[eosio::call]]
|
||||
uint32_t getback(uint32_t in);
|
||||
|
||||
[[eosio::call]]
|
||||
void voidfunc();
|
||||
|
||||
[[eosio::action, eosio::call]]
|
||||
uint32_t sum(uint32_t a, uint32_t b, uint32_t c);
|
||||
|
||||
// pass in a struct and return it
|
||||
[[eosio::call]]
|
||||
struct1_t structonly(struct1_t s);
|
||||
|
||||
// pass in two structs and an integer, multiply each field in the struct by
|
||||
// the integer, add last two fields of the second struct, and return the result
|
||||
[[eosio::call]]
|
||||
struct1_t structmix(struct1_t s1, int32_t m, struct2_t s2);
|
||||
|
||||
using getten_func = eosio::call_wrapper<"getten"_n, &sync_call_callee::getten>;
|
||||
using getback_func = eosio::call_wrapper<"getback"_n, &sync_call_callee::getback>;
|
||||
using voidfunc_func = eosio::call_wrapper<"voidfunc"_n, &sync_call_callee::voidfunc>;
|
||||
using sum_func = eosio::call_wrapper<"sum"_n, &sync_call_callee::sum>;
|
||||
using structonly_func = eosio::call_wrapper<"structonly"_n, &sync_call_callee::structonly>;
|
||||
using structmix_func = eosio::call_wrapper<"structmix"_n, &sync_call_callee::structmix>;
|
||||
|
||||
using void_no_op_success_func = eosio::call_wrapper<"voidfunc"_n, &sync_call_callee::voidfunc, eosio::access_mode::read_write, eosio::support_mode::no_op>;
|
||||
using sum_no_op_success_func = eosio::call_wrapper<"sum"_n, &sync_call_callee::sum, eosio::access_mode::read_write, eosio::support_mode::no_op>;
|
||||
};
|
||||
@@ -1,13 +1,21 @@
|
||||
#include "sync_call_callee.hpp"
|
||||
#include "sync_call_not_supported.hpp"
|
||||
|
||||
#include <eosio/eosio.hpp>
|
||||
#include <eosio/call.hpp>
|
||||
|
||||
using namespace eosio;
|
||||
|
||||
class [[eosio::contract]] sync_call_caller : public eosio::contract{
|
||||
public:
|
||||
using contract::contract;
|
||||
|
||||
// Using host function directly
|
||||
[[eosio::action]]
|
||||
void retvaltest() {
|
||||
auto expected_size = eosio::call("callee"_n, "getten"_n)();
|
||||
void hstretvaltst() {
|
||||
call_data_header header{ .version = 0, .func_name = "getten"_n.value };
|
||||
const std::vector<char> data{ eosio::pack(header) };
|
||||
auto expected_size = eosio::call("callee"_n, 0, data.data(), data.size());
|
||||
eosio::check(expected_size >= 0, "call did not return a positive value");
|
||||
|
||||
std::vector<char> return_value;
|
||||
@@ -17,10 +25,20 @@ public:
|
||||
eosio::check(eosio::unpack<uint32_t>(return_value) == 10u, "return value not 10"); // getten always returns 10
|
||||
}
|
||||
|
||||
// Using call_wrapper
|
||||
[[eosio::action]]
|
||||
void paramtest() {
|
||||
void wrpretvaltst() {
|
||||
sync_call_callee::getten_func getten{ "callee"_n };
|
||||
eosio::check(getten() == 10u, "return value not 10");
|
||||
}
|
||||
|
||||
// Using host function directly, testing one parameter passing
|
||||
[[eosio::action]]
|
||||
void hstoneprmtst() {
|
||||
// `getback(uint32_t p)` returns p
|
||||
auto expected_size = eosio::call("callee"_n, std::make_tuple("getback"_n, 5))();
|
||||
call_data_header header{ .version = 0, .func_name = "getback"_n.value };
|
||||
const std::vector<char> data{ eosio::pack(std::make_tuple(header, 5)) };
|
||||
auto expected_size = eosio::call("callee"_n, 0, data.data(), data.size());
|
||||
eosio::check(expected_size >= 0, "call did not return a positive value");
|
||||
|
||||
std::vector<char> return_value;
|
||||
@@ -30,26 +48,128 @@ public:
|
||||
eosio::check(eosio::unpack<uint32_t>(return_value) == 5u, "return value not 5"); // getback returns back the same value of parameter
|
||||
}
|
||||
|
||||
// Using call_wrapper, testing one parameter passing
|
||||
[[eosio::action]]
|
||||
void mulparamtest() {
|
||||
auto expected_size = eosio::call("callee"_n, std::make_tuple("sum"_n, 10, 20, 30))();
|
||||
void wrponeprmtst() {
|
||||
sync_call_callee::getback_func getback{ "callee"_n };
|
||||
eosio::check(getback(5) == 5u, "return value not 5");
|
||||
}
|
||||
|
||||
// Using host function directly, testing multiple parameters passing
|
||||
[[eosio::action]]
|
||||
void hstmulprmtst() {
|
||||
call_data_header header{ .version = 0, .func_name = "sum"_n.value };
|
||||
const std::vector<char> data{ eosio::pack(std::make_tuple(header, 10, 20, 30)) };
|
||||
auto expected_size = eosio::call("callee"_n, 0, data.data(), data.size());
|
||||
eosio::check(expected_size >= 0, "call did not return a positive value");
|
||||
|
||||
std::vector<char> return_value;
|
||||
return_value.resize(expected_size);
|
||||
auto actual_size = eosio::get_call_return_value(return_value.data(), return_value.size());
|
||||
eosio::check(actual_size == expected_size, "actual_size not equal to expected_size");
|
||||
eosio::check(eosio::unpack<uint32_t>(return_value) == 60u, "return value not 60"); // sum returns the sum of the 3 arguments
|
||||
eosio::check(eosio::unpack<uint32_t>(return_value) == 60u, "sum of 10, 20, and 30 not 60"); // sum returns the sum of the 3 arguments
|
||||
}
|
||||
|
||||
// Using call_wrapper, testing multiple parameters passing
|
||||
[[eosio::action]]
|
||||
void wrpmulprmtst() {
|
||||
sync_call_callee::sum_func sum{ "callee"_n };
|
||||
eosio::check(sum(10, 20, 30) == 60u, "sum of 10, 20, and 30 not 60");
|
||||
}
|
||||
|
||||
// Verify single struct parameter passing
|
||||
[[eosio::action]]
|
||||
void structtest() {
|
||||
sync_call_callee::structonly_func func{ "callee"_n };
|
||||
struct1_t input = { 10, 20 };
|
||||
auto output = func(input); // structonly_func returns the input as is
|
||||
eosio::check(output.a == input.a, "field a in output is not equal to a in input");
|
||||
eosio::check(output.b == input.b, "field b in output is not equal to b in input");
|
||||
}
|
||||
|
||||
// Verify mix of struct and integer parameters passing
|
||||
[[eosio::action]]
|
||||
void structinttst() {
|
||||
sync_call_callee::structmix_func func{ "callee"_n };
|
||||
struct1_t input1 = { 10, 20 };
|
||||
struct2_t input2 = { 'a', true, 50, 100 };
|
||||
int32_t m = 2;
|
||||
|
||||
// structmix_func multiply each field of input1 by m,
|
||||
// add last two fields of input2, and return a struct1_t
|
||||
auto output = func(input1, m, input2);
|
||||
eosio::check(output.a == m * input1.a + input2.c, "field a of output is not correct");
|
||||
eosio::check(output.b == m * input1.b + input2.d, "field b of output is not correct");
|
||||
}
|
||||
|
||||
[[eosio::action]]
|
||||
void voidfunctest() {
|
||||
auto expected_size = eosio::call("callee"_n, "voidfunc"_n)();
|
||||
void hstvodfuntst() {
|
||||
call_data_header header{ .version = 0, .func_name = "voidfunc"_n.value };
|
||||
const std::vector<char> data{ eosio::pack(header) };
|
||||
auto expected_size = eosio::call("callee"_n, 0, data.data(), data.size());
|
||||
eosio::check(expected_size == 0, "call did not return 0"); // void function. return value size should be 0
|
||||
}
|
||||
|
||||
[[eosio::action]]
|
||||
void unknwnfuntst() {
|
||||
eosio::call("callee"_n, "unknwnfunc"_n)(); // unknwnfunc will never be in "callee"_n contract
|
||||
void wrpvodfuntst() {
|
||||
sync_call_callee::voidfunc_func voidfunc{ "callee"_n };
|
||||
voidfunc();
|
||||
}
|
||||
|
||||
// Verify void call. void_func uses default support_mode::abort
|
||||
[[eosio::action]]
|
||||
void voidfncabort() {
|
||||
sync_call_not_supported::void_func void_func_abort{ "callee"_n };
|
||||
void_func_abort(); // Will throw. Tester will verify that.
|
||||
}
|
||||
|
||||
// void_func uses support_mode::no_op
|
||||
[[eosio::action]]
|
||||
void voidfncnoop() {
|
||||
sync_call_not_supported::void_no_op_func void_func_no_op{ "callee"_n };
|
||||
check(void_func_no_op() == std::nullopt, "void_func_no_op did not return std::nullopt");
|
||||
}
|
||||
|
||||
// verify non-void call. int_func uses default support_mode::abort
|
||||
[[eosio::action]]
|
||||
void intfuncabort() {
|
||||
sync_call_not_supported::int_func int_func_abort{ "callee"_n };
|
||||
int_func_abort(); // Will throw. Tester will verify that.
|
||||
}
|
||||
|
||||
// int_func uses support_mode::no_opabort
|
||||
[[eosio::action]]
|
||||
void intfuncnoop() {
|
||||
sync_call_not_supported::int_no_op_func int_func_no_op{ "callee"_n };
|
||||
check(int_func_no_op() == std::nullopt, "void_func_no_op did not return std::nullopt");
|
||||
}
|
||||
|
||||
// void_no_op_success_func uses support_mode::no_op
|
||||
[[eosio::action]]
|
||||
void voidnoopsucc() {
|
||||
sync_call_callee::void_no_op_success_func f{ "callee"_n };
|
||||
check(f().has_value(), "void_no_op_success_func did not return a value");
|
||||
}
|
||||
|
||||
// void_no_op_success_func uses support_mode::no_op
|
||||
[[eosio::action]]
|
||||
void sumnoopsucc() {
|
||||
sync_call_callee::sum_no_op_success_func f{ "callee"_n };
|
||||
check(*f(7, 8, 9) == 24, "sum_no_op_success_func did not return a value");
|
||||
}
|
||||
|
||||
[[eosio::action]]
|
||||
void hdrvaltest() {
|
||||
// Verify function name validation works
|
||||
call_data_header unkwn_func_header{ .version = 0, .func_name = "unknwnfunc"_n.value };
|
||||
const std::vector<char> unkwn_func_data{ eosio::pack(unkwn_func_header) }; // unknwnfunc is not in "callee"_n contract
|
||||
auto status = eosio::call("callee"_n, 0, unkwn_func_data.data(), unkwn_func_data.size());
|
||||
eosio::check(status == -10001, "call did not return -10001 for unknown function");
|
||||
|
||||
// Verify version validation works
|
||||
call_data_header bad_version_header{ .version = 1, .func_name = "sum"_n.value }; // version 1 is not supported
|
||||
const std::vector<char> bad_version_data{ eosio::pack(bad_version_header) };
|
||||
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");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,34 +1,10 @@
|
||||
#include <eosio/eosio.hpp>
|
||||
#include <eosio/call.hpp>
|
||||
#include "sync_call_not_supported.hpp"
|
||||
|
||||
class [[eosio::contract]] sync_call_not_supported : public eosio::contract{
|
||||
public:
|
||||
using contract::contract;
|
||||
[[eosio::action]]
|
||||
void sync_call_not_supported::voidfunc() {
|
||||
}
|
||||
|
||||
// * sync call is not supported as no method is taged by `call`
|
||||
// * no_op_if_receiver_no_support_sync_call is set
|
||||
[[eosio::action]]
|
||||
void noopset() {
|
||||
std::vector<char> data{};
|
||||
|
||||
// For now, because sync_call entry point has not been implemented yet and
|
||||
// no_op_if_receiver_no_support_sync_call is set to true, call should return -1
|
||||
auto rc = eosio::call("caller"_n, data, false /* read_only */, true /* no_op_if_receiver_no_support_sync_call */)();
|
||||
eosio::check(rc == -1, "call did not return -1");
|
||||
|
||||
// call was not executed. return value size should be 0
|
||||
std::vector<char> value(10);
|
||||
auto size = eosio::get_call_return_value(value.data(), value.size());
|
||||
eosio::check(size == 0, "return value size is not 0");
|
||||
}
|
||||
|
||||
// sync call not supported, no_op_if_receiver_no_support_sync_call not set
|
||||
[[eosio::action]]
|
||||
void noopnotset() {
|
||||
std::vector<char> data{};
|
||||
|
||||
// For now, because sync_call entry point has not been implemented yet and
|
||||
// no_op_if_receiver_no_support_sync_call is not set, call should fail
|
||||
eosio::call("caller"_n, data)();
|
||||
}
|
||||
};
|
||||
[[eosio::action]]
|
||||
int sync_call_not_supported::intfunc() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#include <eosio/eosio.hpp>
|
||||
#include <eosio/call.hpp>
|
||||
|
||||
// Because this contract does not tag any functions by `call` attribute,
|
||||
// `sync_call` entry point is not generated.
|
||||
// Any sync calls to this contract will return a status indicating
|
||||
// sync calls are not supported by the receiver.
|
||||
class [[eosio::contract]] sync_call_not_supported : public eosio::contract{
|
||||
public:
|
||||
using contract::contract;
|
||||
|
||||
[[eosio::action]]
|
||||
void voidfunc();
|
||||
|
||||
[[eosio::action]]
|
||||
int intfunc();
|
||||
|
||||
using void_func = eosio::call_wrapper<"voidfunc"_n, &sync_call_not_supported::voidfunc>; // default behavior: abort when called
|
||||
using void_no_op_func = eosio::call_wrapper<"voidfunc"_n, &sync_call_not_supported::voidfunc, eosio::access_mode::read_write, eosio::support_mode::no_op>; // no op when called
|
||||
|
||||
using int_func = eosio::call_wrapper<"intfunc"_n, &sync_call_not_supported::intfunc>; // default behavior: abort when called
|
||||
using int_no_op_func = eosio::call_wrapper<"intfunc"_n, &sync_call_not_supported::intfunc, eosio::access_mode::read_write, eosio::support_mode::no_op>; // no op when called
|
||||
};
|
||||
@@ -232,7 +232,7 @@ namespace eosio { namespace cdt {
|
||||
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";
|
||||
ss << "#include <eosio/call.hpp>\n";
|
||||
ss << "extern \"C\" {\n";
|
||||
const auto& return_ty = decl->getReturnType().getAsString();
|
||||
if (return_ty != "void") {
|
||||
@@ -245,7 +245,7 @@ namespace eosio { namespace cdt {
|
||||
ss << func_name << nm;
|
||||
ss << "\"))) void " << func_name << nm << "(unsigned long long sender, unsigned long long receiver, size_t data_size, void* data) {\n";
|
||||
ss << "eosio::datastream<const char*> ds{(char*)data, data_size};\n";
|
||||
ss << "unsigned long long func_name; ds >> func_name;\n"; // skip called function name
|
||||
ss << "eosio::call_data_header header; ds >> header;\n"; // skip header
|
||||
int i=0;
|
||||
for (auto param : decl->parameters()) {
|
||||
clang::LangOptions lang_opts;
|
||||
@@ -275,32 +275,38 @@ namespace eosio { namespace cdt {
|
||||
call_function();
|
||||
if (return_ty != "void") {
|
||||
ss << "const auto& packed_result = eosio::pack(result);\n";
|
||||
ss << "set_call_return_value((void*)packed_result.data(), packed_result.size());\n";
|
||||
ss << "::set_call_return_value((void*)packed_result.data(), packed_result.size());\n";
|
||||
}
|
||||
ss << "}}\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Generate get_sync_call_func_name which returns called function name
|
||||
static void create_get_sync_call_func_name(std::stringstream& ss) {
|
||||
// Generate get_sync_call_data_version which returns the version of call data.
|
||||
// In version 0, call data is packed as header + arguments, where
|
||||
// header is `struct header { uint32_t version; uint64_t func_name }`
|
||||
static void create_get_sync_call_data_header(std::stringstream& ss) {
|
||||
ss << "\n\n#include <eosio/datastream.hpp>\n";
|
||||
ss << "#include <eosio/name.hpp>\n";
|
||||
ss << "#include <eosio/call.hpp>\n";
|
||||
ss << "extern \"C\" {\n";
|
||||
ss << "__attribute__((weak)) unsigned long long __eos_get_sync_call_func_name_(void* data) {\n";
|
||||
ss << "eosio::datastream<const char*> ds{(char*)data, sizeof(unsigned long long)};\n";
|
||||
ss << "unsigned long long func_name; ds >> func_name;\n";
|
||||
ss << "return func_name;\n";
|
||||
ss << "__attribute__((weak)) void* __eos_get_sync_call_data_header_(void* data) {\n";
|
||||
ss << "size_t size = sizeof(eosio::call_data_header);\n";
|
||||
ss << "eosio::datastream<const char*> ds{(char*)data, size};\n";
|
||||
ss << "eosio::call_data_header header; ds >> header;\n";
|
||||
ss << "void* ptr = malloc(size);\n";
|
||||
ss << "memcpy(ptr, &header, size);\n";
|
||||
ss << "return ptr;\n";
|
||||
ss << "}}\n";
|
||||
}
|
||||
|
||||
// Generate get_sync_call_data which returns call data
|
||||
// Generate get_sync_call_data which returns call data which consists of
|
||||
// header and arguments
|
||||
static void create_get_sync_call_data(std::stringstream& ss) {
|
||||
ss << "\n\n#include <eosio/datastream.hpp>\n";
|
||||
ss << "#include <eosio/name.hpp>\n";
|
||||
ss << "extern \"C\" {\n";
|
||||
ss << "__attribute__((eosio_wasm_import)) uint32_t get_call_data(void*, uint32_t);\n";
|
||||
ss << "__attribute__((weak)) void* __eos_get_sync_call_data_(unsigned long size) {\n";
|
||||
ss << "void* data = malloc(size);\n";
|
||||
ss << "void* data = malloc(size);\n"; // store data in linear memory
|
||||
ss << "::get_call_data(data, size);\n";
|
||||
ss << "return data;\n";
|
||||
ss << "}}\n";
|
||||
@@ -366,10 +372,10 @@ namespace eosio { namespace cdt {
|
||||
CDT_ERROR("codegen_error", decl->getLocation(), std::string("call name (")+s+") is not a valid eosio name");
|
||||
});
|
||||
|
||||
// Genereate create_get_sync_call_data and get_sync_call_func_name only once
|
||||
// Genereate create_get_sync_call_data and create_get_sync_call_data_header only once
|
||||
if (_call_set.empty()) {
|
||||
create_get_sync_call_data(ss);
|
||||
create_get_sync_call_func_name(ss);
|
||||
create_get_sync_call_data_header(ss);
|
||||
}
|
||||
|
||||
if (!_call_set.count(name))
|
||||
|
||||
Reference in New Issue
Block a user