To define a simple singleton, which is storing an account name as primary value and a uint64_t as secondary value in structure testtable
, follow the steps below:
- Include the
eosio.hpp
andsingleton.hpp
headers and declare theeosio
namespace usage
#include <eosio/eosio.hpp>
#include <eosio/singleton.hpp>
using namespace eosio;
- Define the data structure for the multi index table
struct [[eosio::table]] testtable {
name primary_value;
uint64_t secondary_value;
};
- For ease of use, define a type alias
singleton_type
based on theeosio::singleton
template type, parametarized with a random name"testtable"
, which has to respect the EOSIO account name restrictions, and thetesttable
data structure defined above
struct [[eosio::table]] testtable {
name primary_value;
uint64_t secondary_value;
};
+using singleton_type = eosio::singleton<"testtable"_n, testtable>;
- Define the singleton table instance declared as a data member of type
singleton_type
defined in the privious step
struct [[eosio::table]] testtable {
name primary_value;
uint64_t secondary_value;
};
using singleton_type = eosio::singleton<"testtable"_n, testtable>;
+singleton_type singleton_instance;
- Instantiate the data member
singleton_instance
by passing to its constructor thereceiver
and thecode
(in this casereceiver.value
) parameters; these two combined with "testtable" provide access to the partition of the RAM cache used by this singleton. In this example you will initialize thesingleton_instance
data member in the smart contract constructor, see below:
// singleton contract constructor
singleton_example( name receiver, name code, datastream<const char*> ds ) :
contract(receiver, code, ds),
+ singleton_instance(receiver, receiver.value)
{ }
}
Now you have defined and instantiated a singleton. Below you can find a possible implementation for the full class singleton example contract.
singleton_example.hpp
#include <eosio/eosio.hpp>
#include <eosio/singleton.hpp>
using namespace eosio;
class [[eosio::contract]] singleton_example : public contract {
public:
using contract::contract;
singleton_example( name receiver, name code, datastream<const char*> ds ) :
contract(receiver, code, ds),
singleton_instance(receiver, receiver.value)
{}
[[eosio::action]]
void set( name user, uint64_t value );
[[eosio::action]]
void get( );
struct [[eosio::table]] testtable {
name primary_value;
uint64_t secondary_value;
uint64_t primary_key() const { return primary_value.value; }
} testtablerow;
using singleton_type = eosio::singleton<"testtable"_n, testtable>;
singleton_type singleton_instance;
using set_action = action_wrapper<"set"_n, &singleton_example::set>;
using get_action = action_wrapper<"get"_n, &singleton_example::get>;
};
And below is a possible implementation for the two get
and set
actions defined above. It also demonstrates the usage of a couple of singleton methods. Note that the set
action makes use of the singleton's set
method, for which parameter is the account to pay for the new value stored. In this case, the same account name that is stored in the primary value is the payer. However, it can be a different account if so required.
singleton_example.cpp
#include <singleton_example.hpp>
[[eosio::action]] void singleton_example::set( name user, uint64_t value ) {
auto entry_stored = singleton_instance.get_or_create(user, testtablerow);
entry_stored.primary_value = user;
entry_stored.secondary_value = value;
singleton_instance.set(entry_stored, user);
}
[[eosio::action]] void singleton_example::get( ) {
if (singleton_instance.exists())
eosio::print(
"Value stored for: ",
name{singleton_instance.get().primary_value.value},
" is ",
singleton_instance.get().secondary_value,
"\n");
else
eosio::print("Singleton is empty\n");
}