Advanced EOS Series — Part 2 — Singletons
Welcome to the Advanced EOS development series where I’ll be touching on techniques and functionality which is rarely covered by tutorials and courses. The purpose of this series is to bring together the missing pieces you’ll need to complete your skills as an application developer on the EOS network. Each post is ordered by difficulty, so if you’d like a general overview I’d recommend starting with Part 1 and working your way up.
As these are advanced or extended topics, I’m dangerously assuming you already know the basics and are looking to further your knowledge. For that reason, the code shared in these articles will be concise to the topic being discussed.
For Part 2 of this series we will be looking at Singletons, all code for this example can be found here on GitHub.
Singletons should be used to store contract state, or as an alternative to multi-index tables when only one row is required.
Persistence
We’re going to explore singletons as a method to persist our contract state. Using a table for this would be a waste of resources given that our data would only ever occupy one row.
Defining the Singleton
For this example we will use a singleton to store our contracts configuration state. Let’s define our singleton using a struct. We will use a Boolean called closed and char_count which we can use to limit users post size.
struct Config {
bool closed = false;
uint32_t char_count = 144;
};
typedef singleton<N(settings), Config> settings_table;
Next we will create a placeholder variable for our instance, making it global within the context of our contract.
settings_table config;
Initializing the Singleton
We can initialize our singleton each time we use it or in our contracts constructor so it’s available within every action of our contract. Our contract in this instance is named singletons, but you can call it what you’d like.
singletons(action_name self) : contract(self), config(_self, _self) {}
Similar to the multi-index table, initializing the singleton takes a contract-code and scope. In this example we’ve set both the code and scope to_self
.
Getting Singleton Values
Now that we have our singleton initialized, we can start interacting with values by reading our char_count
value and printing it to the console.
auto state = config.get();
print(state.char_count);
And that’s it! Our config.get()
retrieves the singleton we initialized earlier and assigns it to our state variable. Now we can access the properties of our struct using dot notation.
Setting Singleton Values
In this example we will update the char_count
property using the member function singleton.set(STRUCT,PAYER)
. Let’s look at two methods to update our singleton.
Method 1 — Inline Updating
The simplest method is to build our struct inline, passing it directly to our set function as we set it, like so;
config.set(Config{true, 172}, _self);
Here we are setting closed
to true and our char_count
to 172 while delegating _self
as the payer for this RAM. This method is clean when your struct contains only a few properties, and maintains property order, but we will need a better method for larger structs.
Method 2 — Retrieve and Update
Next let’s reopen our contract using a fetch and update method. First we will get our current Config state using the singleton.get()
method, set the closed
value, then set the state again.
auto state = config.get();
state.closed = false;
config.set(state, _self);
And that’s Singletons, simple! In the next part we will be looking at multi-index table indexes, primarily focusing on using secondary indexes. Make sure to click follow if you’d like to be notified when I share more examples.