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