uint8_t + enum For Statuses
This one is more of a little tip/trick that I like to use - it's not necessarily something you need to know in order to write smart contracts.
There are plenty of times where you'll need to store some type of status in a table, or struct. True/false
, open/closed
, etc. Since you can't modify table structures without going through a complicated process, I've learned over the years that it's best to always plan in advance, and allow for the possibility that you might need more than 2 options.
For this reason, I use uint8_t
to store these values, instead of a boolean. Then I combine this with an enum
in order to improve code clarity / readability.
Let's pretend for example that you have some actions on your contract, and you only want specific users to be able to execute these actions. So, you put a flag in your table called is_authorized
, like this.
struct [[eosio::table, eosio::contract(CONTRACT_NAME)]] users {
eosio::name wallet;
bool is_authorized;
uint64_t primary_key() const { return wallet.value; }
};
using users_table = eosio::multi_index<"users"_n, users>;
The problem you will eventually run into, is that you might add some new action to your contract, which requires higher authorization than a regular user, but less authorization than the users you originally created this flag for. Well, now you have no way of adding a 3rd option.
This is the reason that I use uint8_t
instead.
struct [[eosio::table, eosio::contract(CONTRACT_NAME)]] users {
eosio::name wallet;
uint8_t is_authorized;
// 0: not authorized
// 1: has all privileges
// 2: has some privileges but not all
// 3: is_blacklisted
// Can add more options as needed
uint64_t primary_key() const { return wallet.value; }
};
using users_table = eosio::multi_index<"users"_n, users>;
With this approach, your contract remains flexible and is able to adapt to any new features you may add in the future. If you were simply using a bool
, you would've had to create a new table in order to extend the current privilege structure.
There is a problem here, though. When other parts of your contract are comparing against uint8_t
to determine a status, it becomes difficult to keep track of what each number is supposed to mean. This is where enums
come into play.
static const enum AUTH_TYPES {
NOT_AUTHORIZED,
ALL_PRIVILEGES,
SOME_PRIVILEGES,
BLACKLISTED };
struct [[eosio::table, eosio::contract(CONTRACT_NAME)]] users {
eosio::name wallet;
uint8_t is_authorized;
uint64_t primary_key() const { return wallet.value; }
};
using users_table = eosio::multi_index<"users"_n, users>;
ACTION mycontract::dosomething(){
users_table users_t = users_table( _self, _self.value );
auto itr = users_t.require_find( some_user.value );
// Checking their auth
if( itr->is_authorized == uint8_t(BLACKLISTED) ){
check(false, "you are blacklisted");
}
// Modifying their auth
users_t.modify(itr, same_payer, [&](auto &_user){
_user.is_authorized = uint8_t(SOME_PRIVILEGES);
});
}
This allows you to use uint8_t
for storage, but also allows you to keep the code clean and readable, which is always important (for yourself and for other developers).
Last updated