This pull request introduces a new class within our tests: SDKTestSuite. This class seamlessly manages blockchain components (DB, State, P2P, rdPoS, State, Options) and offers a straightforward approach for performing transactions, creating contracts, and calling contract functions/events. Below is an example that demonstrates deploying an ERC20 contract and executing the transfer function, showcasing a significant ease of use compared to our previous environment.
For most cases, using SDKTestSuite("folderPath") suffices. However, it is possible to supply a list of TestAccount and Options to initialize the blockchain, where accounts in the list will receive 1000000000000000000000 (wei) in native tokens. A newly constructed TestSuite will always have a clear DB/chain.
SDKTestSuite includes a default TestAccount, named chainOwnerAccount_, which is equivalent to the chain owner for testnet defaults (0x00dead00665771855a34155f5e7405489df2c3c6). This account is used for all functions requiring a transaction signature, unless another TestAccount is specified in the function argument.
SDKTestSuite::advanceChain() is the primary function for progressing the chain. It generates a new valid block (adhering to rdPoS rules), validates it, and adds it to the blockchain. If an automatically generated block is invalid, it will throw an exception; this is unlikely but serves as a safety measure.
Template function declarations for contract interactions are as follows:
/**
* Create a transaction to deploy a new contract and advance the chain with it.
* Always use the chain owner account to deploy contracts.s
* @tparam TContract Contract type to deploy.
* @tparam Args... Arguments to pass to the contract constructor.
* @return Address of the deployed contract.
*/
template <typename TContract, typename ...Args>
const Address deployContract(Args&&... args);
/**
* Create a transaction to call a contract function and advance the chain with it.
* Specialization for function with args
* We are not able to set default values like in the other specialization because of the variadic template.
* Therefore we need functions with no value/testAccount/timestamp parameters.
* @tparam R Return type of the function.
* @tparam TContract Contract type to call.
* @tparam Args... Arguments to pass to the function.
* @param contractAddress Address of the contract to call.
* @param value Value to send with the transaction.
* @param testAccount Account to send the transaction from.
* @param timestamp Timestamp to use for the transaction in microseconds.
* @param func Function to call.
* @param args Arguments to pass to the function.
*/
template <typename R, typename TContract, typename ...Args>
const Hash callFunction(const Address& contractAddress,
const uint256_t& value,
const TestAccount& testAccount,
const uint64_t& timestamp,
R(TContract::*func)(const Args&...),
const Args&... args)
/**
* Call a contract view function with args and return the result.
* @tparam R Return type of the function.
* @tparam TContract Contract type to call.
* @tparam Args... Arguments to pass to the function.
* @param contractAddress Address of the contract to call.
* @param func Function to call.
* @param args Arguments to pass to the function.
* @return The result of the function call. (R)
*/
template <typename R, typename TContract, typename ...Args>
const R callViewFunction(const Address& contractAddress,
R(TContract::*func)(const Args&...) const,
const Args&... args)
Please refer to the implementation for a comprehensive understanding of all overloaded types and function implementations. For instance, callFunction has 9 different implementations to cover scenarios where the value, testAccount, and timestamp arguments are omitted, defaulting them to 0, the chain owner TestAccount, and std::chrono::high_resolution_clock::now(), respectively.
To retrieve specific events emitted by a transaction, developers should use the following function:
/**
* Retrieve specific events emitted by a confirmed transaction.
* @tparam TContract Contract type.
* @tparam Args... Arguments for the EventParam.
* @tparam Flags... Flags for the EventParam.
* @param txHash Transaction hash to search for events.
* @param func Function to look for.
* @param anonymous (optional) Specify if the event is anonymous.
*/
template <typename TContract, typename... Args, bool... Flags>
std::vector<Event> getEventsEmittedByTx(const Hash& txHash,
void(TContract::*func)(const EventParam<Args, Flags>&...),
const std::tuple<EventParam<Args, Flags>...>& args,
bool anonymous = false)
This can be invoked as follows:
auto events = sdkTestSuite.getEventsEmittedByTx(changeNameAndValueTx, &SimpleContract::nameChanged);
auto filteredEvents = sdkTestSuite.getEventsEmittedByTx(changeNameAndValueTx, &SimpleContract::nameChanged, std::make_tuple(EventParam<std::string, true>("Hello World 2!")));
For a detailed understanding, please consult the implementation for all declarations.
Points for Discussion:
Explicit Type Requirements: As seen in the examples, functions like deployContract and callFunction require explicit type declarations, backed by a static_assert that compares the caller-provided types with the function pointer. Is there a possibility to refine the static_assert to accommodate implicitly convertible types?
Event Topic Syntax: The current syntax used for specifying event topics appears to be awkward. Are there ways to streamline this?
Function Pointer in Template Parameters: Can we avoid using the function pointer on function arguments and instead incorporate it into the template parameters of the functions?
TODO:
Update all existing tests that depend on manual setup to utilize SDKTestSuite.
Develop a getEventsEmittedByAddress feature to identify events emitted by a specific contract.
Alter getEvents related functions, like getEventsEmittedByTx to return a std::tuple where Args... represents the non-indexed arguments of the Event. This change aims to follow a format similar to:
/**
* Proposed template to return a tuple of event arguments.
*/
template <typename TContract, typename... Args, bool... Flags>
std::tuple<Args...> getEventsEmittedByTx(const Hash& txHash,
void(TContract::*func)(const EventParam<Args, Flags>&...),
bool anonymous = false)
{
/// How we can derive the tuple from the function pointer?
/// Remember that we need to ignore the indexed arguments/not return them.
}
Enable the operation of multiple instances of SDKTestSuite to simulate an authentic network environment.
SDK Test Suite Implementation
This pull request introduces a new class within our tests: SDKTestSuite. This class seamlessly manages blockchain components (DB, State, P2P, rdPoS, State, Options) and offers a straightforward approach for performing transactions, creating contracts, and calling contract functions/events. Below is an example that demonstrates deploying an ERC20 contract and executing the transfer function, showcasing a significant ease of use compared to our previous environment.
Implementation Details:
SDKTestSuite has a single constructor, declared as follows:
For most cases, using SDKTestSuite("folderPath") suffices. However, it is possible to supply a list of TestAccount and Options to initialize the blockchain, where accounts in the list will receive 1000000000000000000000 (wei) in native tokens. A newly constructed TestSuite will always have a clear DB/chain.
The TestAccount struct is defined as:
SDKTestSuite includes a default TestAccount, named chainOwnerAccount_, which is equivalent to the chain owner for testnet defaults (0x00dead00665771855a34155f5e7405489df2c3c6). This account is used for all functions requiring a transaction signature, unless another TestAccount is specified in the function argument.
SDKTestSuite::advanceChain() is the primary function for progressing the chain. It generates a new valid block (adhering to rdPoS rules), validates it, and adds it to the blockchain. If an automatically generated block is invalid, it will throw an exception; this is unlikely but serves as a safety measure.
Template function declarations for contract interactions are as follows:
The usage can be exemplified as follows:
Please refer to the implementation for a comprehensive understanding of all overloaded types and function implementations. For instance, callFunction has 9 different implementations to cover scenarios where the value, testAccount, and timestamp arguments are omitted, defaulting them to 0, the chain owner TestAccount, and std::chrono::high_resolution_clock::now(), respectively.
To retrieve specific events emitted by a transaction, developers should use the following function:
This can be invoked as follows:
For a detailed understanding, please consult the implementation for all declarations.
Points for Discussion:
TODO: